安全矩阵

 找回密码
 立即注册
搜索
查看: 4327|回复: 0

Java代码审计基础之反射

[复制链接]

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
发表于 2020-7-27 13:08:20 | 显示全部楼层 |阅读模式
先来一段反射的概念:
在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成对象。
类在运行的时候,可以得到该类的信息,并且可以动态的修改这些信息
反射类的首要操作 - 获取类获取类有 三种 方法:
假设类名为 Cat
(1)直接根据类名获取类
Class a = Cat.class;
(2)通过Class.forName 获取类,需要打全指定类的路径
Class a = Class.forName("org.xiaopan.test.Cat");
注意:
Class.forName 需要一个异常处理。不然编辑器无法通过。
(3)通过实例化的方式获取类
Class a = (new Cat()).getClass();

反射构造方法吾有一类,曰:
  1. class Cat{
  2.     //构造方法,形参为空
  3.     public Cat(){
  4.         System.out.println("Cat->nullCat");
  5.     }

  6.     //构造方法,形参为 一个String类型
  7.     public Cat(String a){
  8.         System.out.println("Cat->aCat,a=" + a);
  9.     }

  10.     //私有构造方法,形参为 一个String类型 和 一个Integer类型
  11.     private Cat(String a,Integer b){
  12.         System.out.println("Cat->abCat,a=" + a + "b=" + b.toString());
  13.     }

  14.     //构造方法,形参为 一个String数组类型 和 一个Map类型
  15.     public Cat(String[] aa,Map bb){
  16.         System.out.println("Cat->aabbCat");
  17.         System.out.println("aa=" + Arrays.toString(aa));
  18.         System.out.println("bb=" + bb);
  19.     }
  20. }
复制代码
无参构造方法调用
  1. try{
  2.     //获取到类
  3.     Class a = Cat.class;
  4.     //通过反射获取到指定类下的构造方法
  5.     //要获取的构造方法为:
  6.     //public Cat()
  7.     //由于该构造方法无参数,所以我们传入一个 null 即可,也可以不传
  8.     Constructor constructor1 = a.getConstructor(null);
  9.     //实例化类
  10.     constructor1.newInstance(null);
  11. }
  12. catch (Exception e){
  13.     System.out.println(e);
  14. }
复制代码
输出:

注意:
getConstructor 需要加一个异常处理。
一个参数的构造方法调用
  1. try{
  2.     //获取到类
  3.     Class a = Cat.class;
  4.     //通过反射获取到指定类下的构造方法
  5.     //要获取的构造方法为:
  6.     //public Cat(String a)
  7.     //由于该构造方法有一 String类型 参数
  8.     //在进行 getConstructor 反射时,就需要指定传参类型为 String.class
  9.     Constructor constructor1 = a.getConstructor(String.class);
  10.    //在实例化时,进行传参
  11.    constructor1.newInstance("testvalue");
  12. }
  13. catch (Exception e){
  14.     System.out.println(e);
  15. }
复制代码
输出:

多个参数的私有构造方法调用注意,这里调用的构造方法是私有的哦~

  1. try{
  2.     //获取到类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //通过反射获取到指定类下的构造方法
  5.     //要获取的 私有 构造方法为:
  6.     //private Cat(String a,Integer b)
  7.     //
  8.     //由于是 私有 方法,获取私有方法的函数为 getDeclaredConstructor
  9.     //由于有两个参数,所以需要在 getDeclaredConstructor 传入对应的参数类型
  10.     Constructor c = a.getDeclaredConstructor(String.class, Integer.class);
  11.     //设置强制反射
  12.     c.setAccessible(true);
  13.     //构造函数实例化,并传参
  14.     //这里有个注意点,看下文的注意
  15.     c.newInstance(new Object[]{"abcd",123456});
  16. }
  17. catch (Exception e){
  18.     System.out.println(e);
  19. }
复制代码
输出:

注意:
参考了大佬的文章(参考文章在本文末尾 Referer 中),文章说 jdk1.4和 jdk1.5 处理调用的方法有区别
jdk1.4中,数组每个元素对应一个参数
jdk1.5中,整个数组是一个参数,用一个对象包起来
所以我们调用的传参的时候,需要使用这种格式:

  1. c.newInstance(new Object[]{"abcd",123456});
  2. //即
  3. new Object[]{"abcd",123456} 的格式,用一个对象包裹起来
