lunes, 3 de septiembre de 2012

Stripe Capture the Flag: Web Edition - Solutions (Part I)

Hace unos días dio por finalizado el Capture The Flag “CTF” organizado por Stripe, el cual era íntegramente dedicado a seguridad web, más o menos ;). El CTF constaba de nueve retos correlativos, por lo que era necesario pasarse el nivel 0, para poder iniciar el nivel 1 y así sucesivamente. El CTF comenzó el Miércoles día 22 de Agosto del 2012 a las 12 del medio día (PDT) y finalizó el Miércoles día 29 de Agosto del 2012 a las 12 del medio día (PDT).

Después de esta grata experiencia y ya que los retos me han parecido muy interesantes, he decidido hacer un “pequeño” solucionario de las diferentes pruebas que hubieron y el modo o las técnicas que he utilizado para solucionarlos. (También explicaré algunos de los errores que cometí o como perder el tiempo en un CTF :))

Cada uno de los retos disponían de una descripción de lo se debía conseguir o de la arquitectura que podías encontrar en ellos. A esto le acompañaba el código fuente (el cual os pondré por si queréis realizar los retos en local) de la aplicación o aplicaciones que nos íbamos a encontrar.

Debido a que explico todas las pruebas en una sola entrada quedaría con demasiada envergadura, he decidido trocear el solucionario en tres partes. De todos modos al finalizar la serie dejaré el enlace al documento en PDF con todas las entregas juntas.

******************************************************************************
- 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)
******************************************************************************

Esto es todo, así que empezamos.

image

Descripción: We'll start you out with Level 0, the Secret Safe. The Secret Safe is designed as a secure place to store all of your secrets. It turns out that the password to access Level 1 is stored within the Secret Safe. If only you knew how to crack safes...

Código fuente: [Descarga_codigo_fuente_nivel_0]

Solución: Tal y como nos decían en la descripción disponíamos de un sistema de almacenamientos de secretos, los cuales podíamos recordar si introducíamos el “namespace” al que se encontraba asociado dicho secreto.

Introducimos un secreto con el nombrado “namespace”:

image

Buscamos de nuevo el secreto por el campo “namespace”:

image

A primera vista y según los datos que nos han dado, parece que nos hemos tomado con un SQL Injection básico, pero para asegurarnos vamos a mirar el código fuente que para eso lo tenemos.

image

Pero al visualizar el código, nos encontramos con una sorpresa mucho mejor, en la sentencia SQL se está haciendo uso de la función “LIKE” para igualar el campo “KEY” con nuestro namespace. Por lo que, solo necesitamos introducir el carácter mágico que hace que la función LIKE nos devuelva todos los datos, el porcentaje “%”.

De este modo, si ingresamos un porcentaje en el parámetro namespace, obtendremos todos los secretos que se encuentran almacenados en la base de datos.

image

Ya tenemos nuestra primera password “stgldUfyzE” ;)

 

image

