我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 96.75% 的成绩。 了解更多

安全

安全

产出证据而不只是检测结果的 AI 渗透测试提示词

一份实用指南,介绍如何设计 AI 辅助的安全测试工作流,借助结构化输出、验证关口和受控执行,将限定范围内的证据转化为可供审查的检测结果。

您让模型去找一个漏洞

设想一个新的 API 摆到了您的桌上。它包含几十条路由、陌生的中间件,以及足够让审查人员忙上好几天的代码量。

给模型的提示词:“审查这个代码仓库,找出安全漏洞。”

片刻之后,答案来了。它提到了 SQL 注入、硬编码密钥、缺失的授权检查以及不安全的反序列化。内容条理清晰、语气笃定,格式也像一份安全报告。

决策点:您会把它发给工程团队吗?

先别急。首先要弄清模型实际测试了什么。也许没有任何一个端点被从请求一路追踪到数据库,没有任何输入到达危险的汇聚点(sink),没有任何授权判断被实际触发,也没有检查任何可利用性条件。输出中可能包含有用的假设,但假设还不是检测结果。

AI 辅助的安全测试正是在这里分道扬镳:要么变得有用,要么沦为噪声。挑战不在于让模型听起来像一名渗透测试人员,而在于从一个宽泛的请求收窄到一个具体的问题,从这个问题走向证据,再从证据得出另一名工程师可以复现的结果。

让我们一次一个决策地重建这次测试。

第一步:选择一个值得回答的问题

在生成的线索中,有一个可能存在于 GET /v1/invoices/{id} 的授权问题。

初步结论:“该端点可能会泄露属于其他用户的发票。”

所需证据:确定资源的所有者,找到授权控制所在位置,了解攻击者拥有的访问权限,并观察一名已认证用户请求另一账户的发票时会发生什么。

测试目标:确定已认证用户能否通过 GET /v1/invoices/{id} 获取属于其他账户的发票。

目标变小了,但调查变深了。它现在有了一个接口、一条所有权边界、一项攻击者能力,以及一个成功条件。

同样的转变适用于各个安全领域:

宽泛的请求 工作流能够回答的问题
检查 Android Intent 对于导出的 Activity ShareActivity,攻击者控制的 extra 能否在未经校验的情况下到达特权组件?
查找注入 sort 查询参数能否在未经安全处理的情况下到达动态查询操作?
查找存储的密钥 可疑的敏感值是否被写入应用存储?在所述的攻击者模型下能否被恢复?

这就是上下文隔离的实际做法。不要把整个代码仓库一股脑丢给模型,而是提供路由定义、相关中间件、处理函数、数据访问代码、测试,以及观察到的请求或响应证据。

现在,模型需要翻阅的材料更少,要回答的问题也更明确。

接下来:确定什么才算证明

假设模型读取了发票处理函数后返回:

初步结论:“该端点存在 IDOR 漏洞,因为它按 ID 获取发票。”

结论:还不能这么说。按 ID 获取对象是正常的应用行为。关键问题在于,在返回响应之前是否执行了所有权校验或其他授权策略。这项控制可能存在于中间件、服务层、数据库查询,或可见代码之外的某个策略函数中。

在要求给出结论之前,先为任务定一份证据契约。要求工作流返回所检查的代码路径、攻击者能力、预期的授权控制、验证所用的请求与响应,以及 confirmed、needs_validation 或 not_reproduced 三者之一的状态。

结构化输出并不能让结论成立,但它能让缺失的证据一目了然。

task:
  id: api-object-authorization-001
  vulnerability_class: broken_object_level_authorization
  target:
    method: GET
    path: /v1/invoices/{id}
    owner_boundary: account_id
  objective: >
    Determine whether an authenticated user can retrieve an invoice that belongs
    to a different account.
  permitted_actions:
    - Read source files listed in context.
    - Inspect supplied test traffic and test fixtures.
    - Generate a test request only against the approved staging target.
  required_evidence:
    - Authorization check location or its absence.
    - Request and response identifiers for the cross-account test.
    - Expected versus observed authorization result.
  output_rules:
    - Do not report a confirmed vulnerability without runtime or test evidence.
    - Return needs_validation when execution is unavailable.
    - Cite every conclusion with an evidence reference.

这份契约引入了一条无论提示词如何修改都应保留的规则:

没有证据,就没有确认。

