jueves, 10 de marzo de 2011

Mi España. Un vistazo rápido a sus servidores web.

Mi España. Un vistazo rápido a sus servidores web.: "
Una de las charlas que más ganas tenía de ver en la Rooted2011 era la de @NigtherMan, titulada "Global Warfare". En ella contaron como han desarrollado una herramienta similar a Shodan, llamada hookle, pero con algunas nuevas funcionalidades adicionales muy interesantes.


En noviembre del año pasado decidí hacer algo similar, pero acotando el resultado únicamente a España. Tenía interés en ver que escondíamos, que se encontraba detrás de cientos de ADSLs e IPs oscuras y raritas, y como siempre, sacar algunos números estadísticos y ridículos para divertirme.


Después de obtener los primeros resultados presente la propuesta, junto al análisis de contraseñas, al Call For Papers de la Rooted, a ver cuál les gustaba más. Se decidieron por la segunda, a la que también le había dedicado tiempo, así que yo y mis análisis se pararon y me dedique a otra cosa.


En esta entrada voy a intentar contar como lo hice, que problemas me encontré y hasta donde llegué. Aunque ahora pienso retomar el tema just 4 fun!


1.- Identificación de Direcciones IP de España.


La primera fase y más sencilla era acotar el análisis a todas las direcciones IP españolas así que debía consultar en RIPE que rangos pertenecían a este país y descartar todo lo demás. Ojo, que no es lo mismo una dirección IP española, que un dominio español. Yo puedo tener registrado alexissexy.es en un hosting en Francia, por ejemplo. Aunque su contenido sea mío y el dominio sea español, quedará excluido.


Por otra parte, por ser un primer análisis decidí únicamente centrarme en IPv4, en otra ocasión ya hablaremos de IPv6.


Para identificar el direccionamiento IP del país de Cervantes, lo más rápido que se me ocurrió fue descargar la base de datos de RIPE y aplicarle un grep, con cuidado, que el espacio entre 'country:' y 'ES' pueden ser espacios, tabulaciones o lo que sea y hay veces que pone 'ES', 'es', 'Es'...


[aramosf@dmz ~]$ wget ftp://ftp.ripe.net/ripe/dbase/ripe.db.gz
[aramosf@dmz ~]$ zegrep -B6 -i "^country:.*ES" ripe.db.gz|grep inetnum \
|awk -F: '{print $2}'|sed -e 's/\s//g' >p0.txt


Para que me fuera más sencillo trabajar con las listas de redes y la herramienta nmap pudiese leerlas, convertí los rangos en notación CIDR:


[aramosf@dmz ~]$ cat p0.txt |perl -ne 'use Net::CIDR::Lite;
my $cidr = Net::CIDR::Lite->new; $cidr->add_range("$_");
@cidr_list = $cidr->list; foreach (@cidr_list) { print "$_\n" };'
>p1.txt


Una vez tenía los resultados en el archivo p1.txt, conté el número de direcciones IP totales:



[aramosf@dmz ~]$ nmap -n -iL p1.txt -sL -oG p2.txt
[aramosf@dmz ~]$ grep 'Host:' p2.txt.gnmap |wc -l


Del que se obtenía el resultado de 45.256.747 direcciones IPs.
También pensé que lo mismo me serviría luego o podría observar algo si resolvía las 45 millones de direcciones IP, así que sin tener demasiado claro por qué, hice las consultas y las guardé en otro fichero:


[aramosf@dmz ~]$ nmap -T5 -iL p1.txt -sL -oG p2-dns


Esto tardó 4 días (354855.44 segundos), aunque se podría optimizar con otras opciones y modificando las DNS para que use algún servidor más rápido, como por ejemplo el de Google.


