17 de febrero de 2012

Bash Script: domain2nmap.sh

domain2nmap.sh

En esta entrada se muestra el código fuente del script domain2nmap.sh que recibe un nombre de dominio y utiliza el comando wget, el buscador Bing y/o Shodan para conseguir un listado relacionado con el mismo. Posteriormente realiza un escaneo con Nmap de las IPs que resuelven dichos dominios.

Sintaxis: ./domain2nmap.sh [OPTIONS] URL

Entre las opciones que podemos utilizar se incluye la posibilidad de elegir el motor de búsqueda: wget, Bing, Shoda o All. También podemos elegir entre realizar un escaneo de tipo SYN, ACK, UDP o Ping y guardar el reporte en formato XML, grepeable o normal.

En caso de lanzar el script sin opciones, utilizará todos los métodos de búsqueda para realizar un escaneo ping y guardar el resultado en formato XML.

Código fuente:
#!/bin/bash
#
# Name:         domain2nmap.sh
# Description:  This script receive a web domain name and obtain a list 
#               of associated domains and their IPs to make differents
#               scans with nmap.
# Author:       BrixtonC
# Date:         15 Feb 2012
# Version:      0.4
# Example:     ./domain2nmap.sh --help
#              ./domain2nmap.sh www.cnn.com
#              ./domain2nmap.sh -r shodan www.cnn.com
#              ./domain2nmap.sh -s A -o normal www.sun.com
#

### FUNCTIONS

function Usage {
# This function show the sintax of script and some examples. This function
# is call by Arguments function or errors.
#

 echo -e " Sintax:\t$0 [OPTIONS] URL\n"
 echo -e " Options:\t* Search methods (-r)"
 echo -e " \t\t   all:  Search with all methods (default**)"
 echo -e " \t\t   bing: Bing search with IP operator"
 echo -e " \t\t   shodan: Shodan search with hostname operator"
 echo -e " \t\t   wget: Search with wget command"
 echo -e " \t\t* Scan types (-s)"
 echo -e " \t\t   A: ACK scan"
 echo -e " \t\t   P: Ping scan (default**)"
 echo -e " \t\t   S: SYN scan"
 echo -e " \t\t   U: UDP scan"
 echo -e " \t\t* Out types (-o)"
 echo -e " \t\t   grep: Save scan in grepable format"
 echo -e " \t\t   normal: Save scan in normal format"
 echo -e " \t\t   xml: Save scan in xml format (default**)\n"
 echo -e " \t\t** Without options the script it's call with default options\n"
 echo -e " Examples:\t$0 www.cnn.com\n\t\t$0 -s A -o normal www.sun.com"
 echo -e "\t\t$0 -r shodan www.microsoft.com\n\t\t$0 -r wget -s U www.cnn.com\n"
 exit 0

}

function Arguments() {
# This function check the number of arguments, print error if don't pass
# any argument and define the domain name objetive for Resolv function.
#

 if [ "$#" == "0" ]; then
   echo -e "[*] ERROR: Valid domain name is required\n"
   Usage
 else

   if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
     Usage
   else
     local NAME="`echo $@ | awk '{print $NF;}' | sed 's/www\.//'`"
     CheckDomain $NAME
   fi

 fi

}

function CheckDomain() {
# This function run ping test to evaluate if the last argument's of script
# is a valid domain name and declarate the variable $DOMAIN. In wrong case,
# show error and exit.
#

 resolveip $1 > /dev/null

 [ "$?" != "0" ] && echo -e "[*] ERROR: '$1' not is a valid" \
   "domain name" && exit 1 || DOMAIN="`echo $1`"

 echo -e "[+] Domain name: $DOMAIN"

}