然后:让每个结果决定下一步

模型追踪了这条路由。它找到了身份验证中间件,以及一条按标识符加载发票的数据库查询,但仍然找不到所有权条件。

下一个决策:不要再生成另一段解释,而是选择能够减少不确定性的下一项测试。工作流需要一个受控的循环:

  1. 规划:确定能够减少不确定性的最小问题。
  2. 收集:获取相关的源代码、测试夹具、流量或经批准的工具输出。
  3. 分析:将证据与所审查的安全控制关联起来。
  4. 验证:重放请求、运行安全的测试、检查追踪记录,或请求人工审查。
  5. 决定:确认该发现、收窄假设,或停止。

对于发票端点,使用两个由测试人员控制的预发布环境账户。让账户 A 创建一张包含唯一且不敏感标记的发票。首先确认账户 A 能够通过被测端点获取它,然后以账户 B 的身份进行身份验证,并请求同一个标识符。

验证问题:什么样的结果能证明这个问题?

如果账户 B 收到了账户 A 的唯一标记,则测试证明存在跨账户访问——但前提是发票是私有的,并且两个账户之间没有共享关系。如果请求被拒绝,则说明被测路径在这种情况下执行了边界控制。如果缺少基线、响应不明确、共享是有意设计的,或测试环境不可用,结果仍为 needs_validation。

源代码追踪与运行时响应回答的是不同的问题。前者显示控制看似缺失的位置,后者显示运行中的系统实际如何表现。可靠的工作流会同时保留这两部分证据,绝不以其中一个替代另一个。

每一轮循环都必须改变调查的状态。如果它只是产出同一怀疑的另一个版本,那么系统是在生成文字,而不是在测试目标。

现在:将同样的方法应用于其他安全问题

发票案例为我们提供了一条完整的路径,但 AI 渗透测试工作流不能只围绕授权来设计。让我们把同样的推理带入三个不同的调查中。

Android Intent 只是一个开始

模型发现了一个名为 ShareActivity 的导出 Android Activity。它还看到了一个传入的 Intent extra 以及对 startActivity 的调用。

决策点:这足以报告 Intent 重定向吗?

不够。工作流仍需把各个环节串联起来。它应当确认该组件可从外部访问,确定攻击者能控制哪些 extra,将这些值追踪到 startActivity、startService 或 sendBroadcast,并检查调用之前施加的任何校验或组件限制。

下一步是从另一个独立应用发起受控的运行时测试。要确认 Intent 重定向,构造的输入必须让受害应用执行测试应用无法直接调用的操作。例如,它可能到达某个未导出的内部组件或受保护的操作。

打开另一个已导出的 Activity 并不能证明存在安全绕过。如果入口 Activity 未导出、嵌套的 Intent 受到限制,或者没有产生任何受保护的效果,请收窄结论或将其标记为 not_reproduced。

危险的 API 只是一条线索。可达性、攻击者控制和观察到的行为,才决定它能否成为一项检测结果。

查询参数并不自动等于注入

接下来,模型注意到 sort 查询参数到达了一个构建查询的函数。它将这条路径标注为“可能存在 SQL 注入”。

下一个问题:这个值是成为 SQL 命令的一部分,还是仍然作为数据?如果有一个允许列表把 name 或 created_at 映射到固定的列名,结论就会改变。如果该值被直接插入查询,就有理由在经批准的环境中进行一次安全的验证测试。

工作流将该值从 HTTP 处理函数追踪到查询构建器,并记录任何校验或转换。随后,它针对测试人员拥有的数据运行成对的、非破坏性的测试。要确认注入,攻击者控制的输入必须改变查询的行为,同时基线请求保持稳定。仅有数据库错误是不够的:格式错误的输入可能导致错误,却并不意味着可以注入。没有可达攻击者输入的字符串拼接仍然只是静态证据,而非已证实的利用。

敏感值必须真正到达存储

最后,模型发现了将值写入 shared preferences、本地数据库或缓存的代码。它报告“敏感数据以不安全的方式存储”。

缺失的证据:工作流必须确定是哪一个值。存储 API 的存在并不能证明密码、令牌、个人数据或加密材料会经由它写入。接着,它必须追踪写入路径,或执行相关的应用流程,并在所述的攻击者模型下检查由此产生的存储内容。

