点击蓝字
关注我们
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
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();
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();0
payload
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();1
完整版本看起来可能有一点麻烦,我们把代码拆解开只保留他的核心
当我们运行之后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中导入依赖
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();2
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
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();3
我们发现该类的构造方法需要两个参数,一个是class的类对象,另一个为map对象
构造payload
当我们将map的key和traget中的value名相同时候,memberTypes.get(name)其中的name就是map的key值,如果key值为vaulue就说明能获取到东西,不为空了我们的方法就可以正常执行
其实不光target注解有,像Retention其实里面也有内容,只要把Key值改成相同的都是可以通过判断
最终payload
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();4
流程分析之LazyMap链
LazyMap链是ysoserial中用到的链,其中用到了动态代理的知识
在transformedMap中查找谁能调用transform方法时,其实除了checkSetValue可以外,LazyMap中的get()方法也可以调用
当map中没有key值的时候,会触发tranform方法进行回调,如果factory是transformerChain那么就可以执行命令,接下来要做的就是如何执行到这个get方法
我们发现AnnotationInvocationHandler类中的invoke方法中可以执行get方法
我们发现AnnotationInvocationHandler中实现了InvocationHandler于是可以使用动态代理的方法调用invoke方法
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();5
Proxy.newProxyInstance三个参数:
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
动态代理方法在执行时,会调用h里面的invoke方法去执行
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();6
这时第一个参数已经无所谓了,因为我们走的是LazyMap这条链了
ProcessBuilder calc = new ProcessBuilder("calc");calc.start();7
这篇文章从反射的命令执行到cc1,其中cc1对新手理解起来可能不友好,需要自己多理解理解才能参悟里面的本质,需要多看多审源码。
往期推荐
还没有评论,来说两句吧...