我的漏洞扫描器,把被测项目的源码写坏了
配套仓库:github.com/mabupt/open... 感谢各位查看并提出意见,劳烦各位star。
先说结论
我给自己的 Python 漏洞检测流水线加了"动态验证"------在 Docker 沙箱里真实触发被测代码的漏洞, 用双轨证据(Sink 调用记录 + Canary 污点)判定告警真假。跑通那天很爽:靶场上 15 条告警被实证确认。
第二天重跑,confirmed 从 15 掉到 2。
排查了半天,原因不是模型、不是规则、也不是我以为的"镜像变弱了"------ 是我自己的扫描器把被测项目的源码写坏了。
事故经过
被测靶场 pygoat 里有两个 lab,漏洞本身就是"把用户提交的内容写进源文件":
python
# pygoat A9 lab(简化)------漏洞就是把用户输入写入 Python 源文件
def a9_lab(request):
log_code = request.POST.get("log_code")
with open("playground/A9/main.py", "w") as f:
f.write(log_code)
动态验证的设计就是真实利用 这类漏洞:构造载荷 → 发请求 → 观察 Sink 是否被污染。 探针一跑,playground/A9/main.py 和 playground/A6/utility.py 就被我自己的载荷覆盖成了空内容。
接下来的连锁反应很隐蔽:
- 被写坏的文件属于
introduction.apis模块 → 导入失败; - Django 的 URLconf 只剩 7 条路由,其余回调退化成占位对象;
- 所有 Django 路由返回 500 → 探针拿不到任何 Canary 污点证据;
- 结果:Django 系候选 16 条全部
app_unreachable/retry_poc。
最讽刺的地方:这不是"检测失败",而是"检测成功得过了头"------工具确实证明了漏洞可利用, 代价是把被测代码本身当作攻击目标写坏了。
修复:副本挂载
根因很明确:探针是直接 bind mount 宿主工程目录进容器的,写操作直达真实文件。
修复方式是把"被测工程"以副本 形式挂进容器,并且保持相同的容器内路径:
python
copy_dir = self._protected_copy(target) # output/sandbox/protect_<hash>/
mounts.append(Mount("/workspace/<rel>", str(copy_dir), type="bind"))
有两个"顺手就能做错"的细节:
- 副本创建失败时不能回退为挂载原目录 。我一开始的回退逻辑是
except: continue, 结果是"隔离静默失效"------探针照样写宿主。现在改为:副本建不出来就直接放弃这次探针, 宁可少做一次动态验证,也不冒险写坏用户工程。 - 仓库外的目标同样要隔离 。我用
relative_to(workspace_root)判断路径, 目标不在仓库内时抛ValueError,而那个异常被except: continue吞掉了------ 也就是说,用户扫描自己仓库外的项目时,隔离是完全失效的 。现在统一约定: 仓库内挂/workspace/<rel>,仓库外挂/target,副本挂载逻辑三方共享。
验证方式很直接:跑完后看副本目录------A9/main.py、A6/utility.py 确实变成了 0 字节, 宿主工程的文件完好无损。
顺带修复:换环境即踩的三类坑
这次事故让我意识到:"在我机器上能跑"和"在别人机器上能跑"是两件事。于是做了一轮针对性复查:
- 换项目 → 路径约定三方不一致 :探针按宿主绝对路径找目标、沙箱挂
/workspace、 供给流程挂/proj,三处各搞各的。统一为一套路径换算函数。 - 换环境 → 仓库外目标无隔离:见上一条,同一个根因。
- 换解释器 → 附加产物不应该让主流程陪葬 :用
python -m venv建一个 零第三方依赖 的环境跑全流程------发现缺jinja2时,JSON 报告已经写好了, 却因为 HTML 渲染抛异常导致整个流程 exit 1。现在 HTML 降级为"只出 JSON + 告警", 并做了更彻底的验证:42 个模块全部可导入(第三方依赖均为延迟导入), Semgrep / CodeQL / pip-audit 走 PATH 照常工作, Qdrant / fastembed / Docker 缺失时逐级降级,全流程 exit 0。
值得记下来的四条经验
- 动态验证工具必须假定"被测代码会被破坏"。因为触发漏洞的后果就是破坏------ 哪怕破坏对象是它自己。隔离不是可选项。
except: continue是隔离类代码里最危险的写法。它把"隔离失败"变成了"静默不隔离", 比直接报错危险得多。- "降级"要覆盖到最外层。附加产物(HTML 报告)失败不该让主产物(JSON)和整个流程陪葬。
- 换环境测试比加功能更值得投入。上面三个坑,没有一个能在"我自己机器上跑一遍"时暴露。
相关的完整加固记录见仓库 docs/HARDENING.md。 项目地址:github.com/mabupt/open...