$ export IP=10.129.33.156$ rustscan --ulimit 10000 -a $IP -- -sCTV -PnOpen 10.129.33.156:22Open 10.129.33.156:80Nmap scan report for browsed.htb (10.129.33.156)PORT STATE SERVICE REASON VERSION22/tcp open ssh syn-ack OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)| ssh-hostkey:| 256 02:c8:a4:ba:c5:ed:0b:13:ef:b7:e7:d7:ef:a2:9d:92 (ECDSA)| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJW1WZr+zu8O38glENl+84Zw9+Dw/pm4IxFauRRJ+eAFkuODRBg+5J92dT0p/BZLMz1wZMjd6BLjAkB1LHDAjqQ=| 256 53:ea:be:c7:07:05:9d:aa:9f:44:f8:bf:32:ed:5c:9a (ED25519)|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICE6UoMGXZk41AvU+J2++RYnxElAD3KNSjatTdCeEa1R80/tcp open http syn-ack nginx 1.24.0 (Ubuntu)|_http-title: Browsed| http-methods:|_ Supported Methods: GET HEAD|_http-server-header: nginx/1.24.0 (Ubuntu)Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Update /etc/hosts
$ echo "$IP browsed.htb" | sudo tee -a /etc/hosts
Visiting website we see obvious attack vector
Files must be directly inside the archive, not in a folder.
Uploading a sample zip reveals a new endpoint within the logs
Add to hosts and visit to reveal gitea database
$ echo "$IP browsedinternals.htb" | sudo tee -a /etc/hosts
We can see some source code and analyze for potential vulnerabilities
app.py
User controlled variable rid passed to bash script
Runs internally on port 5000
RCE vulnerability via bash cmdi
@app.route('/routines/<rid>')def routines(rid): subprocess.run(["./routines.sh", rid]) return "Routine executed !"*snip*# The webapp should only be accessible through localhostif __name__ == '__main__': app.run(host='127.0.0.1', port=5000)
larry@browsed:~/markdownPreview$ sudo -lMatching Defaults entries for larry on browsed: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_ptyUser larry may run the following commands on browsed: (root) NOPASSWD: /opt/extensiontool/extension_tool.py
sudo rights on /opt/extensiontool/extension_tool.py
larry@browsed:~/markdownPreview$ sudo /opt/extensiontool/extension_tool.py[X] Use one of the following extensions : ['Fontify', 'Timer', 'ReplaceImages']
Expects some arguments so let’s investigate further
extension_tool.py
#!/usr/bin/python3.12import jsonimport osfrom argparse import ArgumentParserfrom extension_utils import validate_manifest, clean_temp_filesimport zipfileEXTENSION_DIR = '/opt/extensiontool/extensions/'*snip*def main(): parser = ArgumentParser(description="Validate, bump version, and package a browser extension.") parser.add_argument('--ext', type=str, default='.', help='Which extension to load') parser.add_argument('--bump', choices=['major', 'minor', 'patch'], help='Version bump type') parser.add_argument('--zip', type=str, nargs='?', const='extension.zip', help='Output zip file name') parser.add_argument('--clean', action='store_true', help="Clean up temporary files after packaging")*snip*
Will need to pass --ext Fontify/Timer/ReplaceImages
Checking out /opt/extensiontool/extensions/ we find interesting permissions
larry@browsed:~/markdownPreview$ ls -la /opt/extensiontool/total 24drwxr-xr-x 4 root root 4096 Dec 11 07:54 .drwxr-xr-x 4 root root 4096 Aug 17 12:55 ..drwxrwxr-x 5 root root 4096 Mar 23 2025 extensions-rwxrwxr-x 1 root root 2739 Mar 27 2025 extension_tool.py-rw-rw-r-- 1 root root 1245 Mar 23 2025 extension_utils.pydrwxrwxrwx 2 root root 4096 Dec 11 07:57 __pycache__
/opt/extensiontool/__pycache__/ has world-writable permissions (777)
extension_tool.py runs as root via sudo and imports extension_utils
Python loads .pyc files from __pycache__/ before compiling .py files