初探java安全之反射(1)

前言

上次和亮去接了个渗透的比赛,结果我还是啥都不会,当时意识到现在大多数的网站的后端都基本上是 java 和 go了,想 php 的基本上比较少了,php 在以后肯定会没落的,java不想 php 那样 简单易用且灵活,所以很有必要系统性的学习一下java安全。

初探java反射

Java安全可以从反序列化漏洞开始说起,反序列化漏洞⼜可以从反射开始说起。 反射是⼤多数语⾔⾥都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有 ⽅法(包括私有),拿到的⽅法可以调⽤,总之通过“反射”,我们可以将Java这种静态语⾔附加上动态 特性。

java反射这块学校的教科书上并没有,我是参考这篇学习的 https://www.cainiaojc.com/java/java-reflection.html

什么是java反射

Java中有一个名为Class的类,该类在运行时保留有关对象和类的所有信息。

Class对象描述了特定类的属性。该对象用于执行反射。

反射机制允许程序在执行期借助于 ReflectionAPI 取得任何类的内部信息(比如成员变量、构造器、成员方法等等),并能操作对象的属性及方法。 加载完类后,在堆中就产⽣了⼀个Class类型的对象(⼀个类只有⼀个Class对象),这个对象包含了类的完整结构 信息。通过这个对象得到类的结构。

image-20221007204432423

利用反射机制可以使 java 这样的编译型语言 更加灵活起来

Java反射机制可以完成:

  • 在运⾏时判断任意⼀个对象所属的类
  • 在运⾏时构造任意⼀个类的对象
  • 在运⾏时得到任意⼀个类所具有的成员变量和⽅法
  • 在运⾏时调⽤任意⼀个对象的成员变量和⽅法
  • ⽣成动态代理

反射相关的主要类如下:

1Java.lang.Class:代表⼀个类,Class对象表示某个类加载后在堆中的对象
2Java.lang.reflect.Method:代表类的⽅法
3Java.lang.reflect.Field:代表类的成员变量
4Java.lang.reflect.Constructor:代表类的构造⽅法

反射要用到的一些方法

参考 https://www.cainiaojc.com/java/java-reflection.html

获得名为 Class 的类的对象
  • 使用 forName() 方法

    forName() 接受字符串参数(类的名称)并返回Class对象。返回的对象引用字符串指定的类。例如,

    Class Dog {  }
    Class c1 = Class.forName("Dog"); //获得名为Dog的Class对象
  • 使用getClass()方法

    getClass() 方法使用特定类的对象来创建新的对象Class。例如,

    Dog d1 = new Dog();
    Class c1 = d1.getClass();
  • 使用.class

    java 的每个类中默认会创建一个名为 class 的属性,该属性就是该类的 Class 对象

    我们还可以使用 .class 扩展名创建Class对象。例如,

    Class c1 = Dog.class;   //使用   类名.class   获得Class对象

创建Class对象后,我们可以使用这些对象执行反射。

获取接口

若一个类实现了某个接口,可以使用 Class 的 getInterfaces() 方法来获取。此方法返回一个接口数组。

Class obj = d1.getClass();//获得class对象 
Class[] objInterface = obj.getInterfaces();//获得接口
获取父类和访问修饰符

类 Class 的方法 getSuperclass() 可用于获取有关特定类的父类的信息。

而且,Class提供了一种 getModifier() 方法,该方法以整数形式返回class的修饰符。

Class obj = d1.getClass();
//以整数形式获取Dog的访问修饰符
int modifier = obj.getModifiers();
//获取Dog的父类
Class superClass = obj.getSuperclass();
获取字段(属性)

我们可以使用 Field 类提供的各种方法检查和修改类的不同字段。

  • getFields() -> Field 返回该类及其父类的所有公共字段
  • getDeclaredFields() -> Field[] 返回类的所有字段
  • getModifier() -> int 以整数形式返回字段的修饰符
  • set(Object, value) -> void 使用指定的值设置字段的值(注意是 Object,是个对象实列)
  • get(Object) -> Object 获取字段的值 (注意是 Object)
  • setAccessible(boolean) -> void 使私有字段可访问