复制代码
形参为数组和Map类型的构造方法调用字符串数组
创建格式:


String[] a = {"abc", "def"};
Map:
java中的map,可以理解为“可自定义键值的数组”

形参为数组和Map类型的构造方法调用:

  1. try{
  2.     //获取类
  3.     Class a = (new Cat()).getClass();
  4.     //调用的构造方法为:
  5.     //public Cat(String[] aa,Map bb)
  6.     //照常打 xxx.class 即可。java 万物皆对象
  7.     Constructor c = a.getConstructor(String[].class, Map.class);
  8.     //创建一个map
  9.     Map m = new HashMap();
  10.     m.put("a_key","a_value");
  11.     //实例化构造函数,注意要用 Object包裹的形式
  12.     //new String[]{} 是当场初始化字符串数组,当场赋值
  13.     c.newInstance(new Object[]{new String[]{"a","b","c"}, m});
  14. }
  15. catch (Exception e){
  16.     System.out.println(e);
  17. }
复制代码
输出:

反射方法简介:
反射方法和上文的反射构造方法差不多,如果是私有的话也是要设置强行调用,并且获取方法的函数为 getDeclaredxxxx
吾有一类:

  1. class Cat{
  2.     public void a(){
  3.         System.out.println("a invoke");
  4.     }

  5.     public String[] b(String[] b){
  6.         return b;
  7.     }

  8.     public static void c(){
  9.         System.out.println("cccccc");
  10.     }
  11. }
复制代码
反射无参数方法
  1. try{
  2.     //获取类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //先实例化,后面调用方法的时候需要使用实例化好的类
  5.     //注意,实例化之后返回的类型就是对于的类,做好类型转换
  6.     Cat cat =(Cat) a.newInstance();
  7.     //调用的方法为:
  8.     //public void a()
  9.     //
  10.     //获取方法,需要指定要获取的方法名
  11.     Method m = a.getMethod("a", null);
  12.     //调用方法,调用方法时,用 上一步代码中获取到的方法进行 invoke 调用操作
  13.     //而invoke方法中,第一个参数是实例化好的类
  14.     //第二个参数就是需要传入的参数
  15.     m.invoke(cat,null);

  16. }
  17. catch (Exception e){
  18.     System.out.println(e);
  19. }
复制代码
输出:

反射有参数有返回值方法
  1. try{
  2.     //获取类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //先实例化,后面调用方法的时候需要使用实例化好的类
  5.     //注意,实例化之后返回的类型就是对于的类,做好类型转换
  6.     Cat cat =(Cat) a.newInstance();
  7.     //调用的方法为:
  8.     //public String[] b(String[] b)
  9.     //
  10.     //获取方法,需要指定要获取的方法名
  11.     Method m = a.getMethod("b",String[].class);
  12.     //调用方法,调用方法时,用 上一步代码中获取到的方法进行 invoke 调用操作
  13.     //调用时,如果参数是字符串数组,或者两个以上的参数
  14.     //最好使用 new Object[]{} 的形式传入
  15.     //兼容性好
  16.     //由于有返回值,我们在调用的时候也需要进行接收
  17.     //接受类型就看调用的类返回的类型了
  18.     String[] strs = (String[]) m.invoke(cat, new Object[]{new String[]{"str1","str2","str3"}});

  19.     //打印数组:
  20.     //for each打印数组,先指定抽出来的元素类型
  21.     //然后以冒号 : 分隔,左边是抽出元素变量名,右边是原数组
  22.     for (String str:strs){
  23.         System.out.println(str);
  24.     }

  25. }
  26. catch (Exception e){
  27.     System.out.println(e);
  28. }
复制代码
输出:

反射静态方法由于静态方法不需要实例化类,所以在 getMethod 的时候,直接传个 null 即可。也不需要 newInstance 类了。

  1. try{
  2.     //获取类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //调用的方法为:
  5.     //public static void c()
  6.     //
  7.     //获取方法
  8.     Method m = a.getMethod("c");
  9.     //由于是静态方法。直接调用,类对象中传入null即可
  10.     m.invoke(null);

  11. }
  12. catch (Exception e){
  13.     System.out.println(e);
  14. }
