星期五实验室
阅读须知
星期五实验室的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息造成的直接或间接后果和损失,均由使用者本人负责。
星期五实验室拥有对此文章的修改、删除和解释权限,如转载或传播此文章,需保证文章的完整性,未经授权,不得用于其他。
1. 前言
Java
反射是一种强大的机制,它允许程序在运行时动态地加载、检查和使用类,以及动态地调用类的方法、访问和修改类的属性。
之前已经简单的学过反射的基本使用,但随着反序列化的深入学习,反射被用到的地方也越来越多,而很多东西都是在反射的基础上做伸展,比如动态代理、Spring
的依赖注入等,因此这篇文章打算深入Java
反射的了解和使用。
2. 反射多种方式调用exec
在观看前需要了解最基本的Java
反射知识,获取method
、field
等。首先来认识下我们常见的命令执行的方法Runtime.getRuntime().exec()
可以看到Runtime
类中只存在一个无参的构造方法,且关键是为private
属性,也就是说我们不能直接通过new
来创建一个类对象。
一般情况下,一个类的构造方法为public
属性,这时就可以通过new Object()
实例化一个对象,也就是通过类加载获取类,最终调用他的构造方法的过程。若构造方法为private
属性时是没法访问到的,因此不能通过new Runtime()
实例化对象,但是还好他提供了一个静态方法getRuntime
,从内部创建对象实例,如此便可利用currentRuntime
调用exec
方法执行命令。
所以第一种最简单的调用方式:
//第一种方式:
Runtime.getRuntime().exec("calc");
第二种方式:基础反射调用类下的方法即可:
//第二种方式:
Runtime runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
第三种方式:跳过从本身内部实例化对象,直接获取Runtime
类,使用反射调用Constructor
构造方法,最后再newInstance
创建Runtime
的对象实例。实例化一个对象对于后面反射调用方法是必要的,因为在最后invoke
时需要传入两个参数,一个为实例化的对象,另一个是被调用方法传入的参数,往往第一个会成为最容易犯错和忽视的地方。
当创建了Runtime
实例就不用调用getRuntime()
方法了,直接反射调用exec
方法执行命令即可。
//第三种方式
Class<Runtime> runtimeClass = Runtime.class;
Constructor<Runtime> runtime = runtimeClass.getDeclaredConstructor();
runtime.setAccessible(true);
Runtime runtime1 = runtime.newInstance();
Class<? extends Runtime> aClass = runtime1.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime1,"calc");
第四种方式实现与上一种大体一致,本身通过Class.forName()
和.class
都能实现反射。同样是上诉的方法创建Runtime
实例,后面稍微绕了一下,先调用getRuntime
方法,在利用该方法创建的Runtime
实例反射调用exec
执行命令。其实是大可不必的,这里也是为了加深反射调用的学习才多此一举。
//第四种方式
Class<?> aClass1 = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = aClass1.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Runtime runtime1 = (Runtime) declaredConstructor.newInstance();
Class<? extends Runtime> aClass2 = runtime1.getClass();
Method getRuntime = aClass2.getMethod("getRuntime");
Object invoke = getRuntime.invoke(runtime1);
Class<?> aClass = invoke.getClass();
Method method = aClass.getMethod("exec", String.class);
method.invoke(invoke,"calc");
3. 反射调用私有内部类的私有方法
再来看这样一个demo
:
一个People
类,私有有参构造方法,存在一个私有内部类Student
,Student
中存在私有无参有参构造和私有方法badBoy
,如果说需要反射调用badBoy
方法,那么阁下又该如何应对呢?
public class People {
private String name ;
private int age ;
private People(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello(){
System.out.println("Hello World!");
}
private void sayGoodBye() {
System.out.println("GoodBye World~");
}
private class Student {
public String username;
public int uid;
private Student() {
}
private Student(String username, int uid) {
this.username = username;
this.uid = uid;
}
private void goodBoy(String username) {
System.out.println(username + " is a good Boy~");
}
private void badBoy(String username) {
System.out.println(username + " is a bad Boy~");
}
}
}
先给出我的代码实现:
public class Main {
public static void main(String[] args) throws Exception{
Class<?> people_class = Class.forName("org.example.test.People");
Constructor<?> constructor = people_class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
People people = (People) constructor.newInstance("lemono",100);
Class<?> aClass = Class.forName("org.example.test.People$Student");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
//由于内部类隐含的持有一个外部类的引用,所以在使用反射获取内部类构造方法的时候。需要在参数中加入外部类的Class对象。
declaredConstructors[0].setAccessible(true);
Object o = declaredConstructors[0].newInstance(people);//private org.example.Person$Student(org.example.Person)默认存在一个参数
//不用数组形式实例化
// Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(people_class);//需带上参数
// declaredConstructor.setAccessible(true);
// Object o = declaredConstructor.newInstance(people);
Method declaredMethod = o.getClass().getDeclaredMethod("badBoy",String.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke(o,"lemono");
}
}
首先需要实例化一个People
对象,这是必要的,由于是私有构造方法 ,所以利用和上面是一样的了,只是有参的话需要在后面跟上对应的参数类型class
。
随后是实例化内部类Student
,对于内部类还是有一点区别,从类的获取上可以看到是使用的Class.forName("org.example.test.People$Student")
,在People
后加$
访问Student
,这是Java
特有的形式,随后是反射调用构造方法实例化。
而实际上内部类的构造方法是不同的,通过打印getDeclaredConstructors()
(获取类下所有构造方法包括私有,为一个数组) 可以看到,不论是无参构造还是有参,都会包含他的外部类作为第一个参数,即org.example.text.People
,所以对于反射方式实例化内部类的话,无参并不是真正的无参了。
我这里使用数组访问可避免这个问题,若是使用getDeclaredConstructor()
指定获取的话就需要加上外部类作为参数,像这样:
`Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(people_class);`
后面就是常规的反射获取私有方法badBoy
了。
4. 总结
反射是Java
中很强大的一个功能,即动态修改运行过程中对象的状态,甚至包括私有属性,也正是因为他的强大和灵活,导致一系列的安全问题。在反序列化过程中一定会被拿来动态修改某些值绕过检测判断,最终行成Gadget
,所以能够灵活运用就显得很重要了。
FRIDAY LAB
星期五实验室成立于2017年,汇集众多技术研究人员,在工业互联网安全前瞻技术研究方向上不断进取。星期五实验室由海内外知名高校的学院精英及来自于顶尖企业的行业专家组成,且大部分人员来自国际领先、国内知名的黑客战队——浙大AAA战队。作为木链科技专业的技术研发团队,星期五实验室凭借精湛的专业技术水平,为产品研发提供新思路、为行业技术革新探索新方向。
扫二维码|关注我们
星期五实验室
FRIDAY LAB
原文始发于微信公众号(星期五实验室):Java反射深入再理解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论