function Options() {
# This function receive all arguments to obtain the scan and output options
# to run nmap command with respective options. In default case, without
# options, run the script with ping scan and xml output format.
#

 while getopts "r:s:o:" OPTION; do

   case $OPTION in

       r)
  if [ "$OPTARG" == "bing" ]; then
    RESOLVTYPE="bing" && RESOLVNAME="Bing search"
  elif [ "$OPTARG" == "wget" ]; then
    RESOLVTYPE="wget" && RESOLVNAME="Wget search"
  elif [ "$OPTARG" == "shodan" ]; then
    RESOLVTYPE="shodan" && RESOLVNAME="Shodan search"
  elif [ "$OPTARG" == "all" ]; then
    RESOLVTYPE="all" && RESOLVNAME="All methods"
  fi
  ;;
       s)
  if [ "$OPTARG" == "P" ]; then
    SCANTYPE="-sn" && SCANNAME="Ping scan"
  elif [ "$OPTARG" == "S" ]; then
    SCANTYPE="-sS" && SCANNAME="SYN scan"
  elif [ "$OPTARG" == "A" ]; then
    SCANTYPE="-sA" && SCANNAME="ACK scan"
  elif [ "$OPTARG" == "U" ]; then
    SCANTYPE="-sU" && SCANNAME="UDP scan"
  fi
  ;;

       o)
  if [ "$OPTARG" == "xml" ]; then
    OUTTYPE="-oX" && OUTNAME="XML" && OUTEXT="xml"
  elif [ "$OPTARG" == "normal" ]; then
    OUTTYPE="-oN" && OUTNAME="Normal" && OUTEXT="txt"
  elif [ "$OPTARG" == "grep" ]; then
    OUTTYPE="-oG" && OUTNAME="Grep" && OUTEXT="log"
  fi
  ;;

   esac

 done

 [ -z "$SCANTYPE" ] && SCANTYPE="-sn" && SCANNAME="Ping scan (default)"
 [ -z "$OUTTYPE" ] && OUTTYPE="-oX" && OUTNAME="XML (default)" && \
   OUTEXT="xml"
 [ -z "$RESOLVTYPE" ] && RESOLVTYPE="all" && RESOLVNAME="All methods (default)"
 
 echo -e "[+] Search type: $RESOLVNAME"
 echo -e "[+] Scan type: $SCANNAME"
 echo -e "[+] Output type: $OUTNAME"

}

function Resolv {
# This function decide watch kind of search use: Wget, Bing, Shodan or All.
#

 if [ "$RESOLVTYPE" == "wget" ]; then
   WgetSearch
 elif [ "$RESOLVTYPE" == "bing" ]; then
   BingSearch
 elif [ "$RESOLVTYPE" == "shodan" ]; then
   ShodanSearch
 else
   WgetSearch
   BingSearch
   ShodanSearch
 fi
 
 grep -v "[0-9]f*" tmp | sort | uniq > domains.lst
 rm -rf tmp

}

function WgetSearch {
# This function save index Web page of the domain and obtain a list
# of domains relationated with domain target.
#

 echo -e "[+] Wget search domain: $DOMAIN"

 wget -q $DOMAIN > /dev/null

 grep -oE "http://[a-z0-9\-\.]*\.$DOMAIN" index.html | \
   cut -d "/" -f 3 >> tmp

 rm -rf index.html

}

function BingSearch {
# This function call Bing IP search to obtain a list of domains
# relationated with domain target.
#
# This functions is a modification of bing-ip2host script with
# GPLv3 license:
#
# http://www.morningstarsecurity.com/research/bing-ip2hosts
#

 local IP="`resolveip -s $DOMAIN`"
 local PAGE="0"

 echo -e "[+] Bing search IP: $IP"

 while (( "$PAGE" <= "10")); do

     local URL="http://m.bing.com/search/search.aspx?A=webresults&Q=ip%3a$IP&D=Web&SI=$PAGE"

     wget -q -O $IP-$PAGE.html $URL

     grep -oE "[a-z0-9\-\.]*\.$DOMAIN" $IP-$PAGE.html >> tmp
     rm -rf $IP-$PAGE.html

     PAGE="$(($PAGE + 1))"

 done

}

function ShodanSearch {
# This function run a Shodan hostname search to obtain a list of domains
# relationated with domain target.
#

 local URL="http://www.shodanhq.com/search?q=hostname%3A$DOMAIN"

 echo -e "[+] Shodan search domain: $DOMAIN"

 wget -q -O index.html $URL > /dev/null

 grep -E "class='hostname'" index.html  | \
          grep -oE "http://[0-9a-z\.\-]*\.$DOMAIN" | \
            cut -d "/" -f 3 >> tmp

 rm -rf index.html

}

