先说个事
Node.js生态现在几百万个包,妥妥的软件供应链底座。但漏洞也多得吓人——尤其像命令注入、代码注入、路径遍历、原型污染这种污点式漏洞,传统工具每次碰上JavaScript这种动态语言就头大。动态类型、原型链、复杂依赖……一套组合拳下来,静态分析直接懵,符号执行跑不动,SMT求解器算到超时。
传统思路真的到头了?
传统工具为啥干不动这件事?
老牌工具FAST、NodeMedic-FINE、Explode.js们各有各的绝活,但碰上一堆现实问题,个个都犯难:
- 太依赖动态建模了:JavaScript运行时类型说变就变,原型链搞得飞起,精确建模的难度堪比劝猫别拆家。
- 依赖关系绕不开:npm包动不动带一堆第三方依赖,搞到原生C++模块时,分析人员还得手工建模,累到怀疑人生。
- 类型信息基本随缘:工具想要参数类型、对象结构,但重建这些东西既不完全也不可靠,想完整还原,做梦。
- 基础设施脆弱:解析器、插桩工具跟不上语言演进的步伐,ES2024一出,旧工具当场退役。
- SMT求解器是瓶颈老大难:字符串操作、正则匹配这种约束,Z3都顶不住,动不动就超时。
注意,这不是哪个工具写得不行的实现问题,而是底层分析技术本身就吃力。换个思路,是大势所趋。
换个思路:LLM智能体上
大语言模型这两年火得一塌糊涂,代码理解力也肉眼可见地变强。LLM智能体还能调用工具、根据反馈迭代调整——这就能绕开传统工具踩过的坑,天然占几个优:
- 不用手工建模:LLM预训练时看过海量代码,库和原生函数的用法熟得很,不用给每个包单独搞精确模型。
- 能边试边调:生成PoC、执行、看反馈、改了再跑,这种迭代跟CEGAR异曲同工,但实现起来省事得多。
- npm包基本都能塞进上下文:统计数据在这摆着——90%的npm包代码量不超过15万token,现代LLM上下文窗口轻松装下。碰上大包也简单,只翻相关文件就行。
这几个优势直接催生了新框架——LLMVD.js。
LLMVD.js是怎么干活的?
这套系统把漏洞检测拆成四个阶段,每个阶段派一个独立智能体干细活,走的是ReAct推理+行动模式。支持四类漏洞:OS命令注入(CWE-78)、代码注入(CWE-94)、路径遍历(CWE-22)和原型污染(CWE-1321),输入给个本地路径或package@version就能跑。
第一阶段:Finder(候选枚举)——翻目录、搜模式、读源码,找出可疑地点。输出结构化假设,包含漏洞类型、文件行号、证据代码等。这阶段追求高召回率,先广撒网再筛。
第二阶段:Judge(可利用性过滤)——对着候选漏洞做聚焦代码审计,重点看导出API是否暴露给外部调用,直接给出可利用/不可利用的结论,把明显不靠谱的过滤掉。
第三阶段:Constraints Inferencer(约束推断)——通过过滤的假设进入这层,推断利用条件:入口点、参数格式、载荷结构、要绕过的校验、成功标准,全用结构化自然语言列清楚。
第四阶段:Exploiter(执行耦合的利用合成)——按约束生成PoC,扔进真实Node.js环境跑。失败就保留错误日志,调整再来。跑成功了还有Oracle验证是不是真有效。
Oracle:不靠LLM自说自话
必须有个客观验证标准,不然LLM说"我成功了"能信?所以每类漏洞都定义了硬性成功标:
- 命令注入:PoC执行后必须创建
/tmp/os_cmd_success文件 - 代码注入:PoC必须调用预设的全局标记函数
global.CTF(),STDOUT里得能看到输出 - 路径遍历:得利用
../序列读取预置哨兵文件/tmp/path_traversal,内容还得打印出来 - 原型污染:环境自动探测原型有没有被污染,成功会输出
PROTO_POLLUTION SUCCESS
每次尝试前清理现场,避免串味。
实现方面,底层用LangChain + GPT-5-mini,每个智能体有独立上下文和递归限制,最多54层嵌套,每个包最多试3次,不至于无限循环烧钱。
效果到底咋样?
对比传统工具:降维打击
在公开基准(SecBench.js + VulcaN)的517个漏洞包上,LLMVD.js成功生成有效PoC并确认漏洞的,有433个,83.75%。传统工具里表现最好的Explode.js(文件模式)只确认了223个,43.13%,直接差出一倍多。
数据集 | 总包数 | FAST Expl. | NM-FINE Expl. | Explode.js(File) Expl. | LLMVD.js Val. |
SecBench.js | 373 | 68 | 32 | 172 | 332 |
VulcaN | 144 | 43 | 10 | 51 | 101 |
合计 | 517 | 111 | 42 | 223 | 433 |
私有NodeMedic数据集更夸张,有效确认率94.2%(244/259),而NodeMedic-FINE自己只有49.0%。在爬取的260个新npm包上,传统工具只有2个包成功生成可用PoC,LLMVD.js检测到112个可疑包,最终确认36个未报告漏洞。这36个已经走负责任披露流程报给维护者了,其中3个已获确认。
对比LLM+程序分析混合方案:纯LLM也能赢
在和PoCGen重叠的299个SecBench.js包上,LLMVD.js没有用CVE报告提供先验信息,反而拿到了更高的有效PoC数量——268胜过253。平均API成本也更低:对比0.097。这暗示着一条路:LLM能力上来了,纯智能体方案可能比LLM+CodeQL这类混合方案更划算。
变换测试:真本事还是背答案?
为了防"背题",把基准中143个包做了变量重命名、注释删除、格式变换。结果LLMVD.js依旧成功确认107/108个此前可利用的包,只丢了一个lodash@4.17.15[1]。说明性能基本来自语义推理,不是死记硬背训练集记忆。
成本:不算贵
平均API成本每包**,成功利用的包平均0.084。按类型看,原型污染最贵(),路径遍历最便宜(0.051)。成本随包体积增加而上升,但成不成功,成本分布没明显差距——钱在烧,出不一定出活。
失败模式:LLM也有倔脾气
人工审了半天无效PoC,总结出几类典型翻车现场:
- 原型污染方面最大的问题——智能体直接拿
Object或Object.prototype当参数丢给API,占82.9%,根本不现实。 - 命令注入方面——通过重定义内置函数或桩代码模拟缺失环境糊弄了事,占78.6%。
- 代码注入——环境模拟和用内部路线混在一起。
- 路径遍历——不碰公开API,专挑内部函数、测试代码、示例脚本下手。
这些表现说明LLM智能体容易做出太宽泛的假设,加规则检查或者护栏应该能治一治。
还有8个传统工具能搞定、LLMVD.js反而失败的案例,原因也挺有意思:要么智能体误以为漏洞已经被修复,觉得有sanitizer拦着;要么是加载了一大堆无关代码,把上下文搞混了。
这事还有啥可聊的
先别急着把传统工具扔了
LLM智能体也不是万能。精确路径约束、深层语义理解这类场景,传统程序分析还能补位。把这俩结合起来的思路挺靠谱——把传统工具产出的数据流片段当高价值证据,直接注入提示里,给LLM智能体加buff,推理能力还能再上一层楼。
扩展性:说扩就能扩
现在只管四种污点漏洞,以后往SSRF、不安全反序列化这些方向扩,只需要调整漏洞描述和Oracle定义就行,成本不高。毕竟任务定义用的是自然语言,天然带扩展属性。
安全问题必须提一嘴
这玩意能自动生成可用PoC,双重用途风险真实存在。所有实验都在隔离沙箱里跑,不针对任何生产系统,新漏洞走负责任披露流程。防御价值大于潜在风险——这判断敢下。
总的来看
LLMVD.js这套以LLM为中心的智能体框架,把Node.js包漏洞检测从"先建引擎再检测"的老路子,掰到了"脑子够用就直接上"的新方向。公开基准84%的确认率,260个新包里挖出36个新漏洞,还比混合方案便宜、高效。
LLM推理能力还在往上走,这路子往后会越来越宽。传统程序分析也不会凉,当好精准证据的角色,跟LLM互补,各干各擅长的,这才是更可能的未来。