Descripción: Excellent, you are now on Level 1, the Guessing Game. All you have to do is guess the combination correctly, and you'll be given the password to access Level 2! We've been assured that this level has no security vulnerabilities in it (and the machine running the Guessing Game has no outbound network connectivity, meaning you wouldn't be able to extract the password anyway), so you'll probably just have to try all the possible combinations. Or will you...?

Código fuente: [Descarga_codigo_fuente_nivel_1]

Solución: En el siguiente nivel nos encontramos con un formulario dónde únicamente podemos enviar un parámetro mediante el nombre “attemmpt”.

image

Para aclararnos un poco más vamos a ver que nos encontramos en el código fuente.

image

Si observamos el código podemos observar algo bastante raro:

  1. Se declara una variable $filename, con el nombre de un fichero (el cual contiene una combinación secreta).
  2. Extraemos la combinación secreta y se almacena en la variable $combination.
  3. Comparamos el valor introducido por nosotros con la variable $combination.
  4. Si el resultado es correcto, nos devuelve la clave del nivel 1, de lo contrario, nos muestra un mensaje de error.

¿Y como sacamos la clave…? He aquí lo comentado en la descripción de que la única solución es hacer fuerza bruta, pero no sabemos la longitud, caracteres, números, símbolos que puede contener la combinación secreta, por lo que la opción de fuerza bruta no es viable.

La clave de este reto, la podíamos localizar en la línea 13, donde podemos observar el siguiente código “extract($_GET)”. Se está haciendo un mal uso de la función extract() (convierte una array en variables asociados a su valor), ya que no se está asignando ningún prefijo para que no se puedan sobrescribir variables anteriormente creadas.

Por lo que, si nosotros enviamos los parámetros “attempt=Daniel&filename=romero”, obtendríamos como resultado de las variables $attemp “Daniel” y de $filename “romero”.

Ya lo tenemos solo tenemos que sobrescribir la variable $filename con el valor que nos interese, y obtendremos la password.

Debido que la variable $combination es el contenido del fichero introducido mediante la variable $filename, solo necesitamos introducir un fichero inexistente, así la variable $combination no dispondrá de ningún valor, y a su vez, enviar el parámetro $attemp vacío, para que tampoco disponga de ningún valor y poder entrar en el IF que las compara.

image

De este modo conseguimos la password que nos dará acceso al siguiente nivel “vCuNOxWWfI”.

 

image

Descripción: You are now on Level 2, the Social Network. Excellent work so far! Social Networks are all the rage these days, so we decided to build one for CTF. Please fill out your profile at https://level02-2.stripe-ctf.com/user-qnriiemgks. You may even be able to find the password for Level 3 by doing so.

Código fuente: [Descarga_codigo_fuente_nivel_2]

Solución: Hay que decir que el Nivel 2 me sorprendió bastante ya que no encontré ninguna dificultad en él. Aunque posiblemente no fuera la manera correcta de pasarse el reto, era posible conseguir la password sin mucho ingenio.

Si nos registrábamos en la red social, nos enviaba a una pantalla dónde nos permitían subir nuestra imagen de perfil………mmmmmmm……¿Webshell?

image

Antes de nada comentar que disponíamos de un link que nos enviaba directamente a la password para pasar al Nivel 3, pero este nos devolvía un error “403 - Forbidden”.

Preparamos nuestra sencilla WebShell en PHP:

image

Subimos nuestra WebShell y nos devuelven el mensaje de que se ha subido satisfactoriamente nuestra imagen:

image

Accedemos a ella y ejecutamos el comando “id” para comprobar que todo funciona correctamente:

image

Listamos el contenido de un directorio anterior al nuestro:

image

Aquí tenemos nuestro password.txt, solo nos queda imprimirlo por pantalla mediante el comando “cat”:

image

Y ya tenemos nuestra password que nos da paso al Nivel 3 “oGLuCztZDQ”.

 

image

Descripción: After the fiasco back in Level 0, management has decided to fortify the Secret Safe into an unbreakable solution (kind of like Unbreakable Linux). The resulting product is Secret Vault, which is so secure that it requires human intervention to add new secrets.

A beta version has launched with some interesting secrets (including the password to access Level 4); you can check it out at https://level03-2.stripe-ctf.com/user-vbpkdfkfyc.

Código fuente: [Descarga_codigo_fuente_nivel_3]

Solución: Si entrabamos en el enlace que nos indicaban, nos encontrábamos con un formulario de login donde se podía leer que “el usuario bob almacena la contraseña de acceso del Nivel3”.

image

Antes de ponernos a inyectar código por todos los lados posibles, vamos a ver que nos dice el código fuente:

image

De nuevo parece que hemos topado con un SQL Injection, ya que en la query se está concatenando directamente el usuario sin ningún tipo de filtrado.

Prueba errónea:

Si os fijáis en como está construido el código, los resultados (sin saber las credenciales) que siempre vamos a obtener son:

  1. No existe el usuario
  2. Password incorrecto

Por lo que ya lo teníamos, se trataba de un Blind SQL Injection en toda regla, y de este modo podremos obtener el hash SHA256 y el SALT que posteriormente crackearemos con algún diccionario o fuerza bruta.

Bien, después de obtener ambos campos (SHA256 y SALT) y pasarle absolutamente todos los diccionarios que tenia a mano, me di cuenta que la cosa no podía ir por aquí.

Prueba correcta:

Después de volver a mirar el código me di cuenta que era posible bypassear el login de una forma relativamente fácil aprovechándonos de la vulnerabilidad SQL Injection.

Si nos fijamos en las últimas líneas del código del login, podemos observar lo siguiente:

image

  1. Comprueba si la query es correcta, y en caso afirmativo, extrae los valores campos “user_id”, “password_hash” y “salt” (correspondientes al usuario introducido en la query)
  2. Concatena al password introducido por nosotros en el formulario con el salt extraído de la consulta a la base de datos, y realiza la función de hash SHA256, guardando el resultado en la variable “calculated_hash”
  3. Compara la variable “calculated_hash” con la password extraída de la consulta a la base de datos.

Si os fijáis, en la comprobación del hash, controlamos todos los parámetros utilizados, el password (introducido por nosotros mediante el formulario), el salt (lo podemos modificar mediante el SQL Injection) y el password_hash (lo podemos modificar mediante el SQL Injection).

Por lo que, hemos de construir una inyección SQL que (password + salt = password_hash).

Iniciamos la inyección SQL y comprobamos que realmente funciona mediante una consulta de verdadero y falso:

image

Realizamos una inyección completa:

image

Modificamos el usuario con uno incorrecto para que solo exista una fila en la respuesta de la query, y asignamos los siguientes valores

  1. Parámetro password = “da”
  2. Campo password_hash = “bd3dae5fb91f88a4f0978222dfd58f59a124257cb081486387cbae9df11fb879“ (resultado de SHA256(“daniel”))
  3. Campo salt = “niel”
  4. Campo id = “3” (usuario bob)

image

Con esto conseguimos que se cumpla en código la operación (password + salt = password_hash), iniciar sesión con el usuario bob y obtener nuestro password “OikQogoPtL” ;)

image

 

image

