源代码安全审计很容易买错。服务方案可能承诺“完整的 SAST 加人工审查”,却没有说明具体提交、构建配置、分析器实际接收的文件、人工审查排除的代码,以及关闭发现项所需的证据。最终报告或许包含有价值的缺陷,却无法可靠地对应到计划发布的版本。

采购方真正需要回答的问题更明确:哪些代码和安全关键路径接受了审查,采用了什么方法,有哪些限制,这项工作如何对应到准备发布的制品? 答案应当经得起从安全团队移交给工程、发布管理、客户评估人员或未来复测人员的过程。

本文关注的就是这份证据约定。更广泛的应用安全审计还可能覆盖架构、运行中的基础设施、API 与运营控制。源代码审查则从内部检查约定的代码基线。它可以为其他活动提供依据,但单独进行时不能证明已部署服务的实际行为,也不能证明某个二进制制品只包含已审代码。

先明确要支持的决策,再选择审查类型

首先写明审计要支持什么决策,例如接受一个发布候选版、调查关键遗留组件、向客户提供独立证据,或在合并前检查高风险改动。这个决策决定项目应采用基线审查、差异审查,还是两者结合。

基线审查会勾勒约定代码库的边界,并深入检查其中的高风险部分。它适合首次独立评估或重大版本,但“基线”不能被理解为人工对每一行投入同等精力。差异审查检查两个不可变版本之间的变化,并判断变化如何影响现有控制和信任边界。只有在旧基线仍然有效时,这种方法才能节省后续版本的审查时间。混合方式则先贯穿基线中的关键路径,再沿着这些路径检查改动。

要求服务商把工作量翻译成可观察的单位:仓库和版本、语言和构建目标、自动化实际处理的目标文件、人工追踪的路径、得到验证的发现项,以及明确排除的区域。代码行数可以辅助估算工作量,但不能衡量安全覆盖。一处授权函数可能比数千行生成的模型类更重要。

固定被审查的对象

分支名和版本标签可能移动。范围记录应以规范位置和不可变提交标识来识别每个仓库,并在使用子模块时记录其版本。还要记录审计方拿到的是干净归档、只读仓库克隆,还是有时限的平台访问权限。项目启动时使用的清单以及最终范围台账,都应与报告一起保存。

接下来说明这些源代码如何变成计划发布的版本。有效链路不能只写“审查了 3.4 标签”,而应覆盖提交、依赖锁定状态、构建目标、构建模式、影响安全的构建参数、生成过程的输入、产出的制品,以及制品摘要或其他不可变标识。如果交付流水线已经产生可核验的构建来源记录和证明记录,它可以加强最后一环。但它仍不证明构建过程可信或软件安全,只是标明某项构建声明指向哪个制品。

从审查范围到修复验证的证据链
  1. 基线记录仓库、不可变提交、子模块、锁文件、构建目标、配置假设与排除项
  2. 分析范围保存工具与规则版本、构建模式、目标及跳过文件、警告、错误和生成代码处理方式
  3. 人工追踪沿选定入口追踪身份、授权、转换、安全决策和敏感操作
  4. 发布与复测把发现和修复对应到版本,重新构建目标制品,并验证原路径及邻近变体

对于编译型和生成型系统,这一区别尤其重要。某些分析器可以在受支持语言上不执行构建而建立模型;另一些配置会分析实际构建,构建命令可能由工具自动选择,也可能由使用者明确提供,而这些命令可能只覆盖选定目标。因此,“扫描成功”不能回答哪些源文件真正进入了分析。应要求提供构建模式、命令或目标、分析范围摘要、警告、跳过文件,以及与预期仓库树的核对结果。

明确定义纳入代码、排除代码与配置

工作说明书应对代码分类,不能只列仓库。至少要区分第一方应用代码、内部共享库、构建与部署脚本、基础设施定义、迁移、模板、测试工具、生成代码、随仓库交付的第三方源码以及外部包。对每一类说明它接受自动分析、依赖分析、人工审查、配置审查,还是完全不在范围内。

