PlaidCTF – RoboDate write-up

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

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