Java反序列化漏洞学习实践二-Java的反射机制(Java Reflection)

admin 2022年1月6日01:14:02评论209 views字数 7537阅读25分7秒阅读模式

学习Java的反射机制是为了理解Apache Commons Collections中的反序列化漏洞做准备的。

0x0、基础

Java反射机制

  • 指的是可以于运行时加载,探知和使用编译期间完全未知的类.
  • 程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性;
  • 加载完类之后, 在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射.
  • 每个类被加载进入内存之后,系统就会为该类生成一个对应的java.lang.Class对象,通过该Class对象就可以访问到JVM中的这个类.

Class对象的获取方法

  • 实例对象的getClass()方法;
  • 类的.class(最安全/性能最好)属性;(如demo代码和下图)
  • 运用Class.forName(String className)动态加载类,className需要是类的全限定名(最常用).

注意,有一点很有趣,使用功能”.class”来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象

img

0x1、通过反射方法调用函数

该demo主要实践学习使用反射方法来调用类中的函数。

``

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package 反序列化2;

import java.lang.reflect.Method;




public class reflectionTest {
public static void main(String[] args) {
try{
//Class 获取类的方法一:事例化对象的getclass()方法;
User testObject = new User("zhangsan",19);
Class Method1Class = testObject.getClass();

//Class获取类的方法二:类的.Class(最安全/性能最好)属性;有点类似python的getattr()。java中每个类型都有class属性。
Class method2Class = User.class;

//Class对象的获取方法三:运用Class.forName(String className)动态加载类,className需要是类的全限定名(最常用)
//这种方法也最容易理解,通过类名(jar包中的完整namespace)就可以调用其中方法,也最符合我们需要的使用场景
//j2eeScan burp 插件就使用了这种反射机制
String path = "反序列化2.User";
Class method3Class = Class.forName(path);

Method[] methods = method3Class.getMethods();

//通过类的class属性获取对应的Class类的对象,通过这个Class类的对象获取test类中的方法集合

/* String name = Method3Class.getName();
* int modifiers = Method3Class.getModifiers();
* .....还有很多方法
* 也就是说,对于一个任意的可以访问到的类,我们都能够通过以上这些方法来知道它的所有的方法和属性;
* 知道了它的方法和属性,就可以调用这些方法和属性。
*/

//调用User类中的方法

for(Method method: methods) {
if(method.getName().equals("getName")) {
System.out.println("method = " + method.getName());
Class[] parameterTypes = method.getParameterTypes(); //获取方法的参数
Class returnType = method.getReturnType(); //获取方法的返回类型
try {
User user = (User)method3Class.newInstance();
Object x = method.invoke(user); //user.getName;
//Object x = method.invoke(new test(1), 666);
//new关键字能调用任何构造方法,newInstance()只能调用无参构造方法。但反射的场景中是不应该有机会使用new关键词的。
System.out.println(x);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}

Method method1 = method3Class.getMethod("setName", String.class);
User user1 = (User)method3Class.getConstructor(String.class,Integer.class).newInstance("lisi",19);
//调用自定义构造器的方法
Object x = method1.invoke(user1, "李四");//第一个参数是类的对象。第二个参数是函数的参数
System.out.println(user1.getName());
}catch(Exception e) {
e.printStackTrace();
}


}

}

class User{
private Integer age;
private String name;

public User() {};
public User(String name,Integer age) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}



}

img

0x2、通过反射方法弹个计算器

step1中,我们通过重写readObject方法,直接在里面使用Runtime.getRuntime().exec(“calc.exe”)来执行代码。现在需要改造一下,使用反弹方法来实现,成功调试的代码如下。11

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package 反序列化2;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;


public class reflectionTest2 implements Serializable{
//创建一个简单的可被序列化的类,它的实例化后的对象就是可以被序列化的
private static final long serialVersionUID = 1L;
private int n;
public reflectionTest2(int n) {
this.n=n;
}
@Override
public String toString() {
return "reflectionTest2 [n=" + n + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + ", toString()="
+ super.toString() + "]";
}
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
try {
Method method = java.lang.Runtime.class.getMethod("exec", String.class);
Object result = method.invoke(Runtime.getRuntime(), "open /System/Applications/Calculator.app");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
public static void main(String[] args) {
reflectionTest2 x = new reflectionTest2(5);
operation.ser(x);
operation.deser();
x.toString();
}


}



class operation{
public static void ser(Object obj) {
//序列化操作,写操作
try {
ObjectOutputStream ooStream = new ObjectOutputStream(new FileOutputStream("object.obj"));
//ObjectOutputStream能把Object输出成Byte流
ooStream.writeObject(obj);
ooStream.flush();
ooStream.close();

} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
public static void deser() {
//反序列化,读取操作
try {
ObjectInputStream iiStream = new ObjectInputStream(new FileInputStream("object.obj"));
Object xObject = iiStream.readObject();
System.out.println(xObject);
iiStream.close();
} catch (FileNotFoundException e) {
// TODO: handle exception
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

执行结果:

img

0x3、通过setAccessible访问私有属性和函数

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package 反序列化2;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/*
* 测试setAccessible方法,可以通过将它设置为true--setAccessible(true) 来访问private属性和函数。
* 而且可以提高程序的执行效率,因为减少了安全检查。
*/
public class reflectionTest3 {

public static void main(String[] args){
try {
String path = "反序列化2.User3";
Class clazz = Class.forName(path);

//Method method = clazz.getMethod("setName",String.class);
//getMethod只能获取public的方法,private的方法需要使用getDeclaredMethod来获取,并且设置setAccessible(true)才可以调用访问。
//参数属性也是一样。
Method method = clazz.getDeclaredMethod("setName", String.class);
method.setAccessible(true);

//Constructor strut = clazz.getConstructor(String.class,Integer.class);
//getConstructor只能获取public的构造方法
Constructor strut = clazz.getDeclaredConstructor(String.class,Integer.class);
strut.setAccessible(true);
User3 user = (User3)strut.newInstance("bit4",19);
//调用自定义构造器的方法
Object x = method.invoke(user,"比特");//第一个参数是类的对象。第二参数是函数的参数
System.out.println(user.getName());


} catch (Exception e1) {
e1.printStackTrace();
}
}
}

class User3{

private Integer age;
private String name;

private User3() {}

private User3(String name,Integer age){ //构造函数,初始化时执行
this.age = age;
this.name = name;
}


private Integer getAge() {
return age;
}

private void setAge(Integer age) {
this.age = age;
}

public String getName() {
return name;
}

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

img

0x4、思考总结

程序的两大根本:变量与函数

所以总的来说,要想控制程序实现命令执行,有2个方向

1.控制代码、函数:就像命名注入等注入类漏洞一样数据被当作了代码执行;或者和上面的demo代码一样重写readObject,加入自定义的代码(当然这种场景基本不存在,任意文件上传和执行勉强算是属于这种情况)。

2.控制输入、数据、变量:利用代码中已有的函数和逻辑,通过改变输入内容的形态实现流程的控制(不同的输入会走不同的逻辑流程,执行不同代码块中的代码)。

对于java反序列化漏洞,属于控制数据输入,它有2个基本点必须要满足

1.有一个可序列化的类,并且该类是重写了readObject()方法的(由于不存在代码注入,只能查找已有代码逻辑中是否有这样的类)。

2.在重写的readObject()方法的逻辑中有 method.invoke函数出现,而且参数可控。

再稍作抽象:

1.有一个可序列化的类,并且该类是重写了readObject()方法的,除了默认的对象读取外,还有其他处理逻辑。(主线流程,反序列化漏洞都是这个主线逻辑流程,这是反序列化漏洞的入口点。)

2..在重写的readObject()方法的逻辑中有 直接或间接使用类似method.invoke这种可以执行调用任意方法的函数,而且参数可控。(是否还有其他函数可以达到相同的目的呢?)

Apache Commons Collections 3.x 版本的 Gadget 构造过程 就是典型的例子。它的调用链如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator$MapEntry.setValue()
TransformedMap.checkSetValue()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections <= 3.2.1
*/

参考:

http://blog.csdn.net/liujiahan629629/article/details/18013523

http://blog.csdn.net/zjf280441589/article/details/50453776

http://ifeve.com/java-reflection/

Apache Commons Collections 3.x 版本的 Gadget调用链http://blog.knownsec.com/2015/12/untrusted-deserialization-exploit-with-java/)

FROM :ol4three.com | Author:ol4three

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:14:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java反序列化漏洞学习实践二-Java的反射机制(Java Reflection)http://cn-sec.com/archives/721501.html

发表评论

匿名网友 填写信息