生成代码需要单独决定。审查生成文件可能发现不安全的输出,但持久的修复往往应落在生成器、模式、模板或选项中。排除生成代码也可能隐藏真实执行的代码。范围台账应写明生成器及其输入、输出是否随产品交付,以及审查的是哪一层。自研框架同样如此:如果分析器不了解组织自己的路由、授权包装器、模板引擎或净化函数,通用规则可能漏掉关键路径。服务商应披露自定义模型、桩代码或规则,以及仍然存在的分析限制。

工具配置本身就是证据。应保存分析器和规则包版本、启用与禁用的规则、严重性过滤器、路径纳入与忽略设置、语言模块失败、超时、解析错误,以及分析能否跨函数或跨文件。相同工具的不同版本或模式可能有不同的语义能力。缺少这些背景的仪表盘数字无法表示可复现覆盖。

左右滑动查看对照

审查层能够证明什么应要求的证据必须记录的边界
SAST 与语义分析引擎实际分析文件中的规则匹配和已建模数据流工具与规则版本、构建模式、目标、跳过文件、警告、原始和分流结果不支持的语言、缺失的框架模型、逐文件分析、超时或解析失败
依赖审查声明的依赖、实际解析出的版本,以及适用的漏洞或策略信号锁文件、解析器输出、准确的包名与版本,以及评估过的可达性理由未声明、随仓库复制、运行时加载或服务端组件
配置审查已提供材料中的安全默认值和相关设置具名配置档、清单、环境假设及可获得的实际应用值未提供的秘密和生产覆盖值;范围外基础设施
人工审查选定路径上的授权、数据流、状态与业务规则语义已审路径清单、入口、决策点、敏感操作、审查记录和已验证发现未选路径、不可访问服务以及未能测试的假设
代码辅助的运行验证约定行为能否在获准构建或环境中复现构建和版本身份、账户、请求、响应、日志与测试条件部署漂移、外部服务、未测角色和仅在生产出现的行为

人工追踪安全关键路径

人工审查应沿着系统本身提出的问题进行,而不是在文件之间随意浏览。先明确资产、角色、信任边界、入口和滥用场景,再选择那些一旦出错就会改变访问权限、资金、敏感数据、租户隔离、代码执行或不可逆状态的路径。

审查授权时,从外部可达处理器开始,依次跟踪身份解析、租户选择、对象加载、策略判断、缓存,直到最终敏感操作。记录决策发生在哪里、依赖哪些属性、上下文缺失时采用什么默认行为,以及其他入口是否可以绕过该判断到达同一敏感操作。对于数据流,要识别源、转换和校验、框架行为、净化假设以及最终接收点。对于业务流程,先写出必须始终成立的不变量,再跟踪所有可能破坏它的状态转换。如何采购这类工作,可继续阅读业务逻辑安全测试指南。

考虑一个假设示例:某多租户发票服务的主 API 从签名令牌中解析租户,然后调用共享的 loadInvoice 辅助函数;后台导出处理器从队列消息接收租户标识,再调用同一函数。工具能够发现部分授权模式,但不了解这一应用关系的通用规则可能不产生告警,即使所有值的类型都正确。人工追踪会继续询问:谁能创建队列消息,消息租户是否绑定到任务所有者,辅助函数是否强制这一关系。覆盖记录应列出两个入口、共享决策点、导出操作,以及仍需运行验证的假设。

人工覆盖应以已审路径和安全决策表达,不能写成“阅读了 70% 代码”。维护一份清单,覆盖认证、授权、密码学、反序列化、文件处理、命令和查询构造、秘密、出站请求、日志、管理操作以及关键业务状态转换。每项标记为人工追踪、仅工具检查、抽样、受阻或范围外,并说明原因。

在接受报告前验证发现项

自动告警只是候选项。服务商在把它写成漏洞之前,应检查真实调用路径、配置、前置条件、现有控制和可能影响。在安全且获得授权时,针对约定构建进行小范围验证,可以区分可达弱点、死代码和错误模型。如果无法进行运行验证,报告应保留这种不确定性,不能暗示已经证明可利用。

每个接受的发现项应包含受影响版本、文件以及符号或稳定代码位置;失败的安全规则;相关的源到敏感操作路径或授权链;攻击者所需位置与条件;影响;置信度;严重性理由和修复方向。行号会在代码改动后迅速失效。函数、路径、版本和简洁证据片段可以让记录更持久。

