对微服务平台做一次外部渗透测试,可能得到一份"干净"的报告,却几乎说明不了真正的风险在哪里。这并不是测试人员的问题,而是系统结构决定的:在微服务中,访问决策不是在一条边界上做出的,而是分散在几十个地方,其中很多从外部根本看不到。
我们先看一个假设示例。它仅用于说明,不对应任何真实客户或事件,如有雷同纯属巧合。
一个通过了外部测试的假设平台
设想一个面向企业的订单管理服务。最外层是 API 网关,负责检查用户令牌和角色,过滤明显不合规的请求。网关后面有订单服务、支付服务、通知处理程序,以及一个从队列读取事件的后台退款处理程序。服务之间启用了 mTLS,内部地址没有对外公布。
外部测试覆盖了公开接口,没有发现严重问题。随后团队商定了第二阶段:在测试环境中检查内部路径,使用专用的服务账号和两个测试租户。结果完全不同。
订单服务接受绕过网关的直接调用,并直接采用请求头中的租户标识,而这个请求头本应只由网关设置。退款处理程序接受任何有权访问该主题的服务所发布的"退款已批准"事件,却不核对订单属于哪个租户。同一事件被重复投递时,退款也被创建了两次。
这些问题从边缘都看不到,mTLS 也没能阻止其中任何一个。它如实确认了调用来自网络内部的某个服务,却无法回答这个服务是否有权对这个对象执行这个操作。"谁在调用"与"它被允许做什么"之间的这道缝隙,正是本文要讨论的内容。
为什么边缘不再等于应用
在单体应用中,"测试范围包括什么"通常就是一个应用及其入口。微服务架构则把一条边界拆成了许多小边界。每个服务都暴露接口,多数基于 HTTP 或 gRPC,并依赖网关、身份服务和其他服务。
HTTP 最初为浏览器和服务器设计,如今同一协议也承载机器之间的调用和以编程方式访问 API。问题的根源就在这里:用户请求和内部调用在线路上看起来几乎一样,但对它们的信任假设截然不同。没有人会轻信用户,却常常完全信任相邻的服务。
结论很实际:只触及公开入口的测试,只检查了众多边界中的一条,而且通常是防护最好的那条,其余部分都留在了视野之外。
每次调用中的两种身份
几乎每个内部请求都携带两个"这是谁"的答案。第一个是服务身份,即哪个组件发起了调用。第二个是用户或租户身份,即这次调用代表谁。混淆这两者,是最常见的弱点来源之一。
mTLS 回答的是"谁在调用",而不是"是否允许"
双向 TLS 让服务能够确认通信对象,并保护传输中的数据不被读取或篡改。这是有用且往往必要的一层,但它工作在传输层。证书能证明调用方是订单服务,却不能说明这次调用是否可以读取某个客户的账单。
mTLS 本身也有运维问题,值得纳入检查:密钥如何签发和分发,失陷证书如何吊销,轮换多久进行一次、是否顺畅。这些答案是信任全貌的一部分,而不是走形式。
如果服务间认证基于令牌,也会出现类似的权衡。通过中心服务在线校验令牌,可以发现已吊销的令牌,但会增加延迟;用公钥在本地校验更快,但已吊销的令牌可能还会继续生效一段时间。OWASP 关于微服务安全的资料建议按请求的重要程度选择校验方式。对测试人员来说,这意味着要检查真正敏感的操作采用了哪种方式。
用户上下文如何传入内部
有一种误解:既然 HTTP"无状态",就不应该有服务器端会话。这并不准确。协议本身不会把两个请求联系起来,但应用完全可以在服务器端保存会话,并通过 Cookie 或令牌把请求关联起来。在微服务中,关键不在于选择会话还是令牌,而在于用户上下文如何从边缘传到内部。
常见做法有三种:网关原样转发外部令牌;网关把它换成权限更窄的内部令牌;或者由边缘的可信组件签发自己的用户断言。每种做法都有薄弱点,仅仅检查"有没有签名"是不够的。
签名保护的是断言的完整性,即途中没有人改动过它,但它并不授予访问资源的权限。一个服务即使收到了签名正确的"这是租户 A 的用户",仍然必须自己判断该用户能否执行所请求的操作。还应检查调用方服务能否冒用其他用户的上下文,就像假设示例中的租户请求头那样。
每一步都要做对象与租户授权
网关擅长粗粒度检查:请求是否带有有效令牌,用户是否具备所需角色,这条路由是否允许访问。但它几乎从不知道某个订单属于谁、处于什么状态。这些信息存在于拥有数据的服务里。
因此 OWASP 的立场很明确:每个服务都必须自行控制对其受保护操作的访问,包括内部调用;网关检查只是补充。对象级、租户级和业务规则,应当在掌握上下文的地方执行。
对测试来说,这意味着要对每个敏感操作提出一个简单但费工夫的问题:服务从哪里获取租户标识,是来自已验证的上下文,还是来自请求体?它是否核对对象确实属于该租户?当另一个内部服务请求同一对象时,是否会再次检查?在多租户产品中,这些答案决定了客户隔离是否可靠,详见我们关于 SaaS 公司渗透测试 的文章。
绕过网关:当内部地址变得可达
最糟糕的组合是:所有授权都集中在网关上,而网关后面的服务却可以被直接访问。只要有一条配置错误的网络规则、一个调试端口或一个被遗忘的负载均衡器,边缘的所有检查就都失去了意义。
OWASP 的建议分为两半:网关应拒绝未授权的入站请求,架构则应阻止绕过这些检查的直接访问。渗透测试应同时验证这两点:服务是否接受不经网关的请求?是否信任本应只由网关设置的请求头?如果这样的请求头从外部传入,会发生什么?
同一问题还有更隐蔽的形式。网关、代理以及 HTTP/1.1、HTTP/2、WebSocket 之间的转换,都可能让不同组件对同一请求作出不同解析。如果网关看到的是一回事,后端服务看到的是另一回事,策略就被施加在了并非实际执行的那个请求上。这类差异应在商定的时间窗口内谨慎测试,不得影响生产环境。
网络可达绝不等于获得许可。即使测试人员看到了内部地址,也只能在书面商定的范围内访问。如果部分信任由编排平台提供,最好把应用测试与 Kubernetes 安全审计 结合起来,因为其中的网络策略和服务账号直接决定了谁能调用谁。
队列、事件与重试:系统的异步那一半
许多团队精心保护同步 API,却很少考虑后台部分。而在典型平台中,相当一部分业务操作并不是在响应 HTTP 请求时完成的,而是在从队列读取消息的处理程序中完成的。
后台处理程序面前没有用户,它只看到一条消息,必须决定是否相信它。关键问题与同步调用相同,但答案常被忽略:谁有权向这个主题发布消息?事件是否携带租户上下文,处理程序是否核对对象确实属于该租户?能否发布一个事件,触发同一服务通过 API 无权执行的操作?
重试与幂等性
许多消息代理和集成模式保证"至少一次"投递。这很正常,对可靠性也有好处,但这意味着每个可能重复到达的操作都必须是幂等的:第二条"退款已批准"事件不能产生第二笔退款。
同步重试也一样。客户端或网关可能在超时后重发请求,用户也可能连续点击两次按钮。测试应证明服务能通过幂等键或对象状态识别重复请求,不会再次执行涉及资金或不可逆的操作。这类测试应使用测试数据,并事先商定数量。
跨服务业务操作的一致性
在微服务中,"下单"往往分成多个步骤:预留库存、扣款、确认、通知。某一步失败时,系统用补偿操作撤销前面的步骤。正是在这些步骤的衔接处,会出现业务从未预料的状态:订单已确认但支付失败;退款已完成但订单仍然有效。
这已属于逻辑漏洞的范畴。自动化扫描器很难发现它们,因为每个单独的请求看起来都合法。如何组织此类测试、应向服务商提出什么要求,可参阅我们关于 业务逻辑安全测试 的文章。
信任地图:检查什么,索取什么证据
下表汇总了以上内容,既可用于规划工作,也可用于阅读报告。
左右滑动查看对照
| 信任区域 | 常见假设 | 检查内容 | 应索取的证据 |
|---|---|---|---|
| 网关与边缘 | 危险请求都在入口被拦截 | 服务是否接受绕过网关的请求,是否信任网关的请求头 | 直接访问服务的请求,以及与经过网关时相同的决策或拒绝结果 |
| 服务间调用 | 有 mTLS 就说明调用合法 | 服务是否区分调用方,并按其角色限制操作 | "哪个服务可以调用什么"的矩阵,以及未授权调用方被拒绝的示例 |
| 用户上下文 | 内部请求头或令牌无法伪造 | 调用方服务能否冒用其他用户或租户 | 在测试环境中尝试替换上下文的记录及拒绝结果 |
| 对象与租户 | 网关已经检查过权限 | 服务是否在每次访问时核对对象归属,包括内部访问 | 两个测试租户对同一对象的成对请求及不同结果 |
| 队列与事件 | 只有"自己人"会发布消息 | 谁能发布,处理程序是否核对租户和对象状态 | 主题权限,以及引用他人对象的事件被拒绝的示例 |
| 重试与一致性 | 重试无害,故障罕见 | 敏感操作的幂等性,以及部分失败后的状态 | 在测试数据上重复投递,以及对象的最终状态 |
关键在最右一列:证据是可复现的观察结果,而不是一句"已检查"。如果只写"未发现漏洞",却不说明检查了哪些路径、使用了哪些账号,这对了解系统几乎没有帮助。
下面用流程图展示同样的逻辑。
- 从边缘进入网关对用户进行认证并拒绝明显不允许的请求,但它并不知道对象属于谁。
- 传递上下文服务获得"谁调用了它"的证明,以及途中无法被篡改、可校验的用户与租户上下文。
- 在服务内决策数据所有者自行检查对具体对象和操作的权限,即使调用来自受信任的相邻服务。
- 异步延续事件处理程序重新核对发布者、租户和对象状态,并能安全处理重复投递。
- 日志与关联每一步都用共同的调用链标识记录日志,使每个访问决策都可以被还原和核查。
如何规划测试而不被服务数量淹没
NIST SP 800-115 将评估划分为规划、执行和执行后活动。对于微服务项目,可以将其转化为实践顺序:约定评估、实施测试、分析发现,再安排修复和复测。该文件发布已久,更适合借鉴其流程纪律,而不是架构方案。
规划从清单开始:列出服务、负责人、接口和数据敏感度,并区分边缘路径与内部路径。另外还需要一张异步通道地图:有哪些主题和队列,谁写入,谁读取。没有这些,任何范围界定都是随意的。
接下来是书面授权:写明服务、环境、账号和时间窗口,商定停止条件,以及出现问题时的升级联系人。授权测试至少需要两个不同租户的测试账号;如果规则允许,还需要测试范围内的一个服务账号。
对每个服务都深入测试通常不现实,而只测边缘又会漏掉内部信任失效。合理的折中是:彻底测试边缘和身份层,再有针对性地选择内部服务。选择时按两个因素加权:服务处理的数据有多敏感,以及有多少其他服务信任它。被所有服务信任的那个,最值得优先测试。
分析比看起来更重要。一个服务中轻微的弱点,可能只有与另一个服务对它的信任结合时才变得严重。报告应展示这样的链条,而不仅是孤立缺陷的清单。
如果您正在准备此类测试,用"信任地图"讨论范围比用地址清单更方便:哪些路径经过网关、哪些绕过网关,涉及哪些队列、租户和账号。带着这张地图,可以与 Pentest.red 团队一起 准备渗透测试范围,在工作开始前约定检查哪些路径、以什么形式交付证据。
每个服务内部还要检查什么
信任与授权是本文的核心,但不是全部。凡是根据输入构造命令、数据库查询或对其他服务调用的服务,都需要做注入测试。重要的是,这也包括来自内部相邻服务的数据:"内部"不等于"已净化"。
公开目录有助于确定优先级。MITRE 的 CWE Top 25 有助于把发现与根因对应起来,OWASP Top 10 2025 则便于向管理层解释风险类别。它们不能替代对具体架构的分析,但能让报告在不同时期之间具有可比性。
服务的响应也是分析材料。不同服务之间不一致的状态码和错误内容,可能暴露内部拓扑。只有当响应确实泄露敏感信息时,才应作为信息泄露报告,否则只会让报告充满噪音。
最后是日志。如果服务之间不沿调用链传递共同的调用标识,就几乎无法还原谁出于什么原因访问了某个对象。许多内部 API 就是普通的 REST 接口,可以采用我们 REST API 安全评估 一文中的方法。
报告中应要求哪些证据
一份好的微服务渗透测试报告要回答两个问题:究竟检查了什么,以及什么才算修复。第二张表帮助区分强证据和弱证据。
左右滑动查看对照
| 报告中的结论 | 强证据 | 不够充分的内容 | 如何判断已修复 |
|---|---|---|---|
| 服务无法绕过网关访问 | 从允许的网络位置直接访问的尝试记录及结果 | 未经实际验证的网络示意图 | 变更后重复同样的尝试均被拒绝 |
| 服务间认证有效 | 未授权服务或无凭据调用时被拒绝 | 配置中启用了 mTLS 这一事实 | 未授权调用方在操作层面被拒绝 |
| 租户相互隔离 | 不同测试租户对相同对象的成对请求 | 仅用一个账号做的检查 | 在所有已测路径上都无法访问他人对象,包括内部路径 |
| 重试是安全的 | 重复投递测试事件,以及数据的最终状态 | 仅说明存在带确认机制的队列 | 重复不会改变操作结果 |
| 事件经过授权 | 发布权限及被拒绝事件的示例 | 对"应该如何工作"的描述 | 引用他人对象的事件被拒绝并写入日志 |
从发现到可验证的要求
许多报告只描述发现了什么,却没有定义怎样才算"已修复"。OWASP ASVS 在这里很有帮助:它规定了应用安全验证的要求,并建议引用时注明版本,例如 v5.0.0-1.2.5,因为不同版本之间编号可能变化。要求清单以机器可读格式发布,便于导入跟踪系统。
实用的做法是根据数据敏感度和暴露程度为每个服务设定目标 ASVS 级别,再把渗透测试作为抽样,检验这些要求是否真正落实。修复后,按同一要求重新检查。如何组织复测、怎样才算关闭问题,详见我们关于 渗透测试修复验证 的文章。
需要改变的是流程,而不只是代码
测试结果不仅应进入修复队列,还应影响设计决策。CISA 的"设计即安全"倡议强调,安全是业务要求,而不是附加选项。对微服务而言,这意味着一些具体的默认做法:服务间认证始终开启;对象级授权放在共享且经过评审的库或策略中;日志统一记录,并带有调用链标识。
NIST 网络安全框架 2.0 有助于用管理层熟悉的风险管理语言,解释这些改变的范围、频率和成本。对于发布频繁的服务,可以把定期人工测试与流水线中的自动化授权测试结合起来,让"服务又开始信任请求头"这类回归在发布前就被发现。两者如何平衡,取决于发布节奏和业务的风险偏好。
微服务渗透测试无法证明什么
必须坦诚看待这种方法的局限。渗透测试是一种抽样,它展示的是系统在已测路径、已测账号和已测版本上的表现。它不能证明所有服务都没有漏洞,也不能替代威胁建模和架构评审。
WSTG、ASVS、NIST SP 800-115 等通用标准都不是专门为微服务编写的。OWASP 关于微服务的建议很有用,但它描述的也是模式,而不是现成的测试计划。其余部分依靠专业判断,而这种判断应当在报告中清晰可见:为什么选择这些服务,哪些路径没有测试,原因是什么。
最后,结论会随着架构一起过时。一个新服务、一个新主题,或网关路由的一次调整,都可能打开测试时并不存在的路径。
从哪里开始
如果把本文浓缩成一条规则,那就是:每个拥有数据或执行重要操作的服务,都必须自己判断针对具体对象的操作是否被允许,而不论调用方是谁、这次调用在途中受到了怎样的保护。mTLS、网关和签名令牌都是必要的,但它们回答的是别的问题。
实践上,先画一张地图:服务及其负责人、同步与异步路径,以及做出访问决策的位置。然后挑选最受信任、最敏感的节点,与边缘一起测试。
如果您希望与 Pentest.red 讨论此类测试,请带上这张地图和对结果的期望:哪些信任路径必须检查,需要什么证据,复测如何安排。合作形式见我们的 渗透测试服务 页面。
PENTEST.RED
讨论渗透测试范围与报价
首先明确系统、访问权限、限制条件和预期证据。
本文以深入研究为基础,经过编辑整理,注重清晰表达和实际应用。