AI 漏洞挖掘,突破口不只在模型里
最近面试安全工程师,一个很直观的感受是:同样借助 AI,漏洞挖掘的路径已经有了很多变化。
有人从复现 CVE 入手,希望理解根因,再寻找同类缺陷;有人专门解决反射、动态调用和复杂调用链;有人把目光放到多个系统之间,分析一个系统的输出如何成为另一个系统的输入;也有人优先研究触发条件少、容易确认的问题,先把发现和复现做扎实。
这些方向都有价值。但假设大家都能获得接近的模型和工具,还能靠什么持续找到不同的问题?
我更关心的是:除了让 AI 阅读更多代码,能不能让它检查过去没有被充分检查的关系?
代码与运行环境之间,上游保证与下游依赖之间,程序判断与业务要求之间,安全上下文与内部表示之间,以及局部能力与完整操作序列之间,都值得重新审视。
沿着这些关系,可以把“帮我看看有没有漏洞”拆成更具体的研究问题。下面几个公开 CVE 提供了真实的根因,也提供了值得尝试的 AI 挖掘切口。
变化前后的关系:系统演进让旧防护失效
审查一次变更,通常会注意新增了什么危险逻辑。还可以进一步追问:这次变化,让哪些既有防护不再完整?
Spring4Shell(CVE-2022-22965)是一个典型案例。Spring 此前为修复 CVE-2010-1622,限制了数据绑定过程中对 Class 对象上某些属性的访问,阻断通往类加载器的已知路径。后来,JDK 9 引入模块系统,Class 增加了 getModule(),而模块对象又能返回类加载器。原先被阻断的直接路径之外,出现了一条旧防护没有覆盖的间接路径。(microsoft.com)
这里发生的变化,不一定在业务代码里。运行环境增加了一种对象关系,既有的数据绑定能力便可能触达原先不应开放的内部对象。当然,不能由此推导出“所有使用 JDK 9 的 Spring 应用都能被远程执行代码”。当时公开的利用场景还依赖受影响版本、相应数据绑定入口和传统 WAR 部署等条件;典型 Spring Boot 可执行 JAR 不受该条利用路径影响。(spring.io)
另一个方向是修复本身在演进中丢失。OpenSSH 的 regreSSHion(CVE-2024-6387)就是旧漏洞 CVE-2006-5051 的回归:此前修复过的信号处理竞态,在后续修改中重新出现,回归进入了 OpenSSH 8.5p1。(blog.qualys.com)
这两个案例分别提醒我:升级可能扩展原有代码的能力,重构也可能取消原有修复。仅仅知道“这个漏洞修过了”,不足以说明它所代表的安全要求会一直得到满足。
由此可以设计一种更具体的 AI 任务:先从历史修复中提取它试图阻断的能力,以及它依赖的前提,再结合当前变更重新检查。
假设一个字段过去只能由服务端生成,后来增加了用户导入入口;或者一个方法过去只供内部任务调用,后来被新接口暴露。敏感使用点可能没有任何修改,但输入可控性和路径可达性已经改变。
AI 不应只比较新增了哪些代码行,还应形成这样的调查结论:某个安全前提发生了变化,哪些入口因此可能到达哪些敏感操作,原有检查是否仍然有效。
这里不必一开始分析整个系统。可以围绕一次依赖升级、一个历史修复或一个新增入口,限定调查范围,再通过真实调用关系和受控实验确认。
值得寻找的,不只是新的危险代码,还有不再成立的旧前提。
组件之间的关系:下游信任了上游没有保证的条件
调用链可以说明 A 如何调用 B,却未必说明 B 为什么相信这次调用可以被允许。
Next.js 中间件授权绕过漏洞 CVE-2025-29927 暴露了这个问题。Next.js 使用内部请求头 x-middleware-subrequest 协调中间件与路由处理,避免递归执行。这个信号可以让后续处理跳过中间件,而受影响实现没有充分验证信号来源,外部请求也能影响这一控制逻辑。(nextjs.org)
如果应用将关键授权检查放在中间件中,跳过中间件就可能绕过这些检查。其影响与部署方式有关:官方确认部分自托管模式受到影响,而 Vercel 托管部署因路由架构不同,并未受到该漏洞影响。(nextjs.org)
从组件关系看,接收方需要的是“中间件确实已经执行”的可信保证,实际收到的却可能只是外部调用者能够影响的一段声明。
一个字段表达了安全结论,不等于这个字段能够证明安全结论。
类似问题也可以出现在响应方向。Apache HTTP Server 的 CVE-2024-38476 涉及后端应用输出被重新解释:恶意或可被利用的响应头,可能通过内部重定向触发本地处理器,造成信息泄露、SSRF 或本地脚本执行。后端返回的内容,并不总是被动展示的数据,也可能影响接收组件的内部处理。(httpd.apache.org)
这为跨系统分析提供了一个比“追踪所有输入输出”更集中的切口:上游实际提供了什么保证,下游又依赖什么条件?
假设上游只确认用户已经登录,下游却认为资源归属已经检查;生产者只保证消息来自某个服务,消费者却认为原始用户具有执行权限。缺少的那一项检查,究竟由谁负责?
可以让 AI 对“已认证”“已授权”“已检查”“来自内部”等信号做定向调查,追踪它们由谁生成、谁能修改、经过哪些边界,以及在哪个位置被验证。
输出不应只是一张调用图,而应包含每一段调用的安全契约:调用者保证什么,接收者相信什么,有哪些证据支持这种信任。
但没有在某个函数里看到检查,也不能直接认定存在漏洞。检查可能在上层统一执行,信号也可能由部署层可靠地隔离。需要沿真实路径确认缺口,而不是根据函数名和注释补出一条“看起来合理”的攻击链。
判断与含义的关系:检查通过,却没有证明该证明的事情
有些安全检查并没有被绕过。它被正常执行,返回成功,但这个成功没有对应真实的安全保证。
Java 的 “Psychic Signatures”(CVE-2022-21449)就是这样的案例。受影响的 ECDSA 验签实现遗漏了必要的参数检查,可能将签名参数 r、s 均为零的退化签名接受为有效签名。这是实现缺陷,并不是 ECDSA 算法本身被破解。(neilmadden.blog)
验签原本要证明消息由对应私钥持有者签署。程序返回成功,却没有完成这一证明。
libssh 的 CVE-2018-10933 则表现为认证状态的问题:受影响的服务端在接收到客户端提供的认证成功消息后,可能在没有凭证的情况下完成认证。“收到了认证成功消息”,被错误地等同于“服务端已经完成认证”。(libssh.org)
这些案例提供的启示,不是再增加一条“检查验签函数”的规则,而是重新审视成功条件的含义。
假设业务要求至少一名有效审批人通过,代码却只检查“所有审批人是否都已通过”。如果审批人列表允许为空,程序条件可能成立,但实际没有发生任何有效审批。
又如,业务要求多个独立主体确认,实现却只统计记录数量。同一个主体产生的重复记录,可能满足数量要求,却没有满足职责分离要求。
这些是假设场景,具体规则需要由业务契约确认。对应的 AI 任务可以写得很明确:提取关键判断的通过条件,寻找“条件成立,但安全要求未满足”的反例。
AI 可以辅助定位授权谓词、解释它依赖的状态;属性测试或约束求解工具可以辅助探索零值、空集合、重复主体、默认身份和过时数据等边界。
更重要的是,反例必须能够通过真实流程到达。直接修改数据库,制造一个正常系统不可能产生的状态,不能自动算作有效漏洞。模型理解错了业务规则,也会产生看似严密、实际上没有意义的反例。
需要审查的,不只是系统有没有检查,还包括检查成功究竟意味着什么。
上下文与表示的关系:复用时丢掉了安全相关的信息
两个操作看起来相似,不代表它们可以共享同一个结果。
libcurl 的 CVE-2022-27782 涉及连接过度复用。libcurl 会保留已建立的连接供后续传输使用,但受影响实现的匹配判断遗漏了若干 TLS 和 SSH 安全配置。因此,即使后续传输改变了本应阻止复用的选项,旧连接仍可能被复用。遗漏的配置包括证书吊销列表文件、部分 TLS 认证参数和 SSH 密钥文件等。(curl.se)
这里不是“连接池本身不安全”,而是复用条件没有完整表达连接的安全上下文。两个本应被区别对待的配置,被系统判断成了可以复用同一个连接。
curl 的另一个漏洞 CVE-2024-0853 涉及验证状态没有正确约束会话复用。受影响实现即使在 OCSP 装订校验失败后,仍可能缓存对应的 TLS 会话 ID;后续对同一主机的传输若复用该会话,就可能跳过相关状态检查。官方将这一问题限定在使用 OpenSSL、TLS 1.2 的相应实现条件下。(curl.se)
前一个案例遗漏的是配置差异,后一个案例没有正确保留验证失败带来的约束。它们共同指向一个问题:系统复用的究竟只是一个对象,还是连同它成立所需的安全条件一起复用?
这个切口可以延伸到业务系统。
假设两个租户拥有相同的局部对象编号,查询时正确区分租户,缓存时却只保留对象编号。又或者审批绑定了任务编号,但任务内容可以在审批后修改,执行时仍然复用原审批结果。
需要寻找的不是密码学意义上的碰撞,而是业务表示省略了本应影响安全决策的信息。
可以让 AI 先梳理一个结果实际依赖哪些条件,再检查缓存键、去重键、会话标识和授权凭据保留了哪些条件,随后构造两个只在关键安全维度上不同的上下文,验证它们是否被错误地视为等价。
这不意味着所有标识都要附带所有字段。不可变对象、版本绑定、重新授权或其他有效机制,都可能正确保留相关约束。
调查必须证明三件事:被省略的信息确实应该改变安全结果,系统确实发生了错误复用,而且没有其他控制补上这项差异。
局部与整体的关系:缺陷与正常能力组合成完整利用链
一个漏洞需要认证,不代表外部攻击者一定无法满足这个条件。需要继续追问:系统里是否存在另一条路径,能够提供它所需的访问能力?
Exchange 的 ProxyLogon 利用链提供了一个明确案例。DEVCORE 公布的链路由两个关键漏洞组成:CVE-2021-26855 是认证前 SSRF,可进一步造成认证绕过;CVE-2021-27065 是认证后的任意文件写入,可在完整链路中推进到远程代码执行。(devco.re)
微软的技术说明同样指出,CVE-2021-27065 所需的认证条件,可以通过 CVE-2021-26855 取得,并不一定要预先掌握合法管理员凭证。(microsoft.com)
如果只独立审查后一个问题,调查可能停留在“需要认证才能利用”。放回完整系统,前一段漏洞恰好提供了后一段的前提,两者可以衔接成从未认证入口到代码执行的路径。(devco.re)
这里不能把单点问题统称为“小问题”,也不能说各个组件单独都安全。它说明的是:局部利用条件并不是完整风险判断,前一步获得的能力可能改变后一步的可利用性。
由此可以将每个发现记录成两个部分:利用它需要什么条件,利用之后能够获得什么能力。
但仅有“身份”“文件写入”“内部访问”等标签还不够。身份属于哪个权限域,文件由哪个进程读取,资源属于哪个租户,能力能保持多久,都可能决定下一步是否真的可行。
可以让 AI 围绕一个已经证实的能力做有限扩展,寻找哪些后续操作的前提因此得到满足。每扩展一步,都需要重新核实条件,而不是一次性生成一张很长的攻击图。
这一思路也可以用于正常业务操作的组合。假设任务可以先被批准,再修改执行目标,而执行阶段没有重新确认授权,那么“提交”“审批”“修改”“执行”这些局部动作都可能符合各自接口的规则,整体却没有满足“执行内容必须经过有效批准”的要求。
这类调查需要观察完整操作序列。身份变化、异步任务、并发交错、重试和补偿,都可以成为需要纳入验证的条件。
一条成立的风险链,不是几个可疑点被连在一起,而是每一步都真实提供了下一步需要的能力,并最终突破了明确的安全边界。
把研究切口变成可验证的发现能力
这些案例证明相关问题确实发生过,但它们本身不能证明某个 AI 系统已经具备稳定发现能力。
历史评审、漏洞工单、代码差异和运行日志,是提供证据的材料;反射还原、框架建模和跨服务关联,是完成调查所需的能力;动态测试、属性测试和形式化方法,则可以帮助求证。它们应当服务于具体假设,而不是全部堆进上下文,期待模型自行给出可靠结论。
对于一项发现,我希望至少能追溯清楚:它声称哪项安全要求被破坏,关键前提分别由什么证据支持,如何在保留真实安全控制的条件下复现,以及什么结果能够推翻这个假设。
发现器和验证器可以分开,但不是让两个模型互相投票。验证必须落到代码、配置、状态和执行结果上。证据不足,应保留为待确认;为了让复现成功而移除关键鉴权,则改变了被研究的问题。
评估这些路线,也不能只看告警数量或旧 CVE 的复现成功率。我更关心的是:在合理控制模型、预算和人工投入后,它能否带来现有流程没有找到的、可复现的新根因。
一种对照可以让通用审计与专项方法拿到相同资料,比较发现策略的增量;另一种对照再加入跨服务实现或历史变更,观察新增上下文的价值。安全实现、修复版本和不满足利用条件的场景,也应该进入测试,而不是只准备能够成功的样本。
计数应按独立根因去重,并记录人工确认耗时。同一问题命中几十个接口,可能意味着影响面很大,但不意味着获得了几十种新的发现能力。
起步也不必追求完整。可以先选一次依赖升级、一种广泛使用的 SDK、一个关键授权判断,或者一类缓存和会话机制,把证据收集、假设生成与验证做通,再扩大范围。
回到最初的问题:模型能力接近之后,差异化从哪里来?
我的判断是,除了模型和工具本身,还在于能否把过去分散处理的信息放回同一个安全问题里。变化后的环境是否仍满足旧防护的前提,下游相信的条件是否由上游真正保证,检查通过是否意味着要求成立,复用是否保留了必要上下文,以及一个局部能力能否改变后续操作的利用条件。
这不是替代危险代码分析,而是让调查不再停在危险代码那里。
下一次打开一个仓库,除了问“这里有没有漏洞”,还可以从一个更具体的问题开始:
这个系统认为什么事情已经被保证了?沿着真实执行路径看,它真的被保证了吗?