Estensione di Nmap: Sviluppo di Script Personalizzati e Contributo alla Community
Configurazione dell'Ambiente di Sviluppo
Prima di scrivere script NSE di livello produzione, è necessario un ambiente di build che supporti il debugging e l'iterazione rapida. Compilare Nmap dai sorgenti garantisce che i tuoi script siano destinati alla versione esatta del motore che stai testando.
Compilazione dai Sorgenti per il Testing degli Script
# Clona e configura una build debug-friendly
git clone https://github.com/nmap/nmap.git
cd nmap
./configure --with-openssl --with-libssh2 --enable-debug
make -j$(nproc)
sudo make install
Il flag --enable-debug preserva le tabelle dei simboli e disabilita le ottimizzazioni, permettendoti di tracciare l'esecuzione Lua tramite nmap --script-trace e ispezionare lo stato interno del motore NSE. Per il debugging specifico di Lua, installa luacheck per l'analisi statica e busted per il unit testing della logica dello script in isolamento:
luarocks install luacheck
luarocks install busted
Crea un .luacheckrc nella root del tuo progetto sintonizzato sull'ambiente Lua di Nmap:
std = "nmap" -- definizione std personalizzata
globals = {"nmap", "shortport", "stdnse", "table"}
Scrivere uno Script NSE Completo: Enumerazione delle API Metadata Cloud
L'infrastruttura contemporanea espone superfici di attacco critiche attraverso le API metadata cloud. Lo script seguente enumera AWS IMDSv2 (Instance Metadata Service version 2), che richiede un workflow di session token—una dimostrazione perfetta delle macchine a stati dei protocolli e della gestione degli errori.
Script Completo: aws-imdsv2-enum.nse
local http = require "http"
local json = require "json"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
description = [[
Enumerates AWS IMDSv2 metadata endpoints. IMDSv2 requires
session-oriented access: a PUT for token acquisition, then
token-authenticated GET requests. This script implements the
full state machine, with strict timeout and error handling.
]]
author = "Your Name <[email protected]>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
-- Line 16: Rule targets HTTP services on port 80, 443, or any
-- service fingerprinted as "http"
portrule = shortport.http
-- Line 20: Configuration options with sensible defaults
local function get_options()
return {
max_retries = stdnse.get_script_args(SCRIPT_NAME .. ".max-retries") or 2,
token_ttl = stdnse.get_script_args(SCRIPT_NAME .. ".token-ttl") or 300,
path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
}
end
-- Line 28: IMDSv2 token acquisition (state: UNAUTHENTICATED → TOKEN_HELD)
local function acquire_token(host, port, ttl, max_retries)
local request_opts = {
header = {
["X-aws-ec2-metadata-token-ttl-seconds"] = tostring(ttl)
}
}
-- Line 36: Structured retry loop with exponential backoff
for attempt = 1, max_retries do
local response = http.put(host, port, "/latest/api/token", request_opts)
-- Line 40: Explicit nil-check prevents runtime errors on connection failure
if response and response.status == 200 and response.body then
return true, string.gsub(response.body, "%s+$", "") -- trim trailing whitespace
end
-- Line 44: Backoff with jitter to avoid thundering herd
if attempt < max_retries then
stdnse.sleep(0.5 * attempt)
end
end
return false, "Token acquisition failed after " .. max_retries .. " attempts"
end
-- Line 52: Authenticated metadata retrieval (state: TOKEN_HELD → DATA_RETRIEVED)
local function fetch_metadata(host, port, token, path)
local request_opts = {
header = {
["X-aws-ec2-metadata-token"] = token
}
}
-- Line 60: Path traversal prevention—reject user input with ".."
if string.match(path, "%.%.") then
return false, "Invalid path: directory traversal detected"
end
local target_path = "/latest/meta-data" .. path
local response = http.get(host, port, target_path, request_opts)
-- Line 68: Distinguish HTTP errors from protocol-level failures
if not response then
return false, "Connection terminated without response"
end
if response.status ~= 200 then
return false, string.format("HTTP %d: %s", response.status, response.status_line or "unknown")
end
return true, response.body
end
-- Line 78: Main action with comprehensive error propagation
action = function(host, port)
local opts = get_options()
stdnse.debug1("Targeting %s:%d with TTL=%d, retries=%d",
host.ip, port.number, opts.token_ttl, opts.max_retries)
-- Line 83: State machine execution with early returns on failure
local token_ok, token = acquire_token(host, port, opts.token_ttl, opts.max_retries)
if not token_ok then
stdnse.debug1("Token phase failed: %s", token)
return nil -- NSE convention: nil suppresses output for clean hosts
end
stdnse.debug2("Token acquired: %s...", string.sub(token, 1, 16))
local fetch_ok, data = fetch_metadata(host, port, token, opts.path)
if not fetch_ok then
-- Line 93: Structured error reporting for script-debugging
return string.format("ERROR: %s", data)
end
-- Line 97: Attempt JSON parsing, fallback to raw output
local json_ok, parsed = json.parse(data)
if json_ok then
return parsed
else
return data
end
end
Analisi Riga per Riga:
- Righe 1–15: Import dei moduli e metadata. Il campo
licensedeve dichiarare la compatibilità GPLv2 o usare il riferimento alla licenza standard di Nmap. - Righe 20–26:
stdnse.get_script_args()fornisce la configurazione namespaced, prevenendo collisioni con le opzioni di altri script. - Righe 28–50: L'acquisizione del token implementa una macchina a stati del protocollo: non autenticato → richiesta token → token in possesso. La logica di retry gestisce le partizioni di rete transitorie comuni negli ambienti cloud.
- Righe 52–76: Il recupero dei metadata impone la validazione dell'input (riga 60) e distingue le modalità di fallimento: fallimenti di connessione versus codici di errore HTTP versus risposte di successo.
- Righe 78–101: La funzione
actionorchestra le transizioni di stato e implementa le convenzioni di output dell'NSE—restituiscinilper rimanere silenzioso sui target non vulnerabili, prevenendo rumore nell'output.
Il Processo di Submission a nmap-dev
Contribuire script upstream richiede di navigare sia barriere tecniche che sociali. Il progetto Nmap mantiene gate di qualità rigorosi.
Standard di Code Review
Le submission a [email protected] subiscono una review threadata dai maintainer (principalmente Fyodor, nnposter, e bonsaiviking). Pattern comuni di rifiuto:
| Problema | Frequenza | Risoluzione | |----------|-----------|------------| | Documentazione newtargets script-arg mancante | Molto comune | Includere tutti gli argomenti in description | | Timeout hardcoded senza stdnse.get_timeout | Comune | Rispettare i template di timing globali di Nmap | | Ambiguità nell'assegnazione del copyright | Occasionale | Dichiarazione GPLv2+ esplicita richiesta | | Matching portrule eccessivamente ampio | Comune | Usare predicati shortport, non liste di porte raw |
Requisiti di Documentazione
Ogni script deve includere una description adatta per nmap --script-help. Per protocolli complessi, allegare un esempio di usage nel post della mailing list:
nmap --script aws-imdsv2-enum --script-args \
aws-imdsv2-enum.path=/iam/security-credentials/ \
-p 80,443 <target>
Licenze: Gli script NSE ereditano la licenza di Nmap (GPLv2 con eccezioni specifiche). Non sottomettere lavoro con licenza BSD o MIT senza esplicito permesso di relicensing. Il campo license deve fare riferimento a https://nmap.org/book/man-legal.html o dichiarare license = "GPLv2".
Manutenzione degli Script Attraverso gli Aggiornamenti di Nmap
Il motore NSE di Nmap evolve, a volte rompendo la compatibilità all'indietro. Traccia questi vettori di deprecazione:
- Deprecazione API: La classe
Enginedella libreriabruteha sostituito l'iterazioneunpwdbin Nmap 7.92. Gli script che usano pattern vecchi emettono warning; aggiornare prima della rimozione. - Cambiamenti comportamentali del motore: Nmap 7.94 ha modificato la gestione dei redirect di
http.lua—max_redirectsora defaulta a 0 per gli script, rompendo il following implicito. Audit delCHANGELOGprima dei major release. - Testing di regressione: Mantieni un test harness privato:
# Regressione automatizzata contro servizi mock locali
nmap --script your-script.nse \
--script-args your-script.testmode=1 \
-p 8080 127.0.0.1 \
-oX regression-$(date +%Y%m%d).xml
Confronta gli output XML tra le versioni di Nmap con ndiff o query XPath custom.
Repository di Script Privati
Non tutti gli script appartengono all'upstream. Le esigenze organizzative spesso richiedono la distribuzione privata.
Struttura del Repository e Registrazione
/opt/nmap-custom-scripts/
├── scripts/
│ └── internal-vpn-scanner.nse
├── script.db # generato; non editare manualmente
└── nse_main.lua # opzionale: precarica librerie custom
Registra nel database di Nmap:
# Dopo aver aggiunto i file .nse, ricostruisci l'indice degli script
nmap --script-updatedb
# Verifica la registrazione
nmap --script-help internal-vpn-scanner
Il meccanismo --script-updatedb parsa i campi SCRIPT_NAME, categories, author, e description per popolare script.db. Critico: Questo deve essere eseguito dopo ogni aggiunta o rimozione di script; altrimenti il motore script di Nmap non risolverà i nomi.
Integrazione con il Version Control
Hook pre-commit per validare la sintassi e triggerare --script-updatedb:
#!/bin/bash
# .git/hooks/pre-commit
for f in $(git diff --cached --name-only --diff-filter=ACM | grep "\.nse$"); do
luacheck "$f" || exit 1
cp "$f" /usr/share/nmap/scripts/
done
nmap --script-updatedb
git add /usr/share/nmap/scripts/script.db
Deployment Sicuro: Distribuisci tramite Git tag firmati o repository di pacchetti interni. Non esporre mai i file .nse via HTTP non autenticato—gli script eseguono con i privilegi di Nmap, e gli attacchi alla supply chain contro NSE sono banali se avviene un'intercettazione.
Motori Alternativi: Quando NSE Raggiunge i Suoi Limiti
L'architettura Lua interpretata di NSE impone vincoli intrinseci. Riconosci i punti di divergenza:
Colli di Bottiglia delle Prestazioni
| Scenario | Limitazione NSE | Alternativa | |----------|---------------|-------------| | Scansione SYN ad alta throughput con stato complesso | Lua single-threaded per host | masscan + analizzatore dedicato | | Carico crittografico pesante (parsing certificati TLS) | LuaJIT assente; crittografia pura Lua lenta | Tool Rust/Go con integrazione libpcap | | Connessioni long-lived con I/O asincrono | Solo multitasking cooperativo; nessun vero epoll | Python asyncio o proxy dedicato |
Requisiti Linguistici: Quando le librerie esistenti (es. Kubernetes client-go, AWS SDK v2) fanno impallidire lo sforzo di reimplementazione, wrappa Nmap per la discovery e delega a tool compilati:
# Nmap identifica l'endpoint; tool specializzato lo sfrutta
nmap -p 10250 --open -oG - <cidr> | \
awk '/Host/{print $2}' | \
xargs -I{} kubelet-analyzer --node {}
Tuttavia, resistere all'astrazione prematura. L'integrazione di NSE con la discovery degli host, l'OS fingerprinting, e i formati di output di Nmap fornisce una coerenza di ricognizione insostituibile. La frammentazione in toolchain sacrifica il modello dati unificato che rende Nmap potente.
Il praticante avanzato contribuisce upstream dove possibile, mantiene repository privati dove necessario, e sceglie motori alternativi solo quando le prestazioni interpretate di NSE bloccano genuinamente la missione.