java安全学习笔记1
基础知识
反射:在运行时根据传入的字符串来决定执行哪个类的哪个方法
在Java安全中,这四个方法尤为重要
Class.forName():获取一个类(作为入口)newInstance():实例化这个类,相当于new ClassName(),因为普通方法必须是由对象才能使用getMethod():获取类中的某个函数(公共的)invoke():执行这个函数
获取Class对象的三种方式:
obj.getclass():需要手里有了一个对象实例,如"abc".getClass()Test.class: 代码编译时就需要引入这个类(静态的)Class.forName("类名"):只需知道类的名字,就能获取类
关于Class.forName的一个误区
1. forName 的重载Class.forName("String") 实际上是调用了:Class.forName("String", true, currentLoader)
这里的 true 代表参数 initialize(是否初始化)。
2. 初始化的真正含义
很多人以为“初始化”等于“执行构造函数”,这是错的。
文档通过 TrainPrint 类的实验证明了执行顺序:
static {}** (静态代码块):在类初始化**时执行(即Class.forName时)。{}** (构造代码块):在实例化**对象时执行(即newInstance时)。public TrainPrint()** (构造函数):在实例化**对象时执行,且在{}之后。
结论:
当调用 Class.forName("某各类") 时,不需要实例化该对象(不需要 newInstance),该类中的 static {} 静态代码块就会被立即执行!
重载:
Java中同一个方法,但是接收的参数不一样。如吃饭:
- 吃饭(无参数):去食堂随便吃点
- 吃饭(苹果):我要吃苹果
- 吃饭(牛排+刀叉):我要用刀叉吃牛排
而为了方便记忆,你不用去记eatrice,eatapple,eatsteak,只用记eat就行,Java会根据你传的参数自动判断
比如Runtime类的exec方法
1 | |
为什么要在反射中提重载,因为在通过getMethod拿方法时,需要精确指定参数类型,来告诉Java你要的哪个版本。
1 | |
内部类:
1 | |
在编译成.class文件之后,Java会生成C1.class和C1$C2.class这2个文件,使用$作为一个连接符。
在反射调用(Class.forName)时,我们不能直接搜C2,也不能搜C1.C2,我们要使用编译后的真实名字Class.forName("com.example.C1$C2")
反射
getMethod
获得类以后,我们可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法。`class.newInstance()`的作用就是调用这个类的无参构造函数,这个比较好理解。不过,我们有时候 在写漏洞利用方法的时候,会发现使用 newInstance 总是不成功,这时候原因可能是:1. 你使用的类没有无参构造函数
2. 你使用的类构造函数是私有的
使用getMethod来通过Runtime类的exec执行命令
Runtime是一个单例模式,他的构造函数是私有的,这意味着我们不能直接用clazz.newInstance(),因为构造函数不让用,只能通过静态方法getRuntime(),让它把造好的唯一对象给你
1 | |
再说到exec方法,exec方法是public,不是static,这意味着我们不能用Runtime来执行,要用一个Runtime的对象
1 | |
那我们的思路就明白了Class.forName拿到clazz ->getMethod拿到getRuntime方法->invoke执行getRuntime得到一个对象实例->getMethod拿到exec方法->invoke对对象实例执行exec方法
1 | |
写成一行就是
1 | |
getContructor
一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类?可以使用getConstructor。与getMethod类似,getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。
比如另外一种执行命令的方式<font style="color:rgb(51,51,51);">ProcessBuilder</font>,通过反射获取其构造函数,然后调用start来执行命令
1 | |
但是在漏洞环境里面,通常不支持这种写法,或者没引入ProcessBuilder这个类,没法强转,我们需要这样写
1 | |
分开写就是这样
1 | |
ProcessBuilder有两个构造函数:
<font style="color:rgb(51,51,51);">public ProcessBuilder(List<String> command) </font><font style="color:rgb(51,51,51);">public ProcessBuilder(String... command)</font>
如果我们要用第二个可变长参数(...表示可变长参数),Java在编译时,会把可变长参数编译成一个数组,所以String[] names和String...names是等价的。所以在反射中,将String[].class传给getConstructor即可获取第二种构造函数。由于newInstance本身接受的参数也是一个数组,二者一叠加,就变成了一个二维数组。举例来说:
- ProcessBuilder 说:“我要一箱苹果(数组)。”
- newInstance 说:“所有要传给别人的东西,都必须装在箱子里给我,我会把箱子拆了再给别人。”
如果你直接给 newInstance “一箱苹果”:
newInstance 拆开箱子,拿出一个“苹果”给 ProcessBuilder。
ProcessBuilder 怒了:“我要的是一箱,你给我一个?”
所以你必须给 newInstance “一个装了一箱苹果的箱子”(二维数组):
newInstance 拆开外面的大箱子,拿出里面的“一箱苹果”给 ProcessBuilder。
ProcessBuilder 开心了。
payload如下
1 | |
写成反射就是
1 | |
getDeclared
为了执行一个私有的方法或者构造方法,我们需要用到getDeclared系列的方法。比起getMethod来说:getMethod系列方法获取的是当前类中所有公共方法,包括从父类继承的方法getDeclared系列方法获取当前类中声明的方法,写在这个类里的,包括私有的方法,但是不包含父类里继承的
因为私有方法或者私有构造函数受到保护,我们需要通过setAccessible(True)来把这个锁给砸开。
1 | |