function ResolvDomain {
# This function receive obtained domains names of domain target
# and translate it to their IP address.
#

 echo -e "[+] Resolving domains"

 for NAMES in $(cat domains.lst); do

   dig $DOMAIN | grep -oE "[0-9]{1,3}(\.[0-9]{1,3}){3}$" >> tmp

 done

 sort tmp | uniq > ipaddress.lst
 rm -rf tmp

}

function ScanIP {
# This function scan each host obtained with Resolv function throuth
# ipaddress.lst file and save the result in a file with IP and date
# as name.

 echo -e "[+] Scaning address"
 local NAME="${DOMAIN}_$(date +'%Y-%m-%d')"

 nmap $SCANTYPE $OUTTYPE $NAME.$OUTEXT -iL ipaddress.lst > /dev/null

 echo -e "[+] Filename: $NAME.$OUTEXT"

}

### SCRIPT

# Run Arguments function with all arguments separated to whitespaces to check
# the number of options and domain value.
#
Arguments $@

# Run Options function with all arguments to obtain the scan and output options
# to run the script
#
Options $@

# Run Resolv function with domain name as argument to obtain a list of
# related domains names.
#
Resolv

# Now check if exist domain.lst and ipaddress.lst file by use it in
# ResolvDomain and ScanIP functions. In wrong case show a error and run
# function Usage.
#
# FILE: domains.lst
[ ! -r domains.lst ] && echo "[*] ERROR: Can't read domains.lst file or" \
  "don't exist" && exit 2 || ResolvDomain
#
# FILE: ipaddress.lst
[ ! -r ipaddress.lst ] && echo -e "[*] ERROR: Can't read ipaddress.lst" \
  "file or don't exist" exit 3 || ScanIP

### NORMAL EXIT

# Show some exit messages.
#
echo -e "[+] Exit of script"

# Exit codes.
# 0 -> Succefull exit and function Usage()
# 1 -> No valid domain
# 2 -> Error file domains.lst
# 3 -> Error file ipaddress.lst
#
exit 0

#EOF
##FVE

En el siguiente enlace puedes ver una explicación del mismo así como las pruebas realizadas. Falla cuando Nmap recibe el archivo ipaddress.lst vacío y habría que retocarlo para controlar los fallos. Los comentarios están escritos en Inglés, o Spanglish mejor dicho. Puede haber errores idiomaticos, sintacticos y todos los que encuentres...

La función SearchBing es una modificación del script bing-ip2host de Andrew Horton que se licencia bajo GPLv3 al igual que los contenidos publicados en el blog. Sientete libre de reutilizar o modificar el mismo siempre tengas en cuenta el licenciamiento ; ).

Un saludo, Brixton Cat.

Bash Script: Automatizando el escaneo de dominios

Como comentaba en uno de los últmos post, o si me siges en Twitter lo puedes comprobar, últimamente ando leyendo todo lo que encuentro sobre programación y sobre todo en Python. En esta ocasión les traigo un pequeño script Bash que realicé como práctica del curso Introduction to Pentest Scripting y que con el permiso de mi querido amigo Roberto Martinez paso a comentar.

Manos a la masa

Introducción

El script básicamente toma un nombre de dominio y lo utiliza para obtener un listado de nombres relacionados con nuestro objetivo para realizar un escaneo con la herramienta Nmap. Sencillo no? En la siguiente imagen teneis un pequeño esquema del funcionamiento.


Como se puede apreciar, tenemos varias opciones a la hora de recopilar los nombres relacionados con nuestro dominio objetivo. El script utiliza el comando wget, Bing con el operador IP y el buscador Shodan utilizando hostname como parámetro de búsqueda. Comentar en este punto que la búsqueda de Bing es una modificación del script bing-ip2host que se licencia bajo GPL versión 3.

Una vez tenemos el listado de nombres de dominio, utilizamos la herramienta dig para resolver la dirección IP de éstos y pasarlos a Nmap para que nos realice el escaneo. El script no contempla todas las opciones que Nmap nos permite, pero sí las más básicas e importantes. Podemos realizar varios tipos de escaneo: SYN (-sS), ACK (-sA), UDP (-sU) y por Ping (-sn) y la salida generada la podremos guardar en formato normal (-oN), grepeable (-oG) o XML (-oX).


