From 95d7856978aa8eae4a9ebf4b2e4e17478efb5b9e Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Mon, 22 Apr 2024 23:47:50 +1000 Subject: [PATCH] =?UTF-8?q?NMAPDEV=20plugin=20work=20#645=20=F0=9F=86=95?= =?UTF-8?q?=F0=9F=94=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/js/common.js | 17 +- front/plugins/nmap_dev_scan/README.md | 20 + front/plugins/nmap_dev_scan/config.json | 562 ++++++++++++++++++++++++ front/plugins/nmap_dev_scan/nmap_dev.py | 144 ++++++ front/settings.php | 2 +- server/initialise.py | 2 +- 6 files changed, 737 insertions(+), 10 deletions(-) create mode 100755 front/plugins/nmap_dev_scan/README.md create mode 100755 front/plugins/nmap_dev_scan/config.json create mode 100755 front/plugins/nmap_dev_scan/nmap_dev.py diff --git a/front/js/common.js b/front/js/common.js index 3fed6e92..c4c2df42 100755 --- a/front/js/common.js +++ b/front/js/common.js @@ -1211,11 +1211,6 @@ function resetInitializedFlag() } -// ----------------------------------------------------------------------------- -function isAppInitialized() -{ - return sessionStorage.getItem(sessionStorageKey) === "true"; -} // ----------------------------------------------------------------------------- // 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 function handleFirstLoad(callback) { - if(!app_common_init) + if(!isAppInitialized()) { setTimeout(function() { @@ -1256,14 +1251,20 @@ function handleFirstLoad(callback) // ----------------------------------------------------------------------------- // Check if the code has been executed before by checking sessionStorage -var app_common_init = sessionStorage.getItem(sessionStorageKey) === "true"; var completedCalls = [] 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 function executeOnce() { - if (!arraysContainSameValues(getCache(completedCalls), completedCalls_final)) { + if ( !isAppInitialized()) { showSpinner() diff --git a/front/plugins/nmap_dev_scan/README.md b/front/plugins/nmap_dev_scan/README.md new file mode 100755 index 00000000..900e022c --- /dev/null +++ b/front/plugins/nmap_dev_scan/README.md @@ -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) + diff --git a/front/plugins/nmap_dev_scan/config.json b/front/plugins/nmap_dev_scan/config.json new file mode 100755 index 00000000..466709c0 --- /dev/null +++ b/front/plugins/nmap_dev_scan/config.json @@ -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": "" + } + ], + "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 schedule and then you specify a cron-like schedule in the ARPSCAN_RUN_SCHDsetting. ⚠ Use the same schedule if you have multiple 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 schedule y luego se especifica una programación similar a cron en la configuración ARPSCAN_RUN_SCHD " + }, + { + "language_code": "de_de", + "string": "Auswählen wann der Netzwerkscan laufen soll. Typischerweise wird schedule ausgewählt und ein cron-Intervall in der ARPSCAN_RUN_SCHDEinstellung 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 schedule in the ARPSCAN_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering */3 * * * * will run the scan every 3 minutes. Will be run NEXT time the time passes.
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 schedule en la configuración ARPSCAN_RUN. Asegúrese de ingresar la programación en el formato similar a cron correcto (por ejemplo, valide en crontab.guru). Por ejemplo, ingresar */3 * * * * ejecutará el escaneo cada 3 minutos. Se ejecutará la PRÓXIMA vez que pase el tiempo.
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 schedule in der ARPSCAN_RUN Einstellung ausgewählt wurde. Sichergehen, dass das Intervall in einem korrekten cron-ähnlichen Format angegeben wurde (z.B. auf crontab.guru testen). */3 * * * * würde den Scan alle 3 Minuten starten. Wird erst beim NÄCHSTEN Intervall ausgeführt.
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 CTRL + Click to select/deselect. " + }, + { + "language_code": "es_es", + "string": "Envía una notificación si los valores seleccionados cambian. Utilice CTRL + clic para seleccionar/deseleccionar. " + }, + { + "language_code": "de_de", + "string": "Sende eine Benachrichtigung, wenn ein ausgwählter Wert sich ändert. STRG + klicken zum aus-/abwählen. " + } + ] + }, + { + "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:
sudo nmap -sn ." + } + ] + } + ], + "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": "
" + }, + { + "equals": "watched-changed", + "replacement": "
" + }, + { + "equals": "new", + "replacement": "
" + }, + { + "equals": "missing-in-last-scan", + "replacement": "
" + } + ], + "localized": [ + "name" + ], + "name": [ + { + "language_code": "en_us", + "string": "Status" + }, + { + "language_code": "es_es", + "string": "Estado" + }, + { + "language_code": "de_de", + "string": "Status" + } + ] + } + ] +} \ No newline at end of file diff --git a/front/plugins/nmap_dev_scan/nmap_dev.py b/front/plugins/nmap_dev_scan/nmap_dev.py new file mode 100755 index 00000000..80b71ffd --- /dev/null +++ b/front/plugins/nmap_dev_scan/nmap_dev.py @@ -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() \ No newline at end of file diff --git a/front/settings.php b/front/settings.php index 846b6bbd..09325963 100755 --- a/front/settings.php +++ b/front/settings.php @@ -783,7 +783,7 @@ while ($row = $result -> fetchArray (SQLITE3_ASSOC)) { window.location.reload() - }, 1000); + }, 3000); } } diff --git a/server/initialise.py b/server/initialise.py index e3844b87..1b57a224 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -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') # 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 conf.tz = timezone(conf.TIMEZONE)