反序列化往往从反射说起。
JAVA反射是在JMV运行时才动态加载类或调用方法/访问属性,而不需要事先(写代码时或编译期)知道运行的对象是谁、
反射
- 有反射既有正射
- 反射是大部分语言不可获取的一部分,对象可以通过反射获取他的类,类可以通过反射拿到所有的方法(包括私有方法),拿到方法既可以调用。即通过反射来完成方法的调用
反射用途: 如IDEA,当我们输入一个对象或类并想调用它的属性或方法时,按.编译器就会自动列出它的属性和方法,此时用到反射。
动态特性
- 一段代码,改变其中的变量,将会导致这段代码产生功能性改变,称为动态特性。
反射步骤
- 获取反射中的class对象(获取类
- 使用Class.forName静态方法:Class sqy = Class.forName(“com.package.Apple”);
forName 中的 initialize=true 其实就是告诉Java虚拟机是否执⾏行行”类初始化“。
- 使用某个类的.Class方法:Class sqy = com.package.Apple.class;
- 使用类实例化对象的getClass()方法:Class sqy = new.Apple().getClass();
- ClassLoader.getSystemClassLoader().loadClass(“java.lang.Runtime”)类似的利用类加载机制,获取class对象
- 通过反射创建类对象(实例化类对象
- 通过class对象的newInstance()方法 对无参数的类进行实例化
- 通过Constructor(构造)对象的newInstance()方法 对有参数的构造方法进行实例化 实则差别不大
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法
- 通过反射获取类的属性,方法和构造器(获取函数属性等getMethod
- 通过Class对象的getFields()方法可以获取Class类的属性,但无法获取私有属性
- Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性
- 与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
- 反射源码解析(执行函数invoke
反射机制如何利用
public class TrainPrint {
{
System.out.printf("Empty block initial %s\n", this.getClass());
}
static {
System.out.printf("Static initial %s\n", TrainPrint.class);
}
public TrainPrint() {
System.out.printf("Initial %s\n", this.getClass());
}
}
上述三个“初始化”方法有什么区别,且执行顺序如何,在安全中有什么价值?
由new实例化对象可知(
首先调用的是static{}方法,其次是{},最后是构造方法constructor()。
其中static{}就是在“类初始化”时调用的,而{}中的代码会放在构造函数的super()后面,但是在当前构造函数的前面。
如何通过构造反序列化利用链,即是通过反射,依赖于调用类实例化时自动调用的方法,来执行反序列化攻击,总结如下。
利用类加载器ClassLoader.getSystemClassLoader().loadClass(“”)也会执行三个
那么,假设我们有如下函数,其中函数的参数name可控:
public void ref(String name) throws Exception {
Class.forName(name);
}
我们就可以编写⼀个恶意类,将恶意代码放置在 static {} 中,从⽽执行:
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
在正常系统下,除了系统安装的类以外,若我们想拿到一个类,就必须得先import才能使用,而使用forName就不用,因为会自动加载static代码块,对于攻击来说十分有利,可以加载任意类。
反射question
Class.newInstance()调用失败原因:
class.newInstance() 的作用就是调用这个类的无参构造函数,这个比较好理解。不过,我们有时候
在写漏洞利用方法的时候,会发现使用 newInstance 总是不成功,这时候原因可能是:
- 使用的类没有无参构造函数
- 使用的类构造函数是私有的(单例模式
反射调用单例模式类
类比于Runtime类,此类是私有的,若直接实例化则会报错:
分析一下Runtime类的写法:
如何调用实例化此类从而使用它的方法呢?
通过调用public的static进行类的实例化再进行exec方法调用
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazzz), "calc.exe");
//invoke调用静态方法,第一个参数可null也可为声明的class对象
}
通过修改私有构造器Runtime类的属性,再进行方法调用
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.Runtime");
Constructor x = clazz.getDeclaredConstructor(); //获取私有构造器
x.setAccessible(true); //将private改为public
x.newInstance(); //实例化
clazz.getDeclaredMethod("exec", String.class).invoke(x.newInstance(), "calc.exe");
}
getMethod()
- 作用是通过反射获取一个类的所有的公有方法,包括从父类继承的方法
getDeclaredMethod()
- 当前类中所有声明的方法,包括私有方法(但不包含从父类继承来的
setAccessiable()
- 修改当前类的作用域
invoke的作用是执行方法,它的第一个参数:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类/null
getConstructor()
- 接收的参数类型是构造函数列表类型,因为构造方法支持重载,所以必须用参数列表类型才能唯一确定一个构造函数。
可变长参数构造函数反射执行
可变长参数,当你定义函数的时候不确定参数数量的时候,可以用
...
这样的语法来表示这个函数的参数是可变的(也可编译成一个数组是等价的喔-public ProcessBuilder(String... command)
对于反射来说,如果获取的目标函数里包含可变长参数,我们可认为其是数组。