Como se puede ver en la ayuda del script, en caso de lanzarse el comando sin opciónes utilizará los valores por defecto para cada una. Utilizará todos los métodos para obtener los nombres de dominios relacionados, realizará un escaneo de pines y guardará la salida en formato XML.


Si lanzamos el script sin argumentos nos saca un error por pantalla indicando que es necesario un nombre de dominio válido y nos muestra la aydua del mismo.


El script generará dos archivos de texto uno para los nombres de dominio obtenidos, domains.lst, y otro con las IPs que los resuelven, ipaddress.lst.


Comprobaciones

Para esta primera prueba voy a explayarme un poco y utilizaremos todos los métodos de búsqueda para comprobar que obtenemos un listado único de nombres de dominio así como de las IPs que los resuelven. El resto de opciones las mantendremos por defecto.

* Opción wget: Básicamente descargamos y recorreremos el index del dominio objetivo en búsca de enlaces que apunten a otros subdominios.
./domain2nmap.sh -r wget www.cnn.com

* Opción bing: En este caso utilizamos el buscador Bing con el operador hostname para obtener un listado de dominios asociados a la IP que buscamos.
./domain2nmap.sh -r bing www.cnn.com

* Opción shodan: Vamos a lanzar una consulta por hostname al buscador Shodan y nuevamente a generar un listado con los dominios obtenidos.
./domain2nmap.sh -r shodan www.cnn.com

* Opción all: Esta es la opción por defecto y utiliza los métodos anteriores para generar la lista de dominios.
./domain2nmap.sh [-r all] www.cnn.com

Como podeis observar en las siguientes capturas, si juntamos los archivos obtenidos anteriormente, ordenamos y quitamos los repetidos. Obtenemos el mismo número de elementos que utilizando la búsqueda por defecto. Parece que no funciona mal al fin y al cabo : )



Pruebas varias

* Dominio: www.abc.com
* Busqueda: Bing
* Escaneo: Syn
* Salida: Grepeable

Vamos a generar un reporte en formato grepeable (filtrable) de un escaneo SYN realizado a las IPs que resolvamos de los dominios relacionados utilizando el buscador Bing.
./domain2nmap.sh -r bing -s S -o grep www.abc.com

* Dominio: www.hoy.es
* Busqueda: All (Default)
* Escaneo: Ping (Default)
* Salida: XML (Default)

En este caso vamos a lanzar el script sin opciones para que utilice los valores por defecto. Va a guardarnos un archivo XML de un escaneo de pines a todas las IPs de los nombres de dominios obtenidos a partir de todos los métodos de búsqueda: wget, Bing y Shodan.
./domain2nmap.sh www.hoy.es

Conclusiones

Éste script es un ejemplo de cómo obtener información acerca de un dominio tanto pasiva como activa dentro de la fase de information gathering, en la que descubrir la máxima información posible sobre el objetivo. Las consultas al buscador Bing se basan en el trabajo de Andrew Horton con su script bing-ip2host que se licencia bajo GPLv3 al igual que los contenidos del blog, salvo sea contrario a lo dicho en el artículo.

El código completo del script lo puedes encontrar en el siguiente enlace (domain2nmap.sh), sientete libre de reutilizarlo o mejorarlo, añadirle más opciones, nuevas funciones, etc.

Algunas [posibles/futuras] mejoras serían:

- Buscar también los nombres que utilicen otro TLD (Top Level Domain).
- Pasar a Bing todas las IPs de resolveip.
- Shodan logeado para obtener todas las funciones?
- Wget: búsquedas relacionadas con el dominio (sin TLD).

En algunas pruebas realizadas no ha generado correctamente los archivos de dominios e IPs y falla al realizar el escaneo a objetivos inexitentes. Habría que depurarlo más a fondo y comprobar debidamente el fichero que utiliza Nmap para realizar las pruebas, ipaddress.lst.

No quiero despedirme sin dedicar el post y el script a mi buen maestro Roberto dado que éste se basa en las prácticas de su curso, solo que ampliado y corregido levemente ; ). Muchas gracias Master!