Luego empecé el análisis a los rangos del puerto 80, pero tuve que repetir varias veces por distintos problemas:
  • Guardando resultados, el nmap que utilicé no estaba compilado para soportar ficheros mayores a 2Gbs por lo que los XML dejaban de almacenar datos. De esto te das cuenta después de una larga ejecución :-(

  • Desde el servidor, con un enlace de 100Mbits, desde el que lanzaba las pruebas, no conseguía optimizar los resultados, empecé con el más agresivo (-T5), pero obtenía demasiados falsos negativos. Así que lo configuré de tal forma que estaría entre el -T4 y el -T5.

  • Traté de utilizar el --resume para continuar un escaneo abortado pero no continuaba como se esperaba. Así que decidí dividir el archivo con los rangos en 4 ficheros e ir ejecutando poco a poco.

  • Quite la opción de comprobar si el sistema estaba levantado, ya que eso supone lanzar hasta 8 paquetes y esperar como responden, mientras que mirar si está directamente un único puerto abierto, es lanzar un syn y esperar la respuesta.

Así que dividí el fichero en 8 trozos (y uno más con el resto de la visión) y comencé el escaneo de puertos:


[aramosf@dmz ~]$ wc -l p0.txt
32443 p0.txt
[aramosf@dmz ~]$ echo $(( 32443 / 8 ))
4055
[aramosf@dmz ~]$ split -l 4055 p0.txt


Para finalmente buscar el puerto 80 abierto:



for i in xa?; do 
nmap -n -Pn -g53 --min-hostgroup 1024 --max-rtt-timeout 700ms \
--initial-rtt-timeout 500ms --max-retries 6 --max-scan-delay 8 \
--host-timeout 1m -p80 -sS -iL $i.txt --stats-every 30s -oA $i-nmap
done


Después de un tiempo en ejecución, me di cuenta lo poco útil que es tener una lista con direcciones IP con el puerto 80 abierto, así que decidí obtener las cabeceras HTTP añadiendo la opción: --script http-headers.


Ya que tenía que abrir el puerto y hacer una petición, mejor hacer un GET y guardar la página, que tener solo la cabecera. ¿no?. Así que modifiqué uno de los scripts de Nmap para guardarme el / de la raíz del servidor.



description = [[
Shows the index of the default page of a web server.
]]

author = 'A. Ramos'
license = 'Same as Nmap--See http://nmap.org/book/man-legal.html'
categories = {'default', 'discovery', 'safe'}
local url = require 'url'
local dns = require 'dns'
local http = require 'http'
local ipOps = require 'ipOps'
local stdnse = require 'stdnse'
portrule = function(host, port)
local svc = { std = { ['http'] = 1, ['http-alt'] = 1 },
ssl = { ['https'] = 1, ['https-alt'] = 1 } }
if port.protocol ~= 'tcp'
or not ( svc.std[port.service] or svc.ssl[port.service] ) then
return false
end
-- Don't bother running on SSL ports if we don't have SSL.
if (svc.ssl[port.service] or port.version.service_tunnel == 'ssl')
and not nmap.have_ssl() then
return false
end
return true
end
action = function(host, port)
local data, result, output
local MAX_SIZE = 5000
local request_options = {}
request_options.header = {}
request_options.header['Range'] = 'Range: bytes=0-5000'
result = http.get( host, port, '/')
output = result.rawheader
table.insert(output, result.body)
return stdnse.format_output(true, output)
end
Una de las cosas raras que me encontré fueron varios servidores de streaming, como por ejemplo radios online, en las que el GET no terminaba nunca, generando error en Nmap. Para no meterle mano a la librería http.lua, hice un apaño-chapuza añadiendo la cabecera 'Range' en la petición y así limitando el tamaño de la respuesta :S


El resultado final quedó:



for i in xa?; do 
nmap -n -Pn -g53 --min-hostgroup 1024 --max-rtt-timeout 700ms \
--initial-rtt-timeout 500ms --max-retries 6 --max-scan-delay 8 \
--host-timeout 1m -p80 --script=html-index.nse -iL $i \
--stats-every 30s -oA $i-nmap
done


La ejecución termino en 11,9 días o para ser más exactos: 1.030.133 segundos.


¿Los resultados? Muchos y muy variados. Por ejemplo la entrada sobre el QoS de IPTV, aprovechando el Real Madrid .vs. Barcelona. Os puedo adelantar que el servidor que más se encontró fue 'micro_httpd', seguido por 'RomPager' y luego 'IIS' y finalmente 'Apache'. Los dos primeros son de routers ADSL, y con los dos siguientes hay truco.... Pero eso lo dejo para la entrada siguiente.


"