- 1 序列化与反序列化基础
- 2 漏洞基本原理
- 3 Java 反射
- 4 DNSURL gadget 分析
- 1 背景介绍
- 2 CommonsCollections 1 Gadget 分析
- 3 CommonsCollections 6 Gadget 分析
- 4 CommonsCollections 2&&4 Gadget 分析
- JDK 7U21 Gadget
- 1 原理
- 2 构造
- 3 调用链
- 4 总结
- 1 Java 动态加载字节码
- 2 CommonsCollections 3 Gadget 分析
- 3 CommonsCollections 5 Gadget 分析
- 4 CommonsCollections 7 Gadget 分析
- 反序列化攻击涉及到的相关协议
- 1 RMI
- 2 JNDI
3 Java 反射
3.1 Java 反射定义
对于任意一个类,都能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。
反射是⼤多数语⾔⾥都存在的特性,对象可以通过反射获取它的类,类可以通过反射拿到所有⽅法(包括私有),拿到的⽅法可以直接调用。总之,通过反射,可以将 Java 这种静态语⾔附加上 动态特性 。
Java 语言虽然不像 PHP 那样存在许多灵活的 动态特性 ,但是通过反射,可以达到一定的效果,如下面这段代码,在传入参数值不确定的情况下,该函数的具体作用是未知的。
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
在 Java 中定义的一个类本身也是一个对象,即 java.lang.Class
类的实例,这个实例称为类对象
- 类对象表示正在运行的 Java 应用程序中的类和接口
- 类对象没有公共构造方法,由 Java 虚拟机自动构造
- 类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法
要得到类的方法和属性,首先就要得到该类对象
3.2 获取类对象
假设现在有一个 Person 类:
public class Person implements Serializable {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
要获取该类对象一般有三种方法:
class.forName("com.geekby.Person")
Person.class
new Person().getClass()
最常用的是第一种,通过一个字符串即类的全路径名就可以得到类对象。
3.3 利用类对象创建对象
与直接 new
创建对象不同,反射是先拿到类对象,然后通过类对象获取构造器对象,再通过构造器对象创建一个对象。
package com.geekby;
import java.lang.reflect.*;
public class CreateObject {
public static void main(String[] args) throws Exception {
Class PersonClass = Class.forName("com.geekby.Person");
Constructor constructor = PersonClass.getConstructor(String.class, Integer.class);
Person p = (Person)constructor.newInstance("Geekby", 24);
System.out.println(p.getName());
}
}
方法 | 说明 |
---|---|
getConstructor(Class… parameterTypes) | 获得该类中与参数类型匹配的 公有 构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class… parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
3.4 利用反射调用方法
public class CallMethod {
public static void main(String[] args) throws Exception {
Class PersonClass = Class.forName("com.geekby.Person");
Constructor constructor = PersonClass.getConstructor(String.class, Integer.class);
Person p = (Person)constructor.newInstance("Geekby", 24);
Method m = PersonClass.getDeclaredMethod("setName", String.class);
m.invoke(p, "newGeekby");
System.out.println(p.getName());
}
}
方法 | 说明 |
---|---|
getMethod(String name, Class… parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class… parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
3.5 通过反射访问属性
public class AccessAttribute {
public static void main(String[] args) throws Exception {
Class PersonClass = Class.forName("com.geekby.Person");
Constructor constructor = PersonClass.getConstructor(String.class, Integer.class);
Person p = (Person) constructor.newInstance("Geekby", 24);
// name 是私有属性,需要先设置可访问
Field f = PersonClass.getDeclaredField("name");
f.setAccessible(true);
f.set(p, "newGeekby");
System.out.println(p.getName());
}
}
方法 | 说明 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对 |
getDeclaredFields() | 获得所有属性对象 |
3.6 利用反射执行代码
public class Exec {
public static void main(String[] args) throws Exception {
//java.lang.Runtime.getRuntime().exec("calc");
Class runtimeClass = Class.forName("java.lang.Runtime");
// getRuntime 是静态方法,invoke 时不需要传入对象
Object runtime = runtimeClass.getMethod("getRuntime").invoke(null);
runtimeClass.getMethod("exec", String.class).invoke(runtime,"open /System/Applications/Calculator.app");
}
}
以上代码中,利用了 Java 的反射机制把我们的代码意图都利用字符串的形式进行体现,使得原本应该是字符串的属性,变成了代码执行的逻辑,而这个机制也是后续的漏洞使用的前提。
tips
invoke 的作用是执行方法,它的第一个参数是:
如果该方法为普通方法,那么第一个参数是类对象
如果该方法为静态方法,那么第一个参数是类或 null
此外,另一种常用的执行命令的方式 ProcessBuilder
,通过反射来获取其构造函数,然后调用 start()
来执行命令:
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
查看文档可知: ProcessBuilder
有两个构造函数:
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
上面通过反射的调用方式使用了第一种形式的构造函数。
但是,上述的 Payload 用到了 Java 里的强制类型转换,有时候我们利用漏洞的时候(在表达式上下文中) 是没有这种语法的。因此,仍需利用反射来执行 start
方法。
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("open", "/System/Applications/Calculator.app")));
上述的第二种构造函数如何调用呢?
对于可变长参数,Java 在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价的:
public void hello(String[]names){}
public void hello(String...names){}
因此,对于反射来说,如果目标函数里包含可变长参数,传入数组即可。
Classclazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)
在调用 newInstance
的时候,因为该函数本身接收的是一个可变长参数:
传给 ProcessBuilder
的也是一个可变长参数,二者叠加为一个二维数组,所以整个 Payload 如下:
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"open", "/System/Applications/Calculator.app"}}));
3.7 反序列化漏洞与反射
在安全研究中,使⽤反射的⼀⼤⽬的,就是绕过某些沙盒。比如,上下文中如果只有 Integer 类型的数字,如何获取到可以执行命令的 Runtime 类:
比如可以这样(伪代码): 1.getClass().forName("java.lang.Runtime")
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论