如果测试人员拥有的敏感值出现在磁盘上,请记录其位置、产生步骤、保护措施和恢复条件。同时说明提取是否需要可调试的构建版本、run-as、备份访问、Root 权限、物理接触或运行时插桩。

只有在获得 Root 权限后才能从私有存储中恢复的数据,与暴露在外部可读存储中的数据,并不能支撑同等的风险结论。如果证据只显示了一个通用的存储辅助函数,该结论仍只是假设。

这些示例使用了不同的工具和证据,但遵循相同的推进过程:收窄问题、追踪攻击者控制、确定预期的控制、安全地测试,并保留观察到的结果。

线索在验证关口成为检测结果

账户 A 成功获取了自己带标记的发票。随后,与其没有任何共享关系的账户 B 通过同一端点获取到了同一个私有标记。工作流记录了两个身份、所有权基线、请求、响应、代码路径以及观察到的跨账户泄露。

直到此时,这条线索才能成为一项已确认的检测结果。

不同的结论需要不同的关口:

结论 工作流必须证明的内容
敏感值存储在本地 从所述的存储位置恢复一个测试人员拥有的值,并记录访问前提条件。
端点缺少对象级授权 先确认所有者可以访问,然后证明第二个由测试人员控制、且无共享关系的主体也能访问。
用户输入到达危险的汇聚点 追踪攻击者控制,并使用成对的安全测试证明该汇聚点与安全相关的行为。
远程代码执行 在经授权的环境中产生一个唯一且无害的执行标记;仅有崩溃是不够的。

可疑的 API、看似缺失的检查、不寻常的端点或危险的函数,都可以指引下一项测试,但没有一项会自动成为报告级别的漏洞。

如果无法进行验证:如实说明。Needs_validation 比一个言之凿凿的误报更有用,因为它告诉审查人员还有哪些未知。

在给模型工具之前,先划定边界

调查进展顺利,于是人们很容易想给模型更多访问权限:更多工具、更多目标,甚至生产环境凭据。

安全问题:如果这个工作流误解了任务或遵循了恶意内容,它可能采取的破坏性最大的操作是什么?

“小心一点”不是安全边界。执行层必须强制实施只读的源代码访问、目标允许列表、合成身份、速率限制、经批准的预发布环境,以及在执行改变状态的操作前进行人工审批。

代码仓库文件、工单、网页、日志和目标响应都是不可信的输入。它们可能包含意图劫持 AI 系统的指令。因此,提示词注入和过度自主是系统设计层面的风险,而不是仅靠巧妙措辞就能解决的问题。

模型可以提议一个操作,由执行层决定是否允许。

一次成功的测试仍然只是一次测试

可靠性问题:一次成功的发票调查能否证明提示词架构是可靠的?

不能。它只能证明一个工作流处理好了一个案例。

用已确认的漏洞、已知的误报、干净的组件,以及正确结果应为 needs_validation 的不完整案例构建一个评估集。每当模型、提示词、工具、上下文选择规则或输出模式发生变化时,都运行这些案例。

然后问:

  • 它是否识别出了已知问题?
  • 它是否附上了正确的证据?
  • 它是否排除了已知的误报?
  • 在无法获得运行时证明时,它是否停了下来?
  • 审查人员能否复现该结论?

这会把提示词编辑变成一项工程工作。一次修改之所以值得保留,是因为它改善了可度量的结果,而不是因为它产出了更有说服力的文字。

现在您会信任哪一个?

回到最初的响应:一份从宽泛的代码仓库审查中生成的、措辞精致的可能漏洞列表。

现在将它与发票调查进行比较。第二个结果拥有限定的目标、攻击者模型、代码追踪、两个受控身份、捕获的请求与响应、观察到的授权失败,以及一个通过了既定验证关口的状态。

最终决策:您会把哪个结果发给工程团队?

AI 可以帮助安全团队浏览大型代码库、整理证据、生成有针对性的测试计划,并决定接下来要检查什么。但它并不能免除对目标、测试、控制和审查人员的需要。

目标不是一份更长的可能漏洞列表,而是一条贯穿目标的、简短且站得住脚的路径:

范围 → 问题 → 证据 → 测试 → 观察到的结果 → 结论。

这就是一个能够描述安全测试的模型,与一个能留下可供另一名工程师重放、质疑、修复和验证之成果的工作流之间的区别。

参考资料