site logo

Marico's space

我做了个能自证漏洞的安全工具,评论区却帮我升级了威胁模型

编程技术 2026-07-29 14:49:24 7

最近在折腾自动化攻击工具,踩了个特别尴尬的坑——它会一本正经地告诉你"成功了",其实毛都没有。

拿个工具扫目标,最 naive 的成功判断就是字符串匹配——看到 uid=0(root) 就当拿下 shell。但 banner 可以直接打印这行字,tarpit 可以在连接时自动推送出来。一旦成功信号本身就是假的,后面全完蛋:报告、"哪些主机沦陷了"的状态、下一步行动,全都被这个错误信号带偏。就是个自信心爆棚的引擎,偏偏自信得毫无道理。

说说我是怎么让工具自己证明自己的——今天实战的真实结果,加上评论区那位一针见血的反馈,直接帮我升级了整个设计。

核心思路:让目标回显一个它根本猜不到的 secret
这是身份认证里最老的套路了。每次攻击前,编排器生成一个不可预测的每次_attempt_nonce,然后注入进去。payload 里必须把这个 nonce 带回:

import os def make_nonce() -> str: return os.urandom(12).hex() # 不可预测——目标猜不出来

只有精确返回这个 nonce,才认为结果可信,格式是一条结构化的证据行:

HALO-EVIDENCE nonce=c609007176813c9110fccc27 level=shell uid=0 host= exit=0
_EVIDENCE = re.compile(r"HALO-EVIDENCE nonce=(\S+) level=(\S+)") def breach_confirmed(output, ok, *, nonce) -> bool: m = _EVIDENCE.search(output or "") return bool(ok and m and m.group(1) == nonce)

交付是个阶梯,因为真实主机各有各的状况
能证明漏洞没用,前提是 payload 能送进去。所以交付方式要优雅降级,全用 stdlib socket:

反向 shell——目标主动连接回来,宣布 nonce,然后交出 /bin/sh。
绑定 shell——如果出站被封,目标绑定一个 shell,你连进去。
盲回调——如果压根没有存活的交互通道,目标只连回来发个 nonce。这仍然能证明代码执行了,虽然没有可用的 shell。
每一级都自选择第一个可用的解释器(bash /dev/tcp、python3、perl、nc),所以同一套 primitive 能打各种主机,不用为某个特定环境硬编码。

实战跑一把
指向一台特意留了漏洞的实验主机(192.0.2.3,文档地址段的占位符),agent 扫出 23 个开放端口,挨个试。三条精心准备的 exploit 走两阶段 gate——先隔离自检(不联网),再真打——每次都弹出真正的 root shell,回显自己那一轮的 unique nonce:

BREACHED ports: ['21', '1524', '6667'] 21 vsftpd 2.3.4 HALO-EVIDENCE nonce=c609… uid=0(root) 1524 ingreslock HALO-EVIDENCE nonce=4a8c… root@…:/# 6667 UnrealIRCd 3.2.8.1 HALO-EVIDENCE nonce=6ced… uid=0(root)

三个成功,二十个老老实实失败。没有虚假的"23/23 全中"。最后这点才是关键——诚实的"我拿下三个"永远比自信的"我全拿下了"更有价值。

发出来之后,一小时之内互联网帮我改进了设计
写完发出去,不到一小时一位评论者精准地收窄了我的论断——他说得对,我复述一下:

Nonce 在 payload 里明文传输,所以一个反射型的或者故意对抗的服务可以回显 nonce 而不执行命令。Nonce 在非反射威胁模型下证明新鲜性;它不是远程证明(attestation)。

他说得对,这事得摊开讲。Nonce 能给你什么:

干掉意外假阳性——banner、tarpit 盲目推送 uid=0 之类的情况。
提供每次_attempt_的新鲜性——重放旧的 transcript 不会带上这次 run 的 nonce。
给不了什么:attestation。因为 nonce 在 payload 里跑,一个反射输入的服务可以 echo 回来但什么都没执行。在对抗性目标面前,literal echo 不是执行证明。

修复方案,就从那条反馈里来:

把 nonce 绑定到 {attempt_id, target, payload_hash, expected_channel, expiry};用一次就消耗;拒绝重复和跨_attempt_的回调。
严格解析——只接受来自预期通道的一条结构化 frame,hash 原始 transcript 加监听器元数据。
从 echo 走向计算——要求一个 execution 派生的 fact 配合 challenge,不是 literal echo。只有通过运行代码才能产生的值才能对抗反射。
分层证据——"代码执行了"、"uid 验证了"、"交互通道可用"是三个独立的声明,应该分开报告。
测试负例——反射 payload、重放/延迟回调、两个 attempt 竞态、frame 截断、来自错误目标的 nonce、以及声称的 shell 其实权限更低。

几点体会
别信任输出——信任你自己生成的 secret。Challenge-response 把"它说 uid=0"变成"它返回了我的 token"。
但要清楚你的 token 证明了什么。非反射模型下的新鲜性是真实有用的。Attestation 是更难、更独立的问题——在把 challenge 绑定到执行之前,别声称它。
交付要走阶梯,不要靠猜测。反向 → 绑定 → 盲回调,覆盖乱七八糟的真实情况。
报诚实的数字。三次确凿的胜利胜过二十三次夸大的宣称。
证明胜过乐观——而公开、具体的批评比两者都强。先把 gate 建好,然后让更聪明的人帮你收窄威胁模型。