Descripción: The Karma Trader is the world's best way to reward people for good deeds: https://level04-4.stripe-ctf.com/user-xhgvrtpmbb. You can sign up for an account, and start transferring karma to people who you think are doing good in the world. In order to ensure you're transferring karma only to good people, transferring karma to a user will also reveal your password to him or her.

The very active user karma_fountain has infinite karma, making it a ripe account to obtain (no one will notice a few extra karma trades here and there). The password for karma_fountain's account will give you access to Level 5.

Código fuente: [Descarga_codigo_fuente_nivel_4]

Solución: Si visualizamos el enlace que nos indican podemos encontrar un típico formulario de login, donde si no tienes contraseña te permiten registrarte para poder acceder al servicio.

image

Una vez iniciados sesión, nos encontrábamos con un formulario dónde podíamos enviar karma a los usuarios que existieran en el sistema. Únicamente disponíamos de 500 puntos de karma y no nos podíamos enviarnos karma a nosotros mismos.

image

Una de las cosas que me inquietaron desde un principio fue que el usuario karma_fountain, se conectaba cada pocos minutos :S, extraño, la verdad..

  1. karma_fountain (password: [hasn't yet transferred karma to you], last active 17:23:14 UTC)
  2. karma_fountain (password: [hasn't yet transferred karma to you], last active 17:26:29 UTC)

Después mirar como estaba desarrollado el código por si se podía detectar alguna vulnerabilidad de forma rápida, analizar el código fuente del portal en producción por si había alguna anomalía, inyectar código en los campos que karma y usuario, mirar, decodificar e intentar falsificar las cookies para hacerme pasar por el usuario karma_fountain, sin éxito alguno, me plantee la pregunta clave de éste nivel.

¿Cómo puedo hacer que alguien (karma_fountain) haga una acción en el portal sin poder evitarlo o darse cuenta?

En el mundo de la seguridad web, la respuesta a esta pregunta es bien conocida por la gente, la vulnerabilidad por excelencia que te permite hacer esta acción es el Cross-Site Scripting “XSS”.

Así que a inyectar código HTML y JavaScript en todos los campos posibles (nombre de usuario, karma, destinatario..), sin éxito. Si nos paramos a pensar un poco realmente, ¿qué campos en los que nosotros podamos inyectar los visualiza el usuario karma_fountain, para así verse afectado por el XSS?

Si recordamos lo que nos decía el enunciado: “Con el fin de asegurarse de que está transfiriendo karma sólo a la gente buena, al realizar una transferencia de karma a un usuario se revela su contraseña a él o ella.”

Por lo tanto si nosotros realizamos una transferencia un usuario el podrá ver nuestra contraseña, ¿XSS en la password?. Nos creamos un usuario nuevo llamado “user2” y con la password “<h1>XSS</h1>”, y le enviamos karma al usuario “dromero”, si iniciamos sesión de con éste obtendremos lo siguiente.

image

Lo tenemos!! Hemos conseguido un XSS en el campo password.

Ahora solo nos queda crearnos un código que cuando sea ejecutado envíe karma al usuario que nosotros queramos, para posteriormente crearnos un usuario con ese código y enviar karma al usuario karma_fountain, de este modo cuando este entre, se verá afectado por la vulnerabilidad y podremos visualizar su password.

Para hacer la inyección recordé que al analizar el código fuente de la aplicación en producción me resulto sospechoso que tuviera incluido el javascript de “jquery 1.8”, por lo que me aprovecharía de la inclusión de este js para hacer una petición por POST y así enviarme karma.

image

El código a inyectar era el siguiente:

<script>function send(){$.post("https://level04-4.stripe-ctf.com/user-xhgvrtpmbb/transfer",{to:"hacker", amount:"1000000"});}send();</script>

Enviamos “1000000” de karma al usuario “hacker” mediante una petición POST. Si esperamos a que el usuario karma_fountain se conectase, obtendríamos nuestra recompensa:

image

Ya teníamos nuestra password para pasar al siguiente nivel, “moCxAfiMbh”.

Como comentario mostrar el código fuente de la aplicación, donde claramente se ve como la variable password no sufre ningún tipo de filtrado XSS a la hora de añadirla en la base de datos:

image

NOTA: Comentando posteriormente como habíamos solucionado el nivel 4 con Francisco Oca, me dijo que él había inyectado el siguiente código.

<script>document.getElementsByName('to')[0].value = "ocaxss2";document.getElementsByName('amount')[0].value = "6";document.getElementsByTagName('form')[0].submit();</script>

Sinceramente me gusta más su solución, ya que únicamente utiliza código JavaScript para modificar los valores “to” y “amount” y posteriormente enviar el formulario.

¡Oca crack! ;)

En breves publicaré la segunda entrada de la serie, Un Saludo!!

4 comentarios:

  1. Muy bien explicado! Espero la continuación.
    Gracias por la mención :)

    ResponderEliminar
  2. Muy interesantes las soluciones. Me perdí el reto por falta de tiempo y me alegro de poder ver los niveles y tus soluciones :)

    ResponderEliminar
    Respuestas
    1. Me alegro que te gusten Adrián!

      Un Saludo!!

      Eliminar