反序列化往往从反射说起。
JAVA反射是在JMV运行时才动态加载类或调用方法/访问属性,而不需要事先(写代码时或编译期)知道运行的对象是谁、

反射

  • 有反射既有正射
  • 反射是大部分语言不可获取的一部分,对象可以通过反射获取他的类,类可以通过反射拿到所有的方法(包括私有方法),拿到方法既可以调用。即通过反射来完成方法的调用

    反射用途: 如IDEA,当我们输入一个对象或类并想调用它的属性或方法时,按.编译器就会自动列出它的属性和方法,此时用到反射。

image.png

动态特性

  • 一段代码,改变其中的变量,将会导致这段代码产生功能性改变,称为动态特性。

反射步骤

  • 获取反射中的class对象(获取类
  1. 使用Class.forName静态方法:Class sqy = Class.forName(“com.package.Apple”);

image.png

forName 中的 initialize=true 其实就是告诉Java虚拟机是否执⾏行行”类初始化“。

  1. 使用某个类的.Class方法:Class sqy = com.package.Apple.class;
  2. 使用类实例化对象的getClass()方法:Class sqy = new.Apple().getClass();

image.png

  1. ClassLoader.getSystemClassLoader().loadClass(“java.lang.Runtime”)类似的利用类加载机制,获取class对象


  • 通过反射创建类对象(实例化类对象
  1. 通过class对象的newInstance()方法 对无参数的类进行实例化
  2. 通过Constructor(构造)对象的newInstance()方法 对有参数的构造方法进行实例化 实则差别不大

    通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法

  • 通过反射获取类的属性,方法和构造器(获取函数属性等getMethod
  1. 通过Class对象的getFields()方法可以获取Class类的属性,但无法获取私有属性
  2. Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性
  3. 与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
  • 反射源码解析(执行函数invoke

反射机制如何利用

  1. public class TrainPrint {
  2. {
  3. System.out.printf("Empty block initial %s\n", this.getClass());
  4. }
  5. static {
  6. System.out.printf("Static initial %s\n", TrainPrint.class);
  7. }
  8. public TrainPrint() {
  9. System.out.printf("Initial %s\n", this.getClass());
  10. }
  11. }

上述三个“初始化”方法有什么区别,且执行顺序如何,在安全中有什么价值?

image.png
由new实例化对象可知(
首先调用的是static{}方法,其次是{},最后是构造方法constructor()。
其中static{}就是在“类初始化”时调用的,而{}中的代码会放在构造函数的super()后面,但是在当前构造函数的前面。

如何通过构造反序列化利用链,即是通过反射,依赖于调用类实例化时自动调用的方法,来执行反序列化攻击,总结如下。

image.png
利用类加载器ClassLoader.getSystemClassLoader().loadClass(“”)也会执行三个

那么,假设我们有如下函数,其中函数的参数name可控:

  1. public void ref(String name) throws Exception {
  2. Class.forName(name);
  3. }

我们就可以编写⼀个恶意类,将恶意代码放置在 static {} 中,从⽽执行:

  1. import java.lang.Runtime;
  2. import java.lang.Process;
  3. public class TouchFile {
  4. static {
  5. try {
  6. Runtime rt = Runtime.getRuntime();
  7. String[] commands = {"touch", "/tmp/success"};
  8. Process pc = rt.exec(commands);
  9. pc.waitFor();
  10. } catch (Exception e) {
  11. // do nothing
  12. }
  13. }
  14. }

在正常系统下,除了系统安装的类以外,若我们想拿到一个类,就必须得先import才能使用,而使用forName就不用,因为会自动加载static代码块,对于攻击来说十分有利,可以加载任意类。


反射question

Class.newInstance()调用失败原因:

class.newInstance() 的作用就是调用这个类的无参构造函数,这个比较好理解。不过,我们有时候
在写漏洞利用方法的时候,会发现使用 newInstance 总是不成功,这时候原因可能是:

  • 使用的类没有无参构造函数
  • 使用的类构造函数是私有的(单例模式

反射调用单例模式类

类比于Runtime类,此类是私有的,若直接实例化则会报错: image.png

分析一下Runtime类的写法:
image.png
如何调用实例化此类从而使用它的方法呢?

  • 通过调用public的static进行类的实例化再进行exec方法调用

    1. public static void main(String[] args) throws Exception {
    2. Class clazz = Class.forName("java.lang.Runtime");
    3. clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazzz), "calc.exe");
    4. //invoke调用静态方法,第一个参数可null也可为声明的class对象
    5. }
  • 通过修改私有构造器Runtime类的属性,再进行方法调用

    1. public static void main(String[] args) throws Exception {
    2. Class clazz = Class.forName("java.lang.Runtime");
    3. Constructor x = clazz.getDeclaredConstructor(); //获取私有构造器
    4. x.setAccessible(true); //将private改为public
    5. x.newInstance(); //实例化
    6. clazz.getDeclaredMethod("exec", String.class).invoke(x.newInstance(), "calc.exe");
    7. }

    getMethod()

    • 作用是通过反射获取一个类的所有的公有方法,包括从父类继承的方法

    getDeclaredMethod()

    • 当前类中所有声明的方法,包括私有方法(但不包含从父类继承来的

    setAccessiable()

    • 修改当前类的作用域

    invoke的作用是执行方法,它的第一个参数:

    • 如果这个方法是一个普通方法,那么第一个参数是类对象
    • 如果这个方法是一个静态方法,那么第一个参数是类/null

    getConstructor()

    • 接收的参数类型是构造函数列表类型,因为构造方法支持重载,所以必须用参数列表类型才能唯一确定一个构造函数。

可变长参数构造函数反射执行

可变长参数,当你定义函数的时候不确定参数数量的时候,可以用...这样的语法来表示这个函数的参数是可变的(也可编译成一个数组是等价的喔- public ProcessBuilder(String... command)

对于反射来说,如果获取的目标函数里包含可变长参数,我们可认为其是数组。
image.png