复制代码
输出:

反射属性反射属性也大同小异
吾有一类:
  1. class Cat{
  2.     public String name = "maomao";      //公共 String类型 属性
  3.     public static Boolean sex = true;   //公共 静态 String类型 属性
  4.     private Integer age = 10;           //私有 Integer类型 属性
  5. }
复制代码
反射公共属性
  1. try{
  2.     //获取类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //先实例化,后面获取属性的时候需要使用实例化好的类
  5.     Cat cat = (Cat) a.newInstance();
  6.     //属性:
  7.     //public String name = "maomao";
  8.     //
  9.     //获取属性
  10.     Field m = a.getField("name");
  11.     //获取属性值
  12.     //需要传入实例化类作为对象
  13.     String name = (String) m.get(cat);
  14.     System.out.println(name);

  15. }
  16. catch (Exception e){
  17.     System.out.println(e);
  18. }
复制代码
输出:

反射公共静态属性静态属性也一样,不需要实例化即可调用:

  1. try{
  2.     //获取类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //属性:
  5.     //public static Boolean sex = true;
  6.     //
  7.     //获取属性
  8.     Field m = a.getField("sex");
  9.     //获取属性值,静态属性不需要实例化类,直接传入null作为类对象即可
  10.     Boolean b = (Boolean) m.get(null);
  11.     System.out.println(b);
  12.     //设置属性值
  13.     m.set(null,false);
  14.     b = (Boolean) m.get(null);
  15.     System.out.println(b);

  16. }
  17. catch (Exception e){
  18.     System.out.println(e);
  19. }
复制代码
输出:

反射私有属性私有属性也一样,需要暴力反射

  1. try{
  2.     //获取类
  3.     Class a = Class.forName("org.xiaopan.test.Cat");
  4.     //先实例化,后面获取属性的时候需要使用实例化好的类
  5.     Cat cat = (Cat) a.newInstance();
  6.     //属性:
  7.     //private Integer age = 10;
  8.     //
  9.     //获取私有属性
  10.     Field m = a.getDeclaredField("age");
  11.     //设置强制反射
  12.     m.setAccessible(true);
  13.     //获取属性值
  14.     Integer age = (Integer) m.get(cat);
  15.     //注意输出的时候要将非String类型 toString 哦,规范一点
  16.     System.out.println(age.toString());
  17. }
  18. catch (Exception e){
  19.     System.out.println(e);
  20. }
复制代码
输出:

引用包错误的报错:用IDEA写代码的时候,可以会遇到奇怪报错,如:
代码本来就没问题,但还是报错了:

这个时候可以看看代码最上面,看看IDEA是不是自动引入了错误的包:
​发现有引用错误的包,将其删掉即可,然后再重新在 Method上进行修复:

Runtime.getRuntime.exec 反射了解 Runtime.getRuntime.execRuntime.getRuntime.exec 是Java中执行系统命令的方法
简单使用如下:

  1. byte[] a = new byte[1024];

  2. Process cmd = Runtime.getRuntime().exec("whoami");

  3. InputStream input = cmd.getInputStream();
  4. input.read(a);
  5. String res = new String(a);

  6. System.out.println(res);
复制代码
我们来一一分析下,重点就两大块:Process 和 InputStream
Process cmd = Runtime.getRuntime().exec(“whoami”)首先先看看 Runtime.getRuntime().exec 是什么东西,返回值类型是什么样的:
在手册上查看描述:

可知 exec 函数就是执行系统命令用的
在去看看源码做二次确认
​返回类型是 Process 类型,所以我们调用的时候用 Process 类型接收返回值
Process类提供进程输入、输出等进程方法。粗浅的说就是一个进程类
通过文档可以得知,我调用的这个exec方法需要一个String类型的参数,即要执行的系统命令
InputStream input = cmd.getInputStream()其中:
InputStream    输入流,即数据流入,读入数据
OutputStream 输出流,即数据输出,写入数据
该代码读取上一步 Process 类型的数据流
input.read(a);
在上一步调用getInputStream后,可执行 read 函数。
将当前的数据流读取出来,写入到一个 byte[]类型的变量里。