Un saludo, Brixton Cat.

10 de febrero de 2012

Python script: BssidFromPcap.py paso a paso

En este post quiero explicar paso a paso como crear un script en Python que nos guarde en un archivo de texto las direcciones MAC de los APs disponibles en un archivo de captura PCAP. En este enlace puedes encontrar una pequeña explicación del mismo así como el código fuente del script final.

Versión 0.0

Dado que vamos a utilizar Scapy para manipular los paquetes de red, en primer lugar deberemos importar todos los métodos que ésta libreria nos permite.
from scapy.all import *
Creamos el objeto outfile, con el que creamos y abrimos el archivo de salida para los datos.
outfile = open('./Out.lst', 'w')
Especificamos el archivo de captura, en este caso hardcodeado, y creamos un nuevo objeto (capture) con el método rdpcap para leer el archivo de captura.
capfile = '../out-01.cap' # Change this!
capture = rdpcap(capfile)
Python es un lenguaje altamente secuencial, por lo que un simple for nor permite iterar fácilmente entre los distintos paquetes que contiene nuestro objeto capture.
for pcks in caputre:
   [...]
Hay que tener muy encuenta la identación, tabulaciónes, pues es un factor clave dentro del lenguaje y nos delimita las clases, funciones, estructuras de control, etc. dentro de nuestro código.

En una primera versión, vamos a registrar todas las direcciones físicas de los paquetes capturados en un nuevo objeto llamado bssid.
bssid = pcks.addr2
Realizamos un filtro para direcciones nulas, 00:00:00:00:00:00, a través del objeto blank para pasar a la siguiente iteración.
blank = '00:' * 5 + '00'
if bssid == blank:
    continue
En caso contrario, escribimos la MAC en el archivo de salida, outfile.
outfile.write(bssid + "\n")
Para finalizar, cerramos el archivo de salida.
outfile.close()
El código de esta primera versión sería:
#!/usr/bin/env python

from scapy.all import *

outfile = open('./Out.lst', 'w')
capfile = '../out-01.cap' # Change this!
capture = rdpcap(capfile)
blank = '00:' * 5 + '00'

for pcks in capture:

  bssid = pcks.addr2
  
  if bssid == blank:
    continue
  else:
    outfile.write(bssid + "\n")

outfile.close()
Y una primera prueba del mismo


Versión 0.1

Para mejorar el código anterior, vamos a eliminar los resultados repetidos. Para ello utilizamos un nuevo array, unique, con el que comprobar la existencia del objeto bssid. En caso incorrecto, añadimos dicho valor al array y lo escribimos al archivo de salida.
else:
  unique = []
  if unique.count(bssid) == 0:
    unique.append(bssid)
Si utilizamos el mismo archivo de captura obtenemos el siguiente resultado


Versión 0.2

Una mejora indispensable, es que podamos elegir el archivo de captura a analizar. Para ello deberemos importar el módulo sys que nos permite usar el método argv para acceder a los argumentos del script.
import sys

capfile = sys.argv[1]
Para finalizar, únicamente vamos a analizar los paquetes beacon frame exitentes en los archivos de captura y en caso contrario pasaremos al siguiente paquete.
if pcks.haslayer(Dot11Beacon):
  [...]
else:
  continue
El código final, sin comentar, es el siguiente. Para una versión completa del mismo, visita el siguiente enlace.
import sys
from scapy.all import *

outfile = open('./Out.lst', 'w')
capfile = sys.argv[1]
capture = rdpcap(capfile)
blank = '00:' * 5 + '00'
unique = []

for pcks in capture:

  if pcks.haslayer(Dot11Beacon):
    bssid = pcks.addr2
  
    if bssid == blank:
      continue
    else:
      if unique.count(bssid) == 0:
        unique.append(bssid)
        outfile.write(bssid + "\n")

  else:
    continue

outfile.close()
Realizando algunas pruebas

Utilizando el mismo archivo de captura, obtenemos el siguiente resultado.


Con otro archivo de captura


Si realizamos una captura de varios APs y lanzamos el script al archivo de captura, podemos comprobar que genera una lista con BSSIDs únicos presentes en la red.


Un saludo, Brixton Cat ; )