扫描器设计初衷:从配置意图到实际执行
应用在互联网上的暴露情况复杂多变,很多服务允许无认证访问。这种公开访问可能是有意的,但挑战在于确认其是否符合安全策略。由于应用横跨多个团队和云服务,且配置频繁变更,定期审查互联网暴露面及访问控制变得至关重要。为此,团队构建了名为 Overexposed 的扫描器,专门针对 Google Kubernetes Engine (GKE) 上的 Kubernetes Ingresses 和 Cloud Run 服务进行检查。该工具首先获取云平台的已部署服务清单,随后逐一验证这些候选端点是否允许无登录访问。对于每个发现的端点,扫描器不仅给出最终判定(如暴露、预期或受保护),还会提供详细的理由和证据。正如文中所述:"> Public access can be intentional. The challenge is to understand it and check whether it matches policy." 这种机制确保了团队能够清晰掌握哪些服务是真正对外暴露的,从而进行有效的风险管理。
摒弃配置文件:为何云资产清单更可靠
在开发初期,团队曾尝试通过读取基础设施仓库中的 Kubernetes 配置文件来识别公共主机名且未设置认证规则的 Ingresses。然而,这种方法存在三大缺陷:误报率高、漏检严重且存在盲区。首先,部分团队直接在代理层设置认证,扫描器无法识别这些配置导致误报;其次,准入控制器(Admission Controller)可能在集群接受 Ingress 时动态修改设置,若该控制器未在特定集群运行,正确配置可能失效;最后,来自其他仓库或平台工具的 Ingresses 会被遗漏。核心问题在于:配置文件仅展示团队的意图,而非集群实际执行的状态。因此,团队转向使用 Google Cloud 的 Cloud Asset Inventory API。
"A repository shows what a team wanted, not what the cluster actually runs."
Cloud Asset Inventory 提供了组织内所有资源的完整清单,包括经过准入控制器处理后的 GKE Ingresses、Cloud Run 服务及负载均衡器。通过一次搜索即可获取资源及其访问策略,无需集群凭据。这一转变不仅消除了配置文件的局限性,还意外发现了此前未被检查的多个项目中的 Cloud Run 服务,从而扩大了扫描范围。
匿名探针机制:模拟外部视角的探测规则
云资产清单仅显示“可能”暴露的服务,只有从外部发起的请求才能揭示“真正”的暴露情况。Overexposed 模拟匿名用户发送 HTTP 请求,并遵循严格的探测规则以准确判断访问状态。首先,优先使用 HEAD 方法发送请求;若服务器拒绝,则改用 GET。不自动跟随重定向,最多跟踪三次并检查每个跳转。若重定向指向身份提供商(Identity Provider),则判定为受保护;若返回 401 响应并包含认证挑战,同样视为受保护。只有当收到包含真实内容的 200 OK 响应时,才判定为暴露。路径大小写敏感性也被纳入考量,例如若 /admin 受保护,还需测试 /ADMIN 等变体。
此外,工具需区分 Cloud Run 的两种控制机制:入站流量设置(Ingress settings)与身份和访问管理(IAM)。即使服务允许所有流量,若匿名用户(allUsers)没有 invoker 角色或检查被禁用,陌生人仍会收到 403 错误,这不属于暴露。对于负载均衡器后的服务,扫描器会解析 URL Map 以确定具体响应路径,并结合 Identity-Aware Proxy (IAP) 等机制进行综合判断。
安全防护与证据分类:防止 SSRF 风险并精准定性
由于扫描器可能向内部系统发送请求,存在服务器端请求伪造(SSRF)风险。即使主机名看似正常,DNS 解析也可能指向私有地址。为此,扫描器在连接前检查每个主机名及 DNS 返回的 IP 地址,拒绝已知内部名称、私有地址和回环地址。连接建立后,直接通过校验后的 IP 通信而不重新解析主机名,防止 DNS 劫持导致目标变更。同时,重复检查重定向后的目的地,并限制扫描器的网络访问以阻断内部服务和云元数据端点。
在证据分类上,结合部署配置、匿名探针结果及审批白名单,得出六种判定:暴露(无白名单匹配)、预期(有白名单匹配)、受保护、未找到、不可达或结论不明。例如,若匿名访问成功但不在白名单中,则标记为需审查的“暴露”状态;若响应存在但端点不存在或无法连接,则分别标记为“未找到”或“不可达”。这种基于多重证据的分类方法避免了单一状态码带来的误判,确保每个结论都有据可依。
状态码语义的上下文依赖性与规则表设计
为了便于审查者集中查看所有判定逻辑,我们将所有安全规则统一维护在一张表格中。这种设计揭示了一个关键发现:同一个HTTP状态码在不同服务架构下可能代表完全不同的安全含义。代码实现中,我们定义了 EXACT_OUTCOMES 映射表,将入口点(Ingress)和Cloud Run服务与特定的状态码组合对应到具体的裁决结果。例如,对于Ingress入口,200表示暴露(EXPOSED),401表示受保护(PROTECTED)且原因是认证挑战,而403和404则分别被标记为不确定(INCONCLUSIVE)或找不到资源。对于Cloud Run,逻辑略有不同:401同样表示受保护,但403和404均被标记为不确定。这是因为Cloud Run的前端网关可能在应用代码执行前就返回这些状态码,导致403或404无法作为判断应用层认证机制是否生效的确凿证据。
"A 403 response is not proof of protection either. Many apps send 403 for other reasons."(403响应也不是保护的证明,许多应用出于其他原因也会返回403。)
这种设计避免了误判。如果后端应用因业务逻辑而非安全策略返回403,将其标记为"受保护"会导致虚假的安全感。因此,不确定性不等于安全性。当证据不完整或矛盾时(如Cloud Run返回的403),扫描器必须报告为"不确定",以便人工进一步调查。这种精细化的规则表确保了裁决的准确性,而非简单地依赖状态码的表面含义。
白名单作为上下文信息而非豁免捷径
在安全扫描中,白名单条目应被视为提供上下文的参考信息,而非跳过检查的理由。我们坚持在代码中维护例外列表,以确保任何新增或修改都经过严格的审查流程。扫描器在每个运行周期中仍会对白名单端点进行探测,这是必要的,因为服务状态是动态变化的。一个原本需要认证的接口可能突然开放匿名访问,或者某个端点可能被彻底移除。如果探测结果显示匿名访问可用且与白名单中的预期路径匹配,扫描器会将结果从"暴露"更新为"符合预期";但如果服务行为偏离了白名单的预期(例如突然需要认证或消失),扫描器会保留该结果并标记条目以供审查。
"An allowlist entry is not a reason to skip the check. We keep exceptions in code so additions and changes go through review."(白名单条目不是跳过检查的理由。我们将例外保留在代码中,以便新增和更改经过审查。)
这种机制确保了白名单不会成为安全盲区。通过持续监控和对比预期与实际行为,团队能够及时发现配置漂移或意外变更。核心原则是:如果证据不足以证明保护机制有效,结果应标记为不确定,从而保持可见性并推动后续调查。这种方法平衡了自动化效率与人工审查的必要性。
提升裁决可用性与减少不确定性的策略
为了让扫描结果真正对团队有用,我们强调记录裁决背后的原因而不仅仅是最终结论。每个测试结果都详细记录了配置信息、探针观察到的现象、是否应用了白名单条目以及端点所属的团队。我们刻意避免存储响应体或凭据,因为这些可能包含敏感信息且对解释裁决非必需。我们只保留足以解释、调查和对比后续扫描结果的最小数据集。在早期运行中,约四分之三的结果为"不确定"(如2023年7月的某次运行),这限制了团队的行动力。为此,我们专注于通过增强证据来减少不确定性。
"We focused on reducing inconclusive results where better evidence could support a clear verdict."(我们专注于减少那些可以通过更好证据支持明确裁决的不确定结果。)
我们将结果按原因分组,优先处理最大的不确定性类别。当证据支持时(如明确的"未找到"或"不可达"),我们添加了更清晰的裁决;若证据仍模糊,则保持不确定。在修改任何规则前,我们会将新规则重放至历史扫描结果上进行对比测试。这一策略在几周内使Ingress的不确定结果减少了约80%。通过这种基于数据的迭代优化,我们显著提升了扫描结果的 actionable(可行动性),使团队能更有效地确认预期访问、调查不确定性并调整访问控制。
AI辅助开发与测试验证的严谨性保障
在构建Overexposed扫描器时,我们利用AI编码代理加速开发,并通过AI代码审查检查每个拉取请求。虽然这提高了速度,但也引入了风险:测试可能通过但验证的是错误的东西。例如,某项测试仅检查SQL参数是否存在,而未验证查询是否实际使用了该参数。为捕捉此类漏洞,我们引入了突变测试(Mutation Testing)机制:移除修复代码并运行测试,如果测试仍通过,则说明测试本身存在缺陷。此外,我们为每种裁决类型保留了一个金丝雀目标(Canary Target),即具有已知预期结果的测试用例。如果代码变更导致金丝雀目标返回错误的裁决,CI/CD流水线中的定期运行测试将自动失败。
"We use mutation testing on our guards. We remove the fix and run the tests. If the tests still pass, the tests are wrong."(我们对守卫逻辑使用突变测试。我们移除修复并运行测试,如果测试仍然通过,则说明测试是错误的。)
这种双重验证机制确保了规则变更的可预测性和安全性。通过在生产环境部署前对历史数据进行回放测试,我们能在代码合并前发现潜在的回归问题。最终经验表明:要准确发现无认证的应用,需要真实的资产清单、作为陌生人进行的谨慎探测以及诚实的结果报告。扫描器不应依赖猜测,而应通过解释每个裁决来协助团队确认访问权限、调查不确定性并采取行动。
结论
Overexposed扫描器通过结合Cloud Asset Inventory API与匿名外部探针,有效解决了配置意图与实际执行状态脱节的问题。其核心优势在于摒弃了易产生误报的配置文件读取,转而依赖真实网络探测和精细化的状态码规则表,显著降低了误判率。引入突变测试与金丝雀目标机制保障了逻辑的严谨性,而白名单仅作为上下文参考而非豁免条款的设计,确保了动态监控的有效性。
然而,该方案在初期面临高比例“不确定”结果的挑战。通过基于历史数据回放迭代优化规则,不确定性大幅降低,证明了持续证据增强策略的价值。最终结论表明:准确发现无认证暴露需依赖真实资产清单、谨慎的外部探测及诚实的结果报告,而非猜测。该方法平衡了自动化效率与人工审查必要性,为管理复杂云环境下的应用暴露面提供了可行动的安全依据。