注意:如果我们知道字段名称,则可以使用

  • getField(“fieldName”) -> Field 从类返回名称为 fieldName 的公共字段。
  • getDeclaredField(“fieldName”) -> Field 从类返回名称为 fieldName 的字段。(若获得的是私有字段,则要在使用之前设置 setAccessible(true)

例如有

class Dog {
  public String type;
}

访问公共字段

 Dog d1 = new Dog();
//创建Class对象
Class obj = d1.getClass();
//操纵Dog类的公共字段type
Field field1 = obj.getField("type");
//设置字段的值,第一个参数是对象实例
field1.set(d1, "labrador");
//通过转换成字符串来获取字段的值,第一个参数是对象实例
String typeValue = (String)field1.get(d1);
//获取类型的访问修饰符
int mod1 = field1.getModifiers();
String modifier1 = Modifier.toString(mod1);

访问私有字段

设有

class Dog {
 private String color;
}
 Dog d1 = new Dog();
//创建类Class对象
Class obj = d1.getClass();

//访问私有字段
Field field2 = obj.getDeclaredField("color");

//使私有字段可访问
field2.setAccessible(true);
//设置color值(注意第一个参数为对象实例)
field2.set(d1, "brown");
// get the value of type converting in String
String colorValue = (String)field2.get(d1);
//获取color的访问修饰符
int mod2 = field2.getModifiers();
String modifier2 = Modifier.toString(mod2);
获取方法

像字段一样,我们可以使用 Method 类提供的各种方法来检查类的不同方法。

  • getMethods() -> Method[] 返回该类及其超类的所有公共方法
  • getDeclaredMethod() -> Method[] 返回该类的所有方法
  • getName() -> String 返回方法的名称
  • getModifiers() -> int 以整数形式返回方法的访问修饰符
  • getReturnType() -> Class<?> 返回方法的返回类型
class Dog {
   public void display() {
      System.out.println("I am a dog.");
   }

   protected void eat() {
      System.out.println("I eat dog food.");
   }

   private void makeSound() {
      System.out.println("Bark Bark");
   }

}

class ReflectionDemo {
   public static void main(String[] args) {
      try {
          Dog d1 = new Dog();

          //创建一个Class对象
          Class obj = d1.getClass();
          
          //使用getDeclaredMethod()获取所有方法
          Method[] methods = obj.getDeclaredMethods();

          //获取方法的名称
          for(Method m : methods) {
               
             System.out.println("方法名称: " + m.getName());
              
             //获取方法的访问修饰符
             int modifier = m.getModifiers();
             System.out.println("修饰符: " + Modifier.toString(modifier));
              
             //获取方法的返回类型
             System.out.println("Return Types: " + m.getReturnType());
             System.out.println(" ");
          }
       }
       catch(Exception e) {
           e.printStackTrace();
       }
   }
}
获取构造方法

可以使用 Constructor 类提供的各种方法检查类的不同构造函数。

  • getConstructors() - 返回该类的所有公共构造函数以及该类的父类
  • getDeclaredConstructor() -返回所有构造函数
  • getName() - 返回构造函数的名称
  • getModifiers() - 以整数形式返回构造函数的访问修饰符
  • getParameterCount() - 返回构造函数的参数数量
import java.lang.Class;
import java.lang.reflect.*;

class Dog {

   public Dog() {
      
   }
   public Dog(int age) {
      
   }

   private Dog(String sound, String type) {
      
   }
}

class ReflectionDemo {
   public static void main(String[] args) {
      try {
           Dog d1 = new Dog();
           Class obj = d1.getClass();

           //使用getDeclaredConstructor()获取一个类中的所有构造函数
           Constructor[] constructors = obj.getDeclaredConstructors();

           for(Constructor c : constructors) {
               //获取构造函数的名称
               System.out.println("构造函数名称: " + c.getName());

               //获取构造函数的访问修饰符
               int modifier = c.getModifiers();
               System.out.println("修饰符: " + Modifier.toString(modifier));

               //获取构造函数中的参数数量
               System.out.println("参数个数: " + c.getParameterCount());
          }
       }
       catch(Exception e) {
           e.printStackTrace();
       }
    }
}
实例化对象与执行函数
  • newInstance() 实例化类对象的方法 ,位于 Class 类中 (只能无参构造)
Class clsd = Class.forName("Dog");
Dog dog = (Dog) clsd.newInstance();//通过反射实例化一个对象
dog.display();
  • invoke 执行函数的方法,位于 Method 类中
//执行Dog中的display方法
public class Invo {
    public static void main(String[] args) throws Exception {
        execute("Dog", "display");
    }
    public static void execute(String className, String methodName) throws Exception {
        Class clazz = Class.forName(className);
        clazz.getMethod(methodName).invoke(clazz.newInstance());
    }
}
class Dog {

    public void display() {
        System.out.println("I am a dog.");
    }
}

重点方法

  • 获取类的方法 foName
  • 实例化类对象的方法 newInstance
  • 获取函数的方法 getMethod
  • 执行函数的方法 invoke

基本上,这几个方包揽了Java安全里各种和反射有关的Payload。

public void execute(String className, String methodName) throws Exception {
     Class clazz = Class.forName(className);
     clazz.getMethod(methodName).invoke(clazz.newInstance());
}