NMAPDEV plugin work #645 🆕🔎

This commit is contained in:
jokob-sk
2024-04-22 23:47:50 +10:00
parent 0846c3914a
commit 95d7856978
6 changed files with 737 additions and 10 deletions

View File

@@ -1211,11 +1211,6 @@ function resetInitializedFlag()
} }
// -----------------------------------------------------------------------------
function isAppInitialized()
{
return sessionStorage.getItem(sessionStorageKey) === "true";
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// check if cache needs to be refreshed because of setting changes // check if cache needs to be refreshed because of setting changes
@@ -1244,7 +1239,7 @@ $.get('api/app_state.json?nocache=' + Date.now(), function(appState) {
// Display spinner and reload page if not yet initialized // Display spinner and reload page if not yet initialized
function handleFirstLoad(callback) function handleFirstLoad(callback)
{ {
if(!app_common_init) if(!isAppInitialized())
{ {
setTimeout(function() { setTimeout(function() {
@@ -1256,14 +1251,20 @@ function handleFirstLoad(callback)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Check if the code has been executed before by checking sessionStorage // Check if the code has been executed before by checking sessionStorage
var app_common_init = sessionStorage.getItem(sessionStorageKey) === "true";
var completedCalls = [] var completedCalls = []
var completedCalls_final = ['cacheSettings', 'cacheStrings', 'cacheDevices']; var completedCalls_final = ['cacheSettings', 'cacheStrings', 'cacheDevices'];
// -----------------------------------------------------------------------------
function isAppInitialized()
{
return arraysContainSameValues(getCache("completedCalls").split(',').filter(Boolean), completedCalls_final)
}
// Define a function that will execute the code only once // Define a function that will execute the code only once
function executeOnce() { function executeOnce() {
if (!arraysContainSameValues(getCache(completedCalls), completedCalls_final)) { if ( !isAppInitialized()) {
showSpinner() showSpinner()

View File

@@ -0,0 +1,20 @@
## Overview
Arp-scan is a command-line tool that uses the ARP protocol to discover and fingerprint IP hosts on the local network. An alternative to ARP scan is to enable the `PIHOLE_RUN` PiHole integration settings. The arp-scan (and other Network-scan plugin times using the `SCAN_SUBNETS` setting) time depends on the number of IP addresses to check so set this up carefully with the appropriate network mask and interface. Check the [subnets documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md) for help on setting up VLANs, what VLANs are supported, or how to figure out the network mask and your interface.
### Usage
- Go to settings and set the `SCAN_SUBNETS` setting as per [subnets documentation](https://github.com/jokob-sk/NetAlertX/blob/main/docs/SUBNETS.md).
- Enable the plugin by changing the RUN parameter from disabled to your preferred run time (usually: `schedule`).
- Specify the schedule in the `ARPSCAN_RUN_SCHD` setting
- Adjust the timeout if needed in the `ARPSCAN_RUN_TIMEOUT` setting
- Review remaining settings
- SAVE
- Wait for the next scan to finish
#### Examples
Settings:
![settings](/front/plugins/arp_scan/arp-scan-settings.png)

View File

@@ -0,0 +1,562 @@
{
"code_name": "nmap_dev_scan",
"unique_prefix": "NMAPDEV",
"plugin_type": "device_scanner",
"enabled": true,
"data_source": "script",
"mapped_to_table": "CurrentScan",
"data_filters": [
{
"compare_column": "Object_PrimaryID",
"compare_operator": "==",
"compare_field_id": "txtMacFilter",
"compare_js_template": "'{value}'.toString()",
"compare_use_quotes": true
}
],
"show_ui": true,
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
"string": "NMAP Device discovery"
}
],
"icon": [
{
"language_code": "en_us",
"string": "<i class=\"fa-solid fa-search\"></i>"
}
],
"description": [
{
"language_code": "en_us",
"string": "This plugin is to execute an NMAP scan for device discovery on the local network"
}
],
"params": [
{
"name": "subnets",
"type": "setting",
"value": "SCAN_SUBNETS",
"base64": true
}
],
"settings": [
{
"function": "RUN",
"type": "text.select",
"default_value": "disabled",
"options": [
"disabled",
"once",
"schedule",
"always_after_scan",
"on_new_device"
],
"localized": [
"name",
"description"
],
"events": [
"run"
],
"name": [
{
"language_code": "en_us",
"string": "When to run"
},
{
"language_code": "es_es",
"string": "Cuando ejecutar"
},
{
"language_code": "de_de",
"string": "Wann ausführen"
}
],
"description": [
{
"language_code": "en_us",
"string": "Specify when your Network-discovery scan will run. Typical setting would be <code>schedule</code> and then you specify a cron-like schedule in the <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>setting</a>. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
},
{
"language_code": "es_es",
"string": "Especifique cuándo se ejecutará su análisis de descubrimiento de red. La configuración típica sería <code>schedule</code> y luego se especifica una programación similar a cron en la configuración <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code></a> "
},
{
"language_code": "de_de",
"string": "Auswählen wann der Netzwerkscan laufen soll. Typischerweise wird <code>schedule</code> ausgewählt und ein cron-Intervall in der <a href=\"#ARPSCAN_RUN_SCHD\"><code>ARPSCAN_RUN_SCHD</code>Einstellung</a> gesetzt."
}
]
},
{
"function": "CMD",
"type": "readonly",
"default_value": "python3 /app/front/plugins/nmap_dev_scan/nmap_dev.py userSubnets={subnets}",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Command"
},
{
"language_code": "es_es",
"string": "Comando"
},
{
"language_code": "de_de",
"string": "Befehl"
}
],
"description": [
{
"language_code": "en_us",
"string": "Command to run. This should not be changed"
},
{
"language_code": "es_es",
"string": "Comando para ejecutar. Esto no debe ser cambiado"
},
{
"language_code": "de_de",
"string": "Auszuführender Befehl. Dieser sollte nicht geändert werden"
}
]
},
{
"function": "RUN_TIMEOUT",
"type": "integer",
"default_value": 300,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Run timeout"
},
{
"language_code": "es_es",
"string": "Tiempo límite de ejecución"
},
{
"language_code": "de_de",
"string": "Zeitlimit"
}
],
"description": [
{
"language_code": "en_us",
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
},
{
"language_code": "es_es",
"string": "Tiempo máximo en segundos para esperar a que finalice el script. Si se supera este tiempo, se cancela el script."
},
{
"language_code": "de_de",
"string": "Maximale Zeit in Sekunden, die auf den Abschluss des Skripts gewartet werden soll. Bei Überschreitung dieser Zeit wird das Skript abgebrochen."
}
]
},
{
"function": "RUN_SCHD",
"type": "text",
"default_value": "*/5 * * * *",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Schedule"
},
{
"language_code": "es_es",
"string": "Schedule"
},
{
"language_code": "de_de",
"string": "Zeitplan"
}
],
"description": [
{
"language_code": "en_us",
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>*/3 * * * *</code> will run the scan every 3 minutes. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
},
{
"language_code": "es_es",
"string": "Solo está habilitado si selecciona <code>schedule</code> en la configuración <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_RUN</code></a>. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). Por ejemplo, ingresar <code>*/3 * * * *</code> ejecutará el escaneo cada 3 minutos. Se ejecutará la PRÓXIMA vez que pase el tiempo. <br/> Se recomienda utilizar el mismo intervalo de programación para todos los complementos que analizan su red."
},
{
"language_code": "de_de",
"string": "Nur aktiv, wenn <code>schedule</code> in der <a href=\"#ARPSCAN_RUN\"><code>ARPSCAN_RUN</code> Einstellung</a> ausgewählt wurde. Sichergehen, dass das Intervall in einem korrekten cron-ähnlichen Format angegeben wurde (z.B. auf <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a> testen). <code>*/3 * * * *</code> würde den Scan alle 3 Minuten starten. Wird erst beim NÄCHSTEN Intervall ausgeführt. <br/>Es wird empfohlen, das Intervall aller Plugins, welche nach neuen Geräten suchen, auf den gleichen Wert zu setzen."
}
]
},
{
"function": "WATCH",
"type": "text.multiselect",
"default_value": [
"Watched_Value1",
"Watched_Value2"
],
"options": [
"Watched_Value1",
"Watched_Value2",
"Watched_Value3",
"Watched_Value4"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Watched"
},
{
"language_code": "es_es",
"string": "Watched"
},
{
"language_code": "de_de",
"string": "Überwacht"
}
],
"description": [
{
"language_code": "en_us",
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>Watched_Value1</code> is IP</li><li><code>Watched_Value2</code> is Vendor</li><li><code>Watched_Value3</code> is Interface </li><li><code>Watched_Value4</code> is N/A </li></ul>"
},
{
"language_code": "es_es",
"string": "Envía una notificación si los valores seleccionados cambian. Utilice <code>CTRL + clic</code> para seleccionar/deseleccionar. <ul> <li><code>Valor_observado1</code> es IP</li><li><code>Valor_observado2</code> es Proveedor</li><li><code>Valor_observado3</code> es Interfaz </li><li><code>Valor_observado4</code> es N/A </li></ul>"
},
{
"language_code": "de_de",
"string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. <code>STRG + klicken</code> zum aus-/abwählen. <ul> <li><code>Watched_Value1</code> ist die IP</li><li><code>Watched_Value2</code> ist der Hersteller</li><li><code>Watched_Value3</code> ist das Interface </li><li><code>Watched_Value4</code> ist nicht in Verwendung </li></ul>"
}
]
},
{
"function": "REPORT_ON",
"type": "text.multiselect",
"default_value": [
"new"
],
"options": [
"new",
"watched-changed",
"watched-not-changed",
"missing-in-last-scan"
],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Report on"
},
{
"language_code": "es_es",
"string": "Informar sobre"
},
{
"language_code": "de_de",
"string": "Benachrichtige wenn"
}
],
"description": [
{
"language_code": "en_us",
"string": "When should notification be sent out."
},
{
"language_code": "es_es",
"string": "Cuándo debe enviarse una notificación."
},
{
"language_code": "de_de",
"string": "Wann Benachrichtigungen gesendet werden sollen."
}
]
},
{
"function": "ARGS",
"type": "text",
"default_value": "sudo nmap -sn ",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Arguments"
}
],
"description": [
{
"language_code": "en_us",
"string": "Arguments to run nmap-scan with. Recommended and tested only with the setting: <br/> <code>sudo nmap -sn </code>."
}
]
}
],
"database_column_definitions": [
{
"column": "Object_PrimaryID",
"mapped_to_column": "cur_MAC",
"css_classes": "col-sm-2",
"show": true,
"type": "device_name_mac",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "MAC"
},
{
"language_code": "es_es",
"string": "MAC"
},
{
"language_code": "de_de",
"string": "MAC"
}
]
},
{
"column": "Object_SecondaryID",
"mapped_to_column": "cur_IP",
"css_classes": "col-sm-2",
"show": true,
"type": "device_ip",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "IP"
},
{
"language_code": "es_es",
"string": "IP"
},
{
"language_code": "de_de",
"string": "IP"
}
]
},
{
"column": "Watched_Value1",
"mapped_to_column": "cur_Name",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Name"
}
]
},
{
"column": "Watched_Value2",
"mapped_to_column": "cur_Vendor",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Vendor"
},
{
"language_code": "es_es",
"string": "Proveedor"
},
{
"language_code": "de_de",
"string": "Hersteller"
}
]
},
{
"column": "Watched_Value3",
"mapped_to_column": "cur_LastQuery",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value":"",
"options": [],
"localized": ["name"],
"name":[{
"language_code":"en_us",
"string" : "Interface"
},
{
"language_code":"es_es",
"string" : "Interfaz"
}]
},
{
"column": "Dummy",
"mapped_to_column": "cur_ScanMethod",
"mapped_to_column_data": {
"value": "nmap-dev-scan"
},
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Scan method"
},
{
"language_code": "es_es",
"string": "Método de escaneo"
},
{
"language_code": "de_de",
"string": "Scanmethode"
}
]
},
{
"column": "DateTimeCreated",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Created"
},
{
"language_code": "es_es",
"string": "Creado"
},
{
"language_code": "de_de",
"string": "Erstellt"
}
]
},
{
"column": "DateTimeChanged",
"css_classes": "col-sm-2",
"show": true,
"type": "label",
"default_value": "",
"options": [],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Changed"
},
{
"language_code": "es_es",
"string": "Cambiado"
},
{
"language_code": "de_de",
"string": "Geändert"
}
]
},
{
"column": "Status",
"css_classes": "col-sm-1",
"show": true,
"type": "replace",
"default_value": "",
"options": [
{
"equals": "watched-not-changed",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
},
{
"equals": "watched-changed",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
},
{
"equals": "new",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
},
{
"equals": "missing-in-last-scan",
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
}
],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
"string": "Status"
},
{
"language_code": "es_es",
"string": "Estado"
},
{
"language_code": "de_de",
"string": "Status"
}
]
}
]
}

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env python
# test script by running:
# tbc
import os
import pathlib
import argparse
import subprocess
import sys
import hashlib
import csv
import sqlite3
import re
from io import StringIO
from datetime import datetime
# Register NetAlertX directories
INSTALL_PATH="/app"
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
from plugin_helper import Plugin_Object, Plugin_Objects, decodeBase64
from logger import mylog, append_line_to_file
from helper import timeNowTZ, get_setting_value
from const import logPath, applicationPath, fullDbPath
from database import DB
from device import Device_obj
CUR_PATH = str(pathlib.Path(__file__).parent.resolve())
LOG_FILE = os.path.join(CUR_PATH, 'script.log')
RESULT_FILE = os.path.join(CUR_PATH, 'last_result.log')
pluginName = 'NMAPDEV'
def main():
mylog('verbose', [f'[{pluginName}] In script'])
# Create a database connection
db = DB() # instance of class DB
db.open()
timeout = get_setting_value('NMAPDEV_RUN_TIMEOUT')
subnets = get_setting_value('SCAN_SUBNETS')
mylog('verbose', [f'[{pluginName}] subnets: ', subnets])
# Initialize the Plugin obj output file
plugin_objects = Plugin_Objects(RESULT_FILE)
unique_devices = execute_scan(subnets, timeout)
mylog('verbose', [f'[{pluginName}] Unknown devices count: {len(unique_devices)}'])
for device in unique_devices:
plugin_objects.add_object(
# "MAC", "IP", "Name", "Vendor", "Interface"
primaryId = device[0],
secondaryId = device[1],
watched1 = device[2],
watched2 = device[3],
watched3 = device[4],
watched4 = '',
extra = '',
foreignKey = device[0])
plugin_objects.write_result_file()
mylog('verbose', [f'[{pluginName}] Script finished'])
return 0
#===============================================================================
# Execute scan
#===============================================================================
def execute_scan (subnets_list, timeout):
# output of possible multiple interfaces
scan_output = ""
devices_list = []
# scan each interface
for interface in subnets_list :
scan_output = execute_scan_on_interface (interface, timeout)
mylog('verbose', [f'[{pluginName}] scan_output: ', scan_output])
# Regular expression patterns
entry_pattern = r'Nmap scan report for (.*?) \((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\)'
mac_pattern = r'MAC Address: ([0-9A-Fa-f:]+)'
vendor_pattern = r'\((.*?)\)'
# Compile regular expression patterns
entry_regex = re.compile(entry_pattern)
mac_regex = re.compile(mac_pattern)
vendor_regex = re.compile(vendor_pattern)
# Find all matches
entries = entry_regex.findall(scan_output)
mac_addresses = mac_regex.findall(scan_output)
vendors = vendor_regex.findall(scan_output)
for i in range(len(entries)):
name, ip_address = entries[i]
devices_list.append([mac_addresses[i], ip_address, name, vendors[i], interface])
return devices_list
def execute_scan_on_interface (interface, timeout):
# Prepare command arguments
scan_args = get_setting_value('NMAPDEV_ARGS').split() + [interface.split()[0]]
mylog('verbose', [f'[{pluginName}] scan_args: ', scan_args])
# Execute command
try:
# try running a subprocess safely
result = subprocess.check_output(scan_args, universal_newlines=True)
except subprocess.CalledProcessError as e:
# An error occurred, handle it
error_type = type(e).__name__ # Capture the error type
result = ""
return result
#===============================================================================
# BEGIN
#===============================================================================
if __name__ == '__main__':
main()

View File

@@ -783,7 +783,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) {
window.location.reload() window.location.reload()
}, 1000); }, 3000);
} }
} }

View File

@@ -116,7 +116,7 @@ def importConfigs (db):
conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', 'list', '', 'General') conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', 'list', '', 'General')
# ARPSCAN (+ more settings are provided by the ARPSCAN plugin) # ARPSCAN (+ more settings are provided by the ARPSCAN plugin)
conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', 'subnets', '', 'ARPSCAN') conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', 'subnets', '', 'General')
# Init timezone in case it changed # Init timezone in case it changed
conf.tz = timezone(conf.TIMEZONE) conf.tz = timezone(conf.TIMEZONE)