读p牛的java安全漫谈笔记
01 反射篇
p牛对于动态特性的解释
⼀段代码,改变其中的变量,将会导致 这段代码产⽣功能性的变化,我称之为动态特性
比如php中的一句话
1 | eval($_POST[1]); |
就可以通过post传入1执行其他的功能,在没传入值之前你也不知道他要干啥
下面看一下java的
1 | public void execute(String className, String methodName) throws Exception { |
获取类 的⽅法: forName
实例化类对象的⽅法(调用该类的无参构造函数): newInstance
获取 函数的⽅法: getMethod
getMethod 的作用是通过反射获取一个类的某个特定的公有方法
执⾏函数的⽅法: invoke
这里感觉有点像python的获取基类的那种感觉,在另一篇先知社区的文章里,把forName的方法叫做获取类对象
有三种获取类对象的方法
- class.forName(“”)
obj.class
如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接 拿它的 class 属性即可。这个⽅法其实不属于反射
obj.getClass()
有某个类实例后对象,可以通过这种方法
1
2
3
4Integer i=1;
Class runtimeClass=i.getClass();
System.out.println(runtimeClass);
//class java.lang.Integer然后再通过forName方法就可以获取执行命令的
java.lang.Runtime
类1
2
3
4
5Integer i=1;
Class runtimeClass=i.getClass().forName("java.lang.Runtime");
System.out.println(runtimeClass);
//class java.lang.Runtime
forName(“”)
forName有两个函数重载:
- Class forName(String name)
- Class forName(String name, boolean initialize, ClassLoader loader)
ClassLoader
它就是⼀个“加载器”,告诉Java虚拟机如何加载这个类。关于这个点,后⾯还 有很多有趣的漏洞利⽤⽅法,这⾥先不展开说了。Java默认的 ClassLoader
就是根据类名来加载类, 这个类名是类完整路径,如 java.lang.Runtime
。
1 | public class TrainPrint { |
⾸先调⽤的是 static {} ,其次是 {} ,最后是构造函数。
其中, static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯,
但在当前构造函数内容的前⾯。
所以说, forName
中的 initialize=true
其实就是告诉Java虚拟机是否执⾏”类初始化“。
那么,假设我们有如下函数,其中函数的参数name可控:
我们就可以编写⼀个恶意类,将恶意代码放置在 static {} 中,从⽽执⾏
1 | Person p = new Person("zhangsan",20); |
1 | /** |
02.反射篇(2)
在看这篇之前先学习一下链式编程,如果了解直接跳过,不了解看一下方便后面理解
java链式编程
参考:https://www.jianshu.com/p/a8c51f220189
链式编程比较简单,只要return this
即可具有相应的编程模式
只介绍简单的链式编程
简单链式编程
使用return this
可以实现链式
1 | public class Book { |
调用
1 | public static void main(String[] args) { |
反射
反射1提到可以通过forName
来获取类,获得类以后,我们可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法
class.newInstance()
可以调用类的无参构造函数,但是很多时候这样调用并不能成功,可能的原因
- 你使用的类没有无参构造函数
- 使用的类,它的构造函数是私有的
最后payload
用的比较多的就是java.lang.Runtime
但是不能通过newInstance
利用
1 | Class clazz = Class.forName("java.lang.Runtime"); |
原因是 Runtime 类的构造方法是私有的,构造函数私有主要是单例模式会使用
例如jdbc连接数据库只希望初始化建立一次,就可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来获取
1 | public class TrainDB { |
这样,只有类初始化的时候会执行一次构造函数,后面只能通过 getInstance 获取这个对象,避免建立多个数据库连接
Runtime类也是单例模式,所以无法通过getInstance
获取对象,只能通过 Runtime.getRuntime()
来获取到 Runtime
对象
1 | Class clazz = Class.forName("java.lang.Runtime"); |
getMethod
getMethod
是通过反射获取一个类的公有方法,但是java通过重载可以出现几个相同函数名但是不同参数的函数,无法通过函数名确定函数,所以调用 getMethod 的时候,需要传给他你需要获取的函数的参数类型列表
invoke
invoke
作用是执行方法,它的第一个参数是:
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类
正常执行方法是 [1].method([2], [3], [4]...)
,其实在反射里就是method.invoke([1], [2], [3], [4]...)
[类].静态方法(参数)
===》静态方法.([类],参数)
[对象].普通方法(参数)
===》普通方法.([对象],参数)
因为需要使用的exec
方法是普通方法,需要得到Runtime
对象,但是Runtime
构造函数私有(通过类内的构造函数得到类对象),所以通过静态方法getRuntime
来获取类对象
上面简写的payload可以拆分为
1 | Class clazz = Class.forName("java.lang.Runtime"); |
03.反射篇(3)
反射2解决的是类里面构造函数私有,利用该类中单例模式静态方法实现方法的调用
这篇解决这两个问题
- 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?
- 如果一个方法或构造方法是私有方法,我们是否能执行它呢?
问题一
想要解决第一个问题需要用到一个新的反射方法getConstructor
和反射2用的Runtime
类不同的是
Runtime
类有私有无参构造函,找到静态函数getRuntime
获取类对象
getConstructor
接收的参数是构造函数列表类型,因为构造函数也支持重载,所以必须用参数列表类型才能唯一确定一个构造函数
获取构造函数后使用newInstance
执行
另一种常用的执行命令的方式ProcessBuilder
使用反射来获取其构造函数,然后调用start()
来执行命令
1 | Class clazz = Class.forName("java.lang.ProcessBuilder"); |
ProcessBuilder
有两个构造函数
1 | public ProcessBuilder(List<String> command) |
上面payload用到的是第一个形式的构造函数,所以在getConstructor()
时候传入list.class
前面这个Payload用到了Java里的强制类型转换,有时候我们利用漏洞的时候(在表达式上下文中)是没有这种语法的。所以,我们仍需利用反射用invoke方法来完成这一步
1 | clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))); |
通过getMethod("start")
获取到start
方法,然后 invoke
执行, invoke 的第一个参数就是ProcessBuilder Object
了
第二个构造函数public ProcessBuilder(String... command)
参数是可变长参数
对于可变长参数,Java其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价的
因此我们只要传个数组就可以了
将字符串数组的类 String[].class 传给 getConstructor ,获取 ProcessBuilder 的第二种构造函数
1 | Class clazz = Class.forName("java.lang.ProcessBuilder"); |
在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下
1 | ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start(); |
改成反射来写
1 | clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})); |
问题二
一个方法或构造方法是私有方法,涉及到 getDeclared 系列的反射了,与普通的 getMethod 、 getConstructor 区别是:
getMethod
系列方法获取的是当前类中所有公共方法,包括从父类继承的方法getDeclaredMethod
系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
getDeclaredMethod
的具体用法和getMethod
类似,getDeclaredConstructor
的具体用法和getConstructor
类似
getDeclaredMethod遇到的问题
之前在反射2中的Runtime
类的构造方法就是私有的,这里基于可以用getDeclaredMethod
来解决
这里有个问题用getDeclaredMethods
方法并没有获取到私有的构造方法
1 | Method[] me = clazz.getDeclaredMethods(); |
这里java基础太弱了,没有找到原因,哪个大表哥知道可以联系我呀
Runtime
这个类的构造函数是私有的,我们需要用 Runtime.getRuntime()
来获取对象。其实现在我们也可以直接用 getDeclaredConstructor
来获取这个私有的构造方法来实例化对象,进而执行命令:
1 | Class clazz = Class.forName("java.lang.Runtime"); |
这里使用了一个方法setAccessible
,这个是必须的。我们在获取到一个私有方法后,必须用setAccessible
修改它的作用域,否则仍然不能调用