规模化动态应用安全测试(DAST)
引言
微软工程团队使用安全开发生命周期(Security Development Lifecycle)来确保产品符合微软安全未来倡议(Secure Future Initiative)的安全原则:设计安全、默认安全和运营安全。安全测试是安全开发生命周期的关键组成部分,旨在在攻击者利用之前发现并缓解安全漏洞。大多数企业已经建立了成熟的手动流程(如代码审查、渗透测试和红队演练)和自动化流程(如静态应用安全测试SAST)的解决方案。然而,在保护API Web服务方面,企业通常在动态应用安全测试(DAST)方面遇到困难。这是因为SAST工具可以轻松由DevSecOps团队集成到Web服务的CI/CD流水线中,而无需开发人员额外工作,但DAST工具的编排则不那么简单。
在本文中,我们回顾了最近的BlueHat演讲,讲述了微软Azure Edge & Platform组织如何利用安全自动化大规模执行DAST。这项努力针对微软第一方服务组合中的数千个内部和外部API Web服务,这些服务是由微软自己创建、拥有和管理的应用程序或服务。
为什么大多数企业难以扩展DAST
DAST是一种软件测试过程,通过在运行时执行软件来发现安全漏洞或弱点。在测试Web应用程序和Web服务的背景下,这通常包括通过模糊测试或其他方式发送畸形Web请求,根据Web服务器对这些请求的响应来识别错误。有许多优秀的商业和开源DAST工具可用于扫描Web应用程序,包括微软的RESTler。无论我们想用一个还是多个DAST工具扫描Web服务器,工具都需要知道Web端点,并且Web服务器的端点逻辑需要处理其请求。它还需要访问Web服务的API规范和有效的认证凭据;这两者在大规模安全提供时都不简单,且需要开发人员手动操作。
Web端点发现
历史上,DAST工具唯一需要的输入是目标Web服务器地址。DAST工具会自动爬取给定站点,从主页开始,并跟随HTML中嵌入的链接来发现所有站点页面。通过这种方式,DAST工具会发现站点提供的所有动态功能,这些功能在页面(或更一般地说,URL)的上下文中接受用户输入。
在上图中,我们可以看到DAST工具从https://www.contoso.com/开始爬取,跟随主页的链接到https://www.contoso.com/contact.html,并在该页面上发现一个联系表单,通过URL参数和/或请求体接受用户输入。DAST工具现在可以向https://www.contoso.com/contact/inquiry.asp页面发送带有畸形值的请求,尝试在动态页面的输入处理中发现安全漏洞。
总的来说,这种爬取方法对于传统网站仍然有效,但不能用于功能仅通过API暴露的Web服务。考虑下图,其中移动应用程序与REST API Web服务https://api.fabrikam.com/通信:
在此示例中,移动应用程序将REST API端点/login、/products、/cart和/logout硬编码到应用程序中。当DAST扫描器尝试打开https://api.fabrikam.com/时,它会收到错误。
为了支持这种场景,现代DAST工具通常允许用户通过OpenAPI规范(有时称为Swagger规范)提供端点列表作为输入。该规范详细列举了Web服务的API URL、每个API接受的所有参数以及描述每个参数预期格式的元数据。OpenAPI规范可以通过NSwag和swagger-core等包生成。使用这些包需要访问给定服务的源代码和工作构建环境。这要求开发人员手动将这些解决方案集成到Web服务项目中,这对于较小的组织来说可能是一个好方法。
然而,如果企业安全团队要求所有开发人员将此类解决方案集成到他们的Web服务项目中,他们肯定会受到领导和开发人员的抵制,因为手动更新每个项目的代码和构建配置的成本巨大。
可扩展的自动化OpenAPI规范生成解决方案(某种程度上)
有几种技术可以自动生成OpenAPI规范,而无需每个服务的手动开发人员参与。
一种解决方案是监控发送到目标Web服务器的请求,并根据这些请求实时推断OpenAPI规范。这种监控可以在客户端、服务器端或在API网关、负载均衡器等中间进行。这是一个可扩展、可自动化的解决方案,不需要每个开发人员的参与。根据运行时间的长短,这种方法在全面识别所有Web端点方面可能有限。例如,如果没有用户调用/logout端点,则/logout端点不会包含在自动生成的OpenAPI规范中。
另一种解决方案是静态分析Web服务的源代码,并根据定义的API端点路由生成OpenAPI规范,自动化可以从源代码中收集这些路由。微软内部原型化了这种解决方案,发现通过解析抽象语法树来可靠地发现所有API端点路由和所有参数并非易事,且无需访问工作构建环境。该解决方案也无法处理动态注册API路由端点处理程序的场景。微软还内部原型化了一种静态分析解决方案的变体,使用LLM从源代码推导OpenAPI规范。我们看到了一些有希望的结果,但不幸的是,该解决方案是非确定性的。
为了真正为数千个Web服务扩展DAST,我们需要自动、全面和确定性地生成OpenAPI规范。即使有了这样的解决方案,我们仍然需要确保我们的DAST工具能够执行测试服务的功能。
认证和授权
大多数企业Web服务需要认证,因此,为了完全执行Web服务的功能,DAST工具需要提供认证凭据。Web服务还需要授予DAST工具向服务提交请求的授权。在测试环境中,这通常意味着每个服务使用唯一的测试帐户或使用公司范围的测试帐户,并相应配置DAST工具。前者在编排每个服务的DAST工具执行方面需要显著的复杂性,而后者允许中央妥协点。两种方法仍然需要每个服务所有者手动进行配置更改。同样,对于OpenAPI规范生成,领导和开发人员可能会抵制这种巨大的成本。
可扩展的DAST解决方案
作为云服务提供商,微软拥有我们的Web服务运行的IaaS和PaaS平台,并能够生成我们第一方服务的清单,并将自定义安全工具部署到这些服务上。负责微软边缘计算和操作系统的Azure Edge & Platform组织正在创建一个解决方案,通过开发一个代理来利用这种部署能力,该代理可以部署到我们所有基于IaaS和PaaS的Web服务的非生产测试实例上。这些非生产实例与生产实例共享相同的代码。在非生产环境中执行DAST可以保护生产环境中的数据保真度。DAST代理是一个更大的DAST编排平台的一个组件,该平台消除了对Web服务构建环境的访问需求,也无需麻烦开发人员手动将新功能集成到其服务的CI/CD流水线中。代理可以完全访问运行Web服务进程的运行时状态,使其能够检查内存并将新代码加载到运行进程中。
Web端点发现
典型的Web服务器维护URL路由到路由处理程序的映射以管理传入请求。代理利用这种架构,在运行时检查Web服务器进程的内存以发现其路由映射,并从识别的映射生成OpenAPI规范。
我们组织中的绝大多数Web服务是用ASP.NET编写的,它使用请求处理管道来处理传入请求:
上图中的每个块都是一个中间件组件,它有机会在处理传入请求之前将其传递给管道中的下一个组件(或者短路剩余的中间件组件)。
管道中的最后一个中间件组件是端点组件。根据Microsoft.AspNetCore.Builder.EndpointRoutingApplicationBuilderExtensions.UseEndpoints()的文档,“Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware定义了中间件管道中做出路由决策的点,并且端点与HttpContext关联。”我们检查Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware的源代码,发现它包含一个名为_endpointDataSource的字段,这是一个Microsoft.AspNetCore.Routing.EndpointDataSource对象,它本身包含一个名为Endpoints的属性。根据文档,该Endpoints属性是“端点实例的只读集合”。每个端点实例包含一个元数据集合,其中保存路由端点的详细信息。
我们的Web端点发现解决方案涉及代理在运行时自动发现Web服务器进程内存中的请求处理管道,并遵循上述对象字段找到所有端点对象实例。代理中的自动化然后处理与每个实例关联的元数据,并使用Microsoft.OpenApi.Writers.OpenApiJsonWriter动态生成Web服务的完整OpenAPI规范,包含所有API端点及其详细参数。然后,该OpenAPI规范可以作为输入提供给DAST工具以扫描目标Web服务。
虽然上述细节特定于ASP.NET Core,但通过运行时内存检查进行路由发现的相同方法可以应用于其他框架(如ASP.NET Framework、PHP、Node.js等)编写的Web服务。
认证和授权
为了克服使用测试帐户允许DAST工具向Web服务发送认证请求的挑战和风险,我们可以在运行时挂钩认证和授权中间件组件,完全消除对测试帐户的需求:
我们的认证和授权解决方案是一种我们称为透明认证(Transparent Auth)的技术。这涉及在认证组件之前和授权组件之前插入一个新的委托。我们的代理通过自动遍历内存中的请求处理管道并在运行时插入新委托来实现这一点。
虽然透明认证的细节特定于ASP.NET Core,但运行时认证和授权挂钩的相同方法可以应用于其他框架(如ASP.NET Framework、PHP、Node.js等)编写的Web服务。
认证挂钩
我们在管道的认证组件之前注入的挂钩检查客户端的请求,以确定它是否来自我们的编排DAST工具之一。我们使用一个进程外组件来跟踪所有源自编排DAST工具的请求,我们的挂钩与该组件通信以验证请求确实源自编排DAST工具。如果我们的挂钩确定请求不是来自编排DAST工具,则它将请求传递给原始认证组件,该组件正常处理请求。
另一方面,如果挂钩确定请求来自编排DAST工具,它有一些额外的工作要做。它不能仅仅跳过原始认证组件,因为如果其他中间件组件尝试使用与请求关联的身份,这可能会引起问题。例如,想象一个Web服务响应认证请求时返回“Hello, <用户名>”——如果没有与请求上下文身份关联的用户名,此功能将抛出异常。为了解决这个问题,我们的挂钩创建一个新的ClaimsPrincipal并将该ClaimsPrincipal与当前请求的上下文关联。ClaimsPrincipal由Claim对象的集合组成,以充实模拟用户的身份,包括模拟用户的名称、电子邮件地址等值。
一旦请求的上下文使用这个新的ClaimsPrincipal更新,我们的挂钩跳过原始认证组件并调用紧接原始认证组件之后的请求委托。
授权挂钩
我们在管道的授权组件之前注入的挂钩开始时与认证挂钩类似。它验证客户端的请求是否来自我们的编排DAST工具,如果不是,则将请求传递给原始授权组件。
如果挂钩确定请求来自编排DAST工具,它会对请求的上下文进行一些小的更新,这些更新通常在成功授权时完成。然后它跳过原始授权组件并调用紧接原始授权组件之后的请求委托,从而绕过授权检查并允许服务的端点逻辑处理请求。
DAST编排平台架构
上述代理的功能适用于整体DAST编排平台架构;下图高级别地说明了这一点。
微软的安全扩展部署机制将代理部署到第一方微软Web服务器上。代理然后将OpenAPI规范生成模块和透明认证模块注入到Web服务器上运行的每个Web服务进程中。OpenAPI规范生成模块为Web服务生成OpenAPI规范,并将其传递给由代理启动的DAST代理进程。DAST代理启动一个远程机密容器,将OpenAPI规范发送到容器,并根据提供的OpenAPI规范在容器中运行编排的DAST工具。
我们使用机密容器有几个原因。首先,它们的可信执行环境确保如果攻击者破坏Azure的容器实例服务,他们将无法篡改我们容器中的代码或数据。机密容器还使代理能够验证与其通信的容器的证明报告,以确保容器是从官方DAST容器镜像构建的,并且应用于容器的安全策略是官方DAST容器安全策略。我们每个目标Web服务使用一个容器,每个容器在自己的pod中运行,确保DAST容器无法相互通信。每个pod都设置了防火墙以防止出站连接,并确保只允许来自启动pod容器的代理的入站连接。这些保护措施的结合共同防止攻击者绕过认证或授权检查、冒充我们的编排DAST工具之一或发现DAST扫描结果。
DAST代理将来自DAST工具的请求代理到目标Web服务进程。透明认证模块拦截每个请求并与DAST代理验证请求。如果DAST代理确认请求来自编排DAST工具,则透明认证模块跳过标准认证和授权检查,并允许端点组件处理请求。DAST代理然后将Web服务的响应代理回DAST工具。一旦DAST工具完成扫描,DAST代理收集结果并将安全发现发送到集中管理的错误跟踪系统。然后,这些集中收集的安全发现可以由服务所有者、他们的安全保证团队和任何相关的风险管理团队处理。
结论与展望
在未来几个月,微软的Azure Edge & Platform组织将继续开发并将代理推广到第一方服务。我们还将探索利用代理架构支持更高级的DAST相关概念,如代码覆盖率监控和将DAST安全发现与易受攻击的代码块关联。我们对跨多个DAST工具收敛和去重发现以及使用AI识别误报和确定风险置信度的机会感到兴奋。
总之,这项工作强调了微软保护我们的服务和保障客户数据的承诺,突出了我们在超大规模领域的安全领导地位。我们希望它能激励微软之外的安全专业人员采用类似的策略来大规模保护他们自己的服务。
Jason Geffner
首席安全架构师