Java反序列化基础篇-02-Java反射与URLDNS链分析
0x00 前言
Java反序列化基础篇的第二篇文章。
Java反序列化基础篇-02-Java反射与URLDNS链分析 | Drunkbaby’s Blog
0x01 回顾
攻击思路
在上篇文章,我们大致了解了Java反序列化的攻击思路:
首先要具有
Serializable
接口,可以被序列化喝反序列化入口类(最好重写
readObject
方法)应该满足三个条件:- 该
readObject
方法接收的参数类型宽泛,比如Object
类型 - 该
readObject
方法可以调用常见的函数,比如Object
类下的hashCode
方法 - 该入口类最好
jdk
自带
- 该
找到了入口类,还要找到调用链(
gadget chain
)最后要找到执行类(
sink
)用来攻击
谈起反射
在上文,我们遇到了一个问题,给出题干:
不难看到在反序列化之前执行了hashmap.put
方法,跟进该方法:
对比hashmap
的readObject
方法的一段代码:
二者一模一样的代码,对URLDNS
有很大的影响:
在序列化hashmap
对象之前,hash(key)
方法会执行一遍,也就是执行一遍URL
对象的hashCode
方法:发送DNS
请求并设置URL
对象的hashCode
属性不为-1——到反序列化执行readObject
方法时没法发送DNS请求。
所以我们希望在序列化前设置URL
对象的hashCode
属性不为-1,要序列化时再设置该属性为-1,这就需要用到反射了。
0x03 反射
Java 安全可以从反序列化说起,而反序列化可以从反射说起。
反射概述
两种语言类型
Java本身是一种静态语言,在编译时就要进行类型检查,这通常要求我们显示声明变量的类型。静态语言的优势在于其运行性能较好,减少运行时的错误。劣势在于开发周期更长。
与之相反,PHP则属于动态语言,具有很多灵活的语言特性。PHP在运行时进行类型检查,变量的类型在运行时确定,通常不要求显式声明变量类型。动态语言的优势在于语法更加灵活,适合快速开发和原型涉及。劣势在于可能要面临更多的运行时错误
大家可以对比一下,PHP有很多的语法漏洞可以攻击,而Java的语法漏洞则非常少。
为何需要反射
每一种机制的产生都有其目的性或者说作用,Java的反射机制的作用即是:让Java具有动态性。
这是因为编程语言不能完全分裂成静态语言和动态语言,现实的复杂性决定了一个好的编程语言最好要具备上述两种语言类型的优点。
正射 · 反射
欲知逆,先知正。正射与反射的关键区别在于二者对类和对象的访问和操作方式
- 正射在编译时确定方法调用或属性访问的具体类型,比如直接通过类创建一个对象后使用该类的方法,代码逻辑如下:
- 反射则是指在运行时动态获取类的信息(如方法、字段等)并进行操作,先看看如下代码:
反射操作需要用到Class对象,但是Class对象只有在运行时才能被Java
虚拟机(JVM
)创建,也就是说整个反射机制都依赖于程序运行阶段而非之前的编译阶段。
那么这里的Class类型和对象是什么呢?理解反射的第一步就是理解Class是什么。
Class
读者初写Java程序时不难发现Java程序在编译时会生成**.class**文件,Person.class
文件如下:
1 | // |
Person.class
就是Class
类型的对象,当 JVM
加载一个类时,它会创建一个 Class
对象来表示这个类,这个 Class
对象包含了关于类的信息,包括类名、方法、字段、构造函数等。
所以反射就是操作Class对象
机制组成
反射机制相关操作一般位于java.lang.reflect包中,而java
反射机制组成需要重点注意以下的类或包:
类/包 | 解释 |
---|---|
java.lang.Class | 类对象 |
java.lang.reflect.Constructor | 类的构造器对象 |
java.lang.reflect.Field | 类的属性对象 |
java.lang.reflect.Method | 类的方法对象 |
0x04 使用
相关方法
反射机制允许我们了解并使用某个类,相关操作有:
方法 | 作用 |
---|---|
forName |
通过类名称获取该类 |
newInstance |
实例化类对象 |
getMethod |
获取该类的方法 |
invoke |
执行该类的方法 |
具体流程
(1)首先要获得Class对象
对于常规的类,也就是构造器是公有的类,可以直接实例化类对象:
但是我们尝试直接实例化Class对象,会报错:
跟进Class的构造器:
如上所说,如果构造器是私有的,则不能直接实例化类对象。
下面介绍几种获得Class对象的方法:
第一种:利用已实例化对象的getClass()方法
需要一个已经实例化的对象,通过该类的getClass方法获得Class对象。利用方式如下:
这里多说两点:
- 该方法返回值类型是
Class<?>
,也就是返回一个Class
对象 - 每个类都继承Object类,Object类定义了
getClass
方法,这意味着每个类都有该方法
第二种:使用类的 .class 方法
如果已经加载了某个类,只想获取它的java.lang.Class
对象,那么就直接拿它的 class
属性即可。利用方式如下:
第三种:Class.forName(String className):动态加载类
如果已知某个类的类名,可以通过Class
的forname方法来获取。利用方式如下:
三种方法的使用
用一段代码来展示利用上述的三种方法获取Class
对象:
1 | public class LocalClassTest { |
(2)获得成员变量 Field
获取成员变量Field位于 java.lang.reflect.Field
包中,注意下面四种方法:
方法 | 作用 |
---|---|
Field getField(String name) |
获取指定名称的 public 修饰的成员变量 |
Field[] getFields() |
获取所有 public 修饰的成员变量 |
Field getDeclaredField(String name) |
获取指定的成员变量 |
Field[] getDeclaredFields() |
获取所有的成员变量,不考虑修饰符 |
- 示例如下:
- 代码如下:
- 输出如下:
(3)获取成员方法 Method
获取成员变量Field位于 java.lang.reflect.Method
包中,与获取Field类似,有四种方法:
方法 | 作用 |
---|---|
Method getMethod(String name, 类<?>... parameterTypes) |
返回该类所声明的public方法 |
Method[] getMethods() |
获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法 |
Method getDeclaredMethod(String name, 类<?>... parameterTypes) |
返回该类所声明的所有方法 |
Method[] getDeclaredMethods() |
获取该类中的所有方法 |
不再演示,请读者自行复现。
(4)获取构造函数 Constructor
获取成员变量Field位于 java.lang.reflect.Constructor
包中,仍然是四种方法:
方法 | 作用 |
---|---|
Constructor<> getConstructor(类<?>... parameterTypes) |
匹配和参数配型相符的public构造函数 |
Constructor<?>[] getConstructors() |
只返回public构造函数 |
Constructor<> getDeclaredConstructor(类<?>... parameterTypes) |
匹配和参数配型相符的构造函数 |
Constructor<?>[] getDeclaredConstructors() |
返回所有构造函数 |
不再演示,请读者自行复现。
0x05 进阶
反射创建对象
反射创建对象,也称之为:反射之后实例化对象,过程如下:
newInstance
方法在 Java 中用于通过反射创建一个类的实例。它是java.lang.Class
类中的一个方法,允许你在不知道具体类的情况下动态地创建对象。
使用private修饰的方法和修改private修饰的成员变量
使用private
修饰的方法需要先设置权限
修改private
修饰的成员变量也同理:
这里讲讲invoke
方法,invoke
方法位于 java.lang.reflect.Method
类中,用于执行某个的对象的目标方法,一般会和 getMethod 方法配合进行调用。用法如下:
1 | public Object invoke(Object obj, Object... args) |
第一个参数为类的实例也就是对象,第二个参数为一个数组,接受目标方法的参数。
但是invoke
方法的第一个参数并不是固定的:
如果目标方法是普通方法,第一个参数就是类对象;
如果目标方法是静态方法,第一个参数就是类;
该回归到上篇文章所讲的URLDNS
了,将它完善。
0x06 完善
回顾思路
完善URLDNS,在于先使URL
对象的hashCode属性设置为非-1,再在hashmap.put
方法之后,进行序列化之前重新设置hashCode属性为-1。源码如下:
如何解决
先把hashCode属性设置成非-1,暂且设置成1:
但是这样还不够,因为hashCode属性没法自动回归成-1,所以我们还要再主动设置成-1:
如此URLDNS链便完善了!
0x07 内置
利用Java
内置的java.lang.Runtime
包实现弹出计算器,也就是getshell
:(不能直接实例化对象,因为 java.lang.Runtime
是私有的)
他人更精简的代码:
1 | Class c1 = Class.forName("java.lang.Runtime"); |
0x08 结语
我也没想到,下一篇竟然还是反射,不过会更加进阶。