分流过程也要有审计轨迹。保留原始发现及其处置:已确认、重复、误报、接受风险、由其他控制缓解、未验证或范围外。告警在后续扫描界面中消失,并不能单独证明修复。规则可能被修改或禁用,文件可能被删除或加入忽略项,扫描也可能运行在另一个分支。

报告还应解释负面结果。“没有关键发现”只意味着在说明的方法、范围和时间内没有确认关键问题;它不是认证,也不证明代码没有漏洞。渗透测试报告示例展示了技术发现应有的证据粒度,但源代码审查的验收还需要版本、构建和覆盖台账。

用绑定版本的证据验收修复

在项目开始前约定复测条款:纳入哪些严重级别或发现项、提交修复的时间窗口、循环次数、开发者提供哪些材料,以及是否检查同类变体。每个修复都应以不可变版本或窄范围差异提交,并链接到原发现。审查人员检查改动后的控制、原始脆弱路径、可预见的相邻路径,以及补丁引入的新行为。

合同应提前约定关闭状态的含义,并保持彼此区分。例如,已修复可以定义为:已审改动处理了根因,并且在可比条件下用约定方法无法再证明原弱点。如果复测仅检查源代码,仍应保留运行行为的不确定性。部分修复可以表示风险降低但仍有路径或变体。已缓解可以表示其他控制限制了影响,而根因仍在。接受风险是风险所有者的决定,不是技术修复。无法验证用于记录缺少构建、访问或环境证据。

在假设的导出缺陷中,有说服力的复测证据包括修复提交、任务创建端和工作进程端新增的检查、证明任务不能跨租户的测试、对 loadInvoice 其他调用者的检查,以及在运行验证纳入范围时由该版本构建出的证据。关闭的工单或消失的扫描告警都更弱。关于如何针对发现及其变体组织修复验证,可参阅我们的独立指南。

保护源代码并明确审计交付物

在授予访问权之前,应约定谁能查看源代码、哪些位置可能存在副本、访问如何记录、是否有分包商参与、工作副本和日志保留多久,以及如何证明删除。可行时使用受控测试凭据;对于审查确实需要的秘密证据,则应约定受控处理方式。任何删改或替换都必须记录,因为它改变了提供给审计方的基线,也可能隐藏相关路径。在满足方法需要时,优先采用只读和限时访问。

使用 AI 或托管分析还需要单独定义边界。服务商应说明哪些代码、发现、提示词、元数据和日志会离开采购方控制的环境;接收它们的服务与区域;保留和训练条款;参与处理数据的第三方;操作人员访问;以及本地或离线替代方案。还要约定仓库文本是否被视为代理的不可信输入,以及代理可以执行哪些动作。“我们使用 AI”和“我们不使用你的数据训练”都不足以描述数据流。

价格和周期应由证据负担决定,包括语言与框架的新颖程度、构建目标数量、依赖和生成代码复杂度、关键路径、环境准备度、验证深度及复测轮次。使用同一覆盖矩阵比较方案。低固定价格可能适合窄范围差异审查,却不能与包含自定义建模和代码辅助验证的基线审查直接比较。

最终证据包应包含签署的范围与授权、基线和排除台账、源代码访问记录、工具与构建配置、自动化覆盖和分流证据、人工审查路径清单、已验证发现、限制、修复决定,以及绑定版本的复测结果。它还应明确哪些发布或制品关系已有证据,哪些只是项目假设。

如果运行时影响很重要,应把这一步明确采购。一项约定的白盒Web 应用渗透测试可以利用源代码背景针对运行中的应用,而代码审查则解释实现路径。Pentest.red 可以在工作说明书中明确列出源代码审查活动后,协商一项正式授权的白盒测试或代码辅助验证。可先使用范围规划工具记录系统、版本、角色、构建证据、排除项、处理限制和验收交付物。最终产物是有边界的技术评估和证据包,不是认证,也不是发现所有弱点的保证。

PENTEST.RED

讨论渗透测试范围与报价

首先明确系统、访问权限、限制条件和预期证据。

准备测试范围

本文以深入研究为基础,经过编辑整理,注重清晰表达和实际应用。

PENTEST.RED / RED JOURNAL