Il Nmap Scripting Engine: Architettura e Librerie Core
Da Utente a Estensore: Perché NSE è Importante
Il Nmap Scripting Engine (NSE) trasforma Nmap da uno scanner di porte in una piattaforma di ricognizione completamente programmabile. Laddove i flag da riga di comando limitano a comportamenti predefiniti, NSE espone l'infrastruttura di scansione di Nmap—scoperta host, stato delle porte, rilevamento versione e I/O di rete—a script Lua che vengono eseguiti in modo concorrente su migliaia di target. Questa sezione ti guida dall'esecuzione degli script altrui alla comprensione del funzionamento degli script NSE, della valutazione dei target e dello sfruttamento delle librerie core. La padronanza di queste fondamenta ti prepara a scrivere i tuoi script e a valutare criticamente quelli che erediti.
Architettura NSE: Categorie, Fasi di Regola e Modello di Esecuzione
Gli script NSE seguono una struttura rigida con quattro fasi di esecuzione definite dalle regole: prerule, hostrule, portrule e postrule. Queste determinano quando uno script viene eseguito rispetto alla pipeline di scansione di Nmap.
| Tipo di Regola | Condizione di Attivazione | Utilizzo Tipico | |----------------|---------------------------|---------------| | prerule | Una volta prima che inizi qualsiasi scansione host | Configurazione globale, lettura wordlist, inizializzazione database | | hostrule | Per host, dopo il completamento della scoperta host | Controlli basati su host (rilevamento firewall, analisi traceroute) | | portrule | Per porta aperta che corrisponde alla regola | Probe specifici per servizio, controlli vulnerabilità | | postrule | Una volta dopo il completamento di tutte le scansioni host | Reporting, statistiche, correlazione cross-host |
Le regole restituiscono valori booleani; solo true attiva la funzione action dello script. Il portrule è il più comune e sfrutta il rilevamento servizi di Nmap:
portrule = shortport.version_port_or_service({80, 443}, {"http", "https"})
Questo corrisponde alle porte 80 o 443 o ai servizi rilevati come HTTP/HTTPS.
Le categorie organizzano gli script per scopo: auth, broadcast, brute, default, discovery, dos, exploit, external, fuzzer, intrusive, malware, safe, version e vuln. La categoria default viene eseguita con -sC o --script=default; gli script safe evitano di bloccare i servizi; intrusive può attivare allarmi IDS.
Modello di concorrenza: NSE ottiene il parallelismo attraverso le coroutine Lua, non thread del sistema operativo. Ogni esecuzione di script è una coroutine che il motore di Nmap schedula attraverso il suo modello di parallelismo su host e porte. Quando uno script cede sull'I/O di rete (lettura/scrittura socket), Nmap sospende quella coroutine ed esegue altre, massimizzando il throughput senza bloccare. Questo multitasking cooperativo scala efficientemente: un singolo processo Nmap può eseguire migliaia di istanze di script concorrenti.
Meccanismi di Selezione degli Script
L'argomento --script fornisce un controllo granulare oltre le categorie:
# Per nome script (nome base del file, senza estensione .nse)
nmap --script http-title target
# Per categoria
nmap --script "discovery and safe" target
# Per percorso directory
nmap --script /custom/scripts/ target
# Espressioni booleane
nmap --script "(default or discovery) and not intrusive" target
# Script specifici con argomenti
nmap --script ssh-brute --script-args userdb=users.txt,passdb=passes.txt target
Le espressioni supportano and, or, not e parentesi. I nomi degli script possono usare wildcard: http-* corrisponde a tutti gli script relativi a HTTP.
Fondamenti di Lua per NSE: Coroutine, Tabelle e Librerie Standard
Gli script NSE sono programmi Lua 5.3. Tre caratteristiche del linguaggio dominano l'uso di NSE:
Le tabelle fungono da unica struttura dati di NSE—array, dizionari, oggetti e namespace usano tutti le tabelle. L'iterazione di tabelle con pairs (chiavi qualsiasi) e ipairs (indici interi) è onnipresente:
local hosts = {"192.168.1.1", "192.168.1.2"}
for i, host in ipairs(hosts) do
stdnse.debug1("Scanning %s at index %d", host, i)
end
Le coroutine abilitano il modello di I/O non bloccante. Gli script raramente manipolano le coroutine direttamente, ma comprendere coroutine.yield() spiega come nmap.new_socket():receive() sospende l'esecuzione senza bloccare altri script.
NSE estende Lua con librerie standard che sostituiscono o integrano i moduli io e os di Lua per sicurezza (nessun accesso arbitrario ai file) e sandboxing.
Librerie Core: nmap, stdnse, shortport e table
Libreria nmap: Fornisce oggetti host e porta, creazione socket e accesso al registro.
local socket = nmap.new_socket()
socket:connect(host, port)
socket:send("HEAD / HTTP/1.0\r\n\r\n")
local status, response = socket:receive_lines(1)
socket:close()
Funzioni chiave: nmap.fetchfile() individua i file di dati nel percorso di ricerca di Nmap; nmap.registry condivide dati tra script che puntano allo stesso host; nmap.set_port_version() aggiorna la confidenza del fingerprint del servizio.
Libreria stdnse: Funzioni di utilità che ogni script utilizza.
| Funzione | Scopo | |----------|-------| | stdnse.debug(level, fmt, ...) | Output di debug a livelli (usare --script-trace) | | stdnse.format_output(success, table) | Formattazione output standardizzata | | stdnse.get_script_args(name) | Recupero valori --script-args | | stdnse.make_retry_socket(times) | Socket con logica di retry automatico | | stdnse.output_table() | Creazione tabella risultati correttamente formattata |
Libreria shortport: Semplifica la costruzione di portrule con pattern comuni.
-- Corrisponde HTTP su qualsiasi porta, o qualsiasi servizio sulla porta 80/443
portrule = shortport.http
-- Corrisponde combinazioni specifiche porta/servizio
portrule = shortport.port_or_service({21,22,23}, {"ftp","ssh","telnet"})
-- Esclude dalle scansioni default a meno che il servizio corrisponda fortemente
portrule = shortport.version_port_or_service(nil, {"mysql"}, "tcp", "open")
Libreria table: Estensioni alle operazioni standard sulle tabelle di Lua. table.contains(tab, val) verifica l'appartenenza; table.sort(tab, func) con comparatori personalizzati; table.concat() per l'assemblaggio di stringhe.
I/O di Rete: Socket, SSL e Negoziazione Protocollo
Il nmap.new_socket() raw fornisce connettività TCP e UDP. Per SSL/TLS:
local socket = nmap.new_socket()
socket:connect(host, port)
local status, err = socket:reconnect_ssl()
-- Ora leggi/scrivi dati cifrati in modo trasparente
La libreria comm astrae i pattern comuni di negoziazione protocollo—invia un probe, ricevi risposta, corrispondenza con pattern attesi:
local comm = require "comm"
local status, response = comm.exchange(host, port, probe, {proto="tcp", timeout=5000})
if status and response:match("^HTTP/1%.[01]") then
-- Servizio HTTP confermato
end
comm.tryssl() tenta automaticamente l'upgrade SSL per servizi che possono funzionare in chiaro o cifrato (comune per SMTP, IMAP, POP3).
Test Credenziali: brute, creds e unpwdb
Queste librerie standardizzano il test di autenticazione attraverso i protocolli.
Libreria brute: Framework per script di guessing credenziali. Tu fornisci la classe Driver implementando i metodi login() e check(); brute gestisce il threading, la capacità di resume e l'iterazione credenziali.
local brute = require "brute"
local creds = require "creds"
local unpwdb = require "unpwdb"
-- Inizializza iteratori username/password
local usernames, pwds = unpwdb.usernames(), unpwdb.passwords()
local Engine = {
new = function(self, host, port)
local o = { host = host, port = port }
setmetatable(o, self)
self.__index = self
return o
end,
login = function(self, username, password)
local socket = nmap.new_socket()
-- Tentativo di autenticazione specifico per protocollo
-- Restituisci true, creds.Account in caso di successo; false in caso di fallimento
end,
check = function(self)
-- Verifica che il servizio sia adatto al brute-forcing
return true
end
}
local status, result = brute.new(Engine, host, port, { usernames = usernames, passwords = pwds }):start()
Libreria creds: Rappresenta e memorizza credenziali scoperte con creds.Account:new(username, password, state), dove state è creds.State.VALID, INVALID o LOCKED. Gli account popolano il database credenziali di Nmap, accessibile attraverso gli script via nmap.registry.creds.
Libreria unpwdb: Fornisce iteratori sulle liste integrate di username e password, file specificati dall'utente via --script-args userdb=..., o valori singoli.
Standard di Documentazione e Parsing Argomenti
Ogni script NSE richiede commenti di documentazione strutturati analizzati da nmap --script-help:
---
-- @usage nmap --script http-example --script-args http-example.path=/admin
-- @output
-- PORT STATE SERVICE
-- 80/tcp open http
-- | http-example:
-- | Title: Administration Panel
-- | Server: nginx/1.18.0
-- @xmloutput
-- <elem key="title">Administration Panel</elem>
-- <elem key="server">nginx/1.18.0</elem>
| Elemento | Scopo | |----------|-------| | @usage | Esempi di righe di comando | | @output | Output umanamente leggibile atteso (delimitato da pipe) | | @xmloutput | Struttura XML corrispondente per output -oX | | @args | Parametri --script-args documentati |
Parsing argomenti in action():
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 10000
La variabile SCRIPT_NAME viene impostata automaticamente al nome base dello script.
Walkthrough Completo di uno Script
Ecco uno script minimale che dimostra la struttura dalla regola all'azione:
local http = require "http"
local shortport = require "shortport"
local stdnse = require "stdnse"
-- Regola: esegui contro servizi HTTP
portrule = shortport.http
-- Azione: recupera e riporta l'header server
action = function(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local response = http.get(host, port, path)
if not response then
return nil
end
local server = response.header["server"] or "unknown"
return string.format("Server: %s", server)
end
Flusso di esecuzione: Nmap scansiona il target → il portrule valuta true per porta HTTP aperta → coroutine creata con action(host, port) → http.get() cede sull'I/O socket → altri script vengono eseguiti → risposta ricevuta → coroutine riprende → valore di ritorno formattato per l'output.
Comprendere questa pipeline—dalla corrispondenza della regola attraverso lo scheduling delle coroutine fino all'utilizzo delle librerie—fornisce le fondamenta per valutare gli script esistenti e eventualmente crearne di propri.