La lentezza di una barra 16

Sul lavoro ho l’esigenza di mappare grosse reti, per scoprire quello che c’è dentro. I software di asset inventory sono un’opzione, richiedono però un agent a bordo e, se per qualsiasi motivo, l’agent non c’è ho un problema di copertura del mio inventario.
Voglio quindi affidarmi alla rete. Per questo cosa posso usare? Tutti in coro
nmap
. Già e se volessi solo questo potrei accontentarmi di questo script
shell:
#!/usr/bin/env bash
#
# This is an host discovery script
#
#
# -PS = Port Syn (TCP Syn)
# -PE = Echo Request
# --privileged = assume the user has priviliges
# -PU = UDP Ping
# -n = no resolution (for speed)
# -v = increase verbosity
# -oX = xml output
NMAP=`which nmap`
ICMP_SCAN="-PE"
SYN_SCAN_PORTS="21-23,25,53,80,135,137,139,443,445,3389,8080,8181"
UDP_SCAN_PORTS="53"
DATE=`(date +%Y%m%d)`
sudo -v
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
if ! [ -x $NMAP ]; then
echo "nmap not installed. Giving up"
exit -1
fi
if [ $EUID -ne 0 ]; then
echo "sorry, you must be root"
echo "sudo $0 $*"
exit -2
fi
if [ -z $1 ]; then
echo "Missing network to scan"
exit -3
fi
echo "Discovering host on $1"
echo $NMAP --privileged -n $ICMP_SCAN -PS$SYN_SCAN_PORTS -PU$UDP_SCAN_PORTS -sT -O --osscan-guess --max-os-tries 1 -p T:$SYN_SCAN_PORTS --max-retries 2 --min-rtt-timeout 100ms --max-rtt-timeout 3000ms --initial-rtt-timeout 500ms --min-rate 400 --max-rate 450 --max-parallelism 10 -v $1 -oX scout_$DATE.xml
sudo $NMAP --privileged -n $ICMP_SCAN -PS$SYN_SCAN_PORTS -PU$UDP_SCAN_PORTS -sT -O --osscan-guess --max-os-tries 1 -p T:$SYN_SCAN_PORTS --max-retries 2 --min-rtt-timeout 100ms --max-rtt-timeout 3000ms --initial-rtt-timeout 500ms --min-rate 400 --max-rate 450 --max-parallelism 10 -v $1 -oX scout_$DATE.xml
Funziona. Niente da dire. Il problema è che io voglio produrre un DB SQL, SQLite va benissimo, con anche l’informazione sulla presenza dell’host nel mio sistema di vulnerability management, Nexpose nella fattispecie.
Soluzione A. Unisco più strumenti
Poche settimane fa, ho scritto un post dove mostravo come integrare la command line e Nexpose con poche righe di ruby. Nmap mi fornisce un output anche in un comodo formato XML, quindi quello che dovrei fare è: estendere il mio script che ho mostrato qualche settimana fa in maniera tale che consumi il file XML di nmap come input.
A questo punto dovrei solamente aggiungere un pezzo di codice che mi salva i risultati su DB, ma per questo ho un ORM flessibile come datamapper.
Perché non mi convince questa soluzione? Perché nmap ha bisogno di essere eseguito come root per il tipo di parametri passati, e io vorrei evitarmelo.
Soluzione B. Faccio un nuovo strumento
Provo quindi a passare alla soluzione all in one. Monto sullo scheletro dello script che parla con Nexpose, un semplice codice che rileva se la porta è aperta oppure no.
class Host
def self.has_port_open?(host, port, timeout, sleep_period)
begin
Timeout::timeout(timeout) do
begin
s = TCPSocket.new(host, port)
s.close
return true
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
sleep(sleep_period)
retry
end
end
rescue Timeout::Error
return false
end
end
def self.is_alive?(host="127.0.0.1", ports=[21, 22, 25, 80, 139, 443, 445, 3389, 8080])
ports.each do |p|
return true if Scout::Host.tcp_sonar(host, 1, p)
end
# If TCP sonar failed try a single UDP port
return true if Scout::Host.udp_sonar(host, 1, 53)
return false
end
private
def self.udp_sonar(host="127.0.0.1", timeout=5, port=53)
begin
Timeout::timeout(timeout) do
s = UDPSocket.new(host, port)
s.close
end
rescue Errno::ECONNREFUSED
return true
rescue => e
return false
end
return true
end
def self.tcp_sonar(host="127.0.0.1", timeout=5, port=7)
begin
Timeout::timeout(timeout) do
s = TCPSocket.new(host, port)
s.close
end
rescue Errno::ECONNREFUSED
return true
rescue => e
return false
end
return true
end
end
Uso poi una routine che monta i pezzi insieme e crea un file CSV. Veramente, l’import poi su DB è davvero l’ultimo dei miei pensieri.
def self.do_the_job(n)
sn = Scout::Nexty.new
unless sn.live?
$logger.error "nexpose is not alive. Giving up"
return false
end
n.each do |h|
begin
name = Resolv.getname(h.to_s)
rescue Resolv::ResolvError, SocketError => e
$logger.warn e.to_s if WARN_IF_REVERSE_LOOKUP_FAILS
name = h.to_s
end
alive = Scout::Host.is_alive?(h.to_s)
STDERR.printf("%s,%s,%s,%s\n", h,name,alive, sn.is_scanned?(h.to_s)) unless PRINT_ONLY_ALIVE
STDERR.printf("%s,%s,%s,%s\n", h,name,alive, sn.is_scanned?(h.to_s)) if alive && PRINT_ONLY_ALIVE
end
sn.logout
end
Funziona, l’output è quello di cui ho bisogno. C’è un unico problema. E’ drammaticamente lento. Su una /16 non basta una settimana di lavoro per scoprire tutti gli host.
“Beata innocenza, non usare la 3-way handshake, manda solo syn”, direte voi. Già, ma non sempre gli apparati di rete digeriscono bene un sacco di connessioni mezze aperte. Lo scopo è quello di essere il più silente possibile, quindi meglio chiudere la connessione in maniera educata.
“Sei un n00b, usa i thread”. Ecco, sempre con moderata educazione verso gli altri parties della rete, questa è la strada da seguire. In qualche modo devo capire come scomporre il mio input, una CIDR notation, in pezzi da far consumare ai figlioletti del processo padre… vedremo gli sviluppi.
Off by one
Solo enjoy e ricordatevi che per fare application security, bisogna sporcarsi le mani col codice.
Vuoi aiutarmi a portare avanti il progetto Codice Insicuro con una donazione? Fantastico, allora non ti basta che premere il pulsante qui sotto.
Supporta il progetto