freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

Java反射深入再理解
Lemono 2023-07-26 15:22:30 120307
所属地 浙江省

前言

Java反射是一种强大的机制,它允许程序在运行时动态地加载、检查和使用类,以及动态地调用类的方法、访问和修改类的属性。之前已经简单的学过反射的基本使用,但随着反序列化的深入学习,反射被用到的地方也越来越多,而很多东西都是在反射的基础上做伸展,比如动态代理、Spring的依赖注入等,因此这篇文章打算深入Java反射的了解和使用。

反射多种方式调用exec

在观看前需要了解最基本的Java反射知识,获取method、field等。
首先来认识下我们常见的命令执行的类Runtime.getRuntime().exec()

image.png

可以看到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);
Method method = invoke.getClass().getMethod("exec", String.class);
method.invoke(invoke,"calc");

image.png

反射调用私有内部类

再来看这样一个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");
    }
}

image.png
首先需要实例化一个People对象,这是必要的,由于是私有构造方法 ,所以利用和上面是一样的了,只是有参的话需要在后面跟上参数类型的泛型。

随后是实例化内部类Student,对于内部类还是有一点区别,从类的获取上可以看到是使用的Class.forName("org.example.test.People$Student"),在People后加$访问Student,这是Java特有的形式,随后是反射调用构造方法实例化。而实际上内部类的构造方法是不同的,通过打印getDeclaredConstructors()(获取类下所有构造方法包括私有,为一个数组) 可以看到,不论是无参构造还是有参,都会包含他的外部类作为第一个参数,即org.example.text.People,所以对于反射方式实例化内部类的话,无参并不是真正的无参了。
4.png

总结

Java反射是Java中很强大的一个功能,即动态修改运行过程中对象的状态,甚至包括私有属性,也正是因为他的强大和灵活,导致一系列的安全问题。在反序列化过程中一定会被拿来动态修改某些值绕过检测判断,最终行成Godgate,所以能够灵活运行就显得很重要了。

# web安全 # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 Lemono 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
Lemono LV.3
do what i want
  • 5 文章数
  • 7 关注者
浅谈二次反序列化利用
2023-07-20
Tomcat内存马之Valve和WebSocket型
2023-05-08
关于Java中order by注入详解
2023-03-16