String res = new String(a);
将byte类型转换成字符串。以便后面打印输出
这就是一个简单的 Java 命令执行并回显结果。
我们可以看到主要调用了 Runtime.getRuntime().exec
那么我们要如何通过反射的方式进行调用呢?
反射调用 Runtime.getRuntime().exec第一种方式,通过强行反射私有构造方法,用 Runtime 实例化进行反射这里有一个小坑,Runtime的构造函数是私有的:

所以我们要强制反射私有构造方法,而且不能直接 newInstance Class:
错误写法:
直接用Class来进行实例化
  1. Class runtime = Class.forName("java.lang.Runtime");
  2. runtime.newInstance();
复制代码
会报错:
java.lang.IllegalAccessException: Class org.xiaopan.test.Main can not access a member of class java.lang.Runtime with modifiers “private”
正确写法:
先强制反射Runtime的构造方法,再实例化构造方法。
  1. Class runtime = Class.forName("java.lang.Runtime");
  2. Constructor c = runtime.getDeclaredConstructor();
  3. c.setAccessible(true);
  4. c.newInstance();
复制代码
反射调用Runtime.gettime.exec
  1. byte[] a = new byte[1024];
  2. try{
  3.     //获取Runtime类
  4.     Class runtime = Class.forName("java.lang.Runtime");
  5.     //获取Runtime类构造方法
  6.     Constructor c_runtime = runtime.getDeclaredConstructor();
  7.     //设置强制反射
  8.     c_runtime.setAccessible(true);
  9.     //Runtime类的构造方法 实例化
  10.    //由于 Runtime类构造方法返回类型为 Runtime类,所以需要使用 Runtime 类型变量进行接收
  11.     Runtime r = (Runtime) c_runtime.newInstance();
  12.     //获取 Runtime类的方法 exec
  13.     Method m = runtime.getMethod("exec", String.class);
  14.     //调用 exec 方法,传入对象为 Runtime类的构造方法实例化
  15.     //由于 exec 方法返回的是 Process 类型数据,所以需要使用 Process 类型变量进行接收
  16.     Process p = (Process) m.invoke(r,"whoami");
  17.     //读入数据流,读入到 byte[] 类型的变量中
  18.     p.getInputStream().read(a);
  19.     System.out.println(new String(a));
  20. }
  21. catch (Exception e){
  22.     System.out.println(e);
  23. }
复制代码
成功输出:

第二种方式,不进行 Runtime实例化,直接通过getRuntime进行反射注意点:
发现盲点:在本节一开头,调用系统命令函数 exec 的形式如下:
  1. Runtime.getRuntime().exec("whoami");
复制代码
我们去源码翻翻 getRuntime()是个什么函数

我们可以发现,getRuntime就是为了返回 Runtime类 实例的,感觉应该是一个 单例模式
我们遵守源码的规则,直接调用 getRuntime,拿到 Runtime类实例
注意点:
由于 getRuntime方法 返回的是 Runtime类实例,所以反射的时候需要显示类型转换。
代码如下:
  1. byte[] a = new byte[1024];
  2. try{
  3.     //获取 Runtime 类
  4.     Class runtime = Class.forName("java.lang.Runtime");
  5.     //获取getRuntime方法
  6.     Method m = runtime.getMethod("getRuntime");
  7.     //调用getRuntime方法,并用 Runtime类 类型进行接收,显式转换成 Runtime类
  8.     //调用的时候,在上文查看源码时,发现是不需要传入参数的
  9.     //不需传入参数,我们直接传一个 null 进去即可
  10.     Runtime r = (Runtime) m.invoke(null);
  11.     //由于上一个代码中调用 getRuntime 方法,返回了 Runtime类
  12.     //我们直接就可以调用底下的 exec 方法了
  13.     Process p = r.exec("ifconfig");
  14.     //数据输入流,读入数据
  15.     InputStream res = p.getInputStream();
  16.     res.read(a);
  17.     System.out.println(new String(a));
  18. }
  19. catch (Exception e){
  20.     System.out.println(e);
  21. }
复制代码
成功输出:

Referer:
java手册:
https://www.oracle.com/cn/java/technologies/java-se-api-doc.html
大佬文章:
https://blog.csdn.net/ju_362204801/article/details/90578678





回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 02:40 , Processed in 0.014143 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表