Java安全_反射

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

什么是反射

正向考虑

在java实际应用为什么要有反射

定义是:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用方法的功能成为反射机制。

正常在Java中,对象的类型是在编译期就确定下来的,而Java反射机制就可以在对象类型未知的情况下,动态的创建对象并调用其属性

举个栗子深入浅出

Test ts = new Test();\\正射,在编译的时候就知道这个对象的类型是Test
Class clz = Class.forname("类的全限定名");Object ts = clz.newInstance();\\反射,动态获取类的对象

这里new Student和clz.newInstance是等价的,后面再进行解释具体什么是什么

通过上面就可以理解一些实际用途如:

  • 框架和库开发:许多框架和库需要在不了解客户端代码的情况下与各种类和对象进行交互。反射允许这些框架在运行时检查、加载和操作客户端提供的类。

  • 插件系统:开发插件式应用程序时,你可能不知道哪些插件将被安装和运行。反射可用于在运行时加载和实例化插件类。

  • 配置和扩展性:通过配置文件或其他外部资源指定类名,反射允许你动态地创建对象。这对于实现可配置性和可扩展性非常有用。

  • 单元测试和模拟:在单元测试中,你可以使用反射来创建对象、调用私有方法和设置私有字段的值,以便模拟测试环境。

  • 数据库访问和ORM框架:许多ORM(对象关系映射)框架使用反射来将数据库表的行映射到Java对象,以便进行数据库操作。

  • 注解处理器:反射用于处理注解,例如自定义注解的处理器,以执行特定的操作。

  • 动态代理:反射可用于创建动态代理对象,允许你在运行时生成实现特定接口的代理类,以实现横切关注点(例如日志、性能监控等)。

说通俗一点:一个游戏需要更新一些配置总不能让用户把这个游戏卸载了再重新安装把

反向考虑

在Java安全中可以用反射做些什么

Java反射机制可以无视类方法、变量访问权限修饰符,可以调用任何类的任意方法、访问并修改成员变量值。不可访问也变的可以访问了,也就是说只要发现一处Java反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能控制反射的类名、方法名和参数。

反射组成的相关类

反射机制相关操作一般位于java.lang.reflect包中

image-20231014121558574

java反射机制组成需要重点注意以下的类:

java.lang.Class:类对象;

java.lang.reflect.Constructor:类的构造器对象;

java.lang.reflect.Field:类的属性对象;

java.lang.reflect.Method:类的方法对象;

反射的源头_Class对象

想要理解反射首先需要知道Class这个类,它的全称是java.lang.Class类。java是面向对象的语言,讲究万物皆对象,即使强大到一个类,它依然是另一个类(Class类)的对象,换句话说,普通类是Class类的对象,即Class是所有类的类(There is a class named Class)。

我们知道java世界是运行在JVM之上的,我们编写的类代码,在经过编译器编译之后,会为每个类生成对应的.class文件,这个就是JVM可以加载执行的字节码。

运行时期间,当我们需要实例化任何一个类时,JVM会首先尝试看看在内存中是否有这个类,如果有,那么会直接创建类实例;如果没有,那么就会根据类名去加载这个类,当加载一个类,或者当加载器(class loader)的defineClass()被JVM调用,便会为这个类产生一个Class对象(一个Class类的实例),用来表达这个类,该类的所有实例都共同拥有着这个Class对象,而且是唯一的。

也就是说,加载.class文件之后会生成一个对应的Class对象。

Snipaste_2023-10-14_16-33-26

Class是java.lang.Class类中,是所有类的的类。而类是java.lang.Class类的实例对象,所以反射就是得到Class对象后反向回去Test对象的各种信息。

获取Class对象的方法

对于普通的类我们这样创建实例

Test ts = new Test();

而我们在创建class类的实例对象却不能使用上述方法,运行会抛出错误

image-20231014172717838

跟进Class类的源码发现其构造器是私有的,所以只有JVM才能够创建Class对象

image-20231014172914659

因为Class类是private私有属性,我们也无法通过创建对象的方式来获取class对象,那么我们怎样才能够获取到class对象呢?

第一种"类名.class"的方式获取

比如有个类叫Test

public class MyClass {  }

那么可以通过

Class clz = Test.class

第二种通过类创建的实例对象的getClass方法取得

Test ts = new Test();
Class clz = ts.getClass();

第三种Class.forName(String className):动态加载类

第三种则是调用Class类中的forName方法,将字节码文件加载进内存,返回Class对象。

Class clz = Class.forName("cmo.ref.Test");

写个简单的demo来演示分别利用这三种方法获取当前类Class对象的当前类名。

package cmo.ref;
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 类的.class属性
        Class clz1 = Test.class;
        System.out.println(clz1.getName());
        // 实例化对象的getClass()方法
        Test demo = new Test();
        Class clz2 = demo.getClass();
        System.out.println(clz2.getName());
        // Class.forName(String className): 动态加载类
        Class clz3 = Class.forName("cmo.ref.Test");
        System.out.println(clz3.getName());
    }
    }
​
​
​

image-20231014175440183

但在这三种获取CLass类方式中,我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。

而使用类.class属性,需要导入类的包,依赖性太强,在大型项目中容易抛出编译错误;

而使用实例化对象的getClass()方法,需要本身创建一个对象,本身就没有了使用反射机制意义。

所以我们在获取class对象中,一般使用Class.forName方法去获取。

获取成员变量Field

获取成员变量Field位于java.lang.reflect.Field包中

Field[] getFields() :获取所有public修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符

Field getField(String name) 获取指定名称的 public修饰的成员变量

Field getDeclaredField(String name) 获取指定的成员变量

获取成员方法Method

Method getMethod(String name, 类<?>... parameterTypes) //返回该类所声明的public方法

Method getDeclaredMethod(String name, 类<?>... parameterTypes) //返回该类所声明的所有方法

//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型

Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

Method[] getDeclaredMethods() // 获取该类中的所有方法

获取构造函数Constructor

Constructor<?>[] getConstructors() :只返回public构造函数

Constructor<?>[] getDeclaredConstructors() :返回所有构造函数

Constructor<> getConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的public构造函数

Constructor<> getDeclaredConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的构造函数

通过反射创建类对象

在前面我们获取了Class对象,之后展示了一系列获取成员变量、成员方法和成员函数的方式后,我们现在可以通过反射来生成实例化对象,一般我们使用Class对象的newInstance()方法来进行创建类对象。

使用的方式也特别简单,只需要通过forname方法获取到的class对象中进行newInstance方法创建即可。

Class c = Class.forName("com.reflect.MethodTest"); // 创建Class对象
Object m1 =  c.newInstance(); // 创建类对象

invoke

通过上面的讲解,现在已经可以获得Class对象,创建类的实例对象,获得成员方法现在就差调用方法了

这就涉及到invoke方法,invoke方法位于java.lang.reflect.Method类中用于执行某个的对象的目标方法。 一般会和getMethod方法配合进行调用。

使用用法

public Object invoke(Object obj, Object... args)

第一个参数为类的实例,第二个参数为相应函数中的参数

obj:从中调用底层方法的对象,必须是实例化对象

args: 用于方法的调用,是一个object的数组,参数有可能是多个

但需要注意的是,invoke方法第一个参数并不是固定的:

  • 如果这个方法是一个普通方法,那么第一个参数是类对象

  • 如果这个方法是一个静态方法,那么第一个参数是类

这样就可以出来一个小demo了(借鉴

package cmo.ref;
import java.lang.reflect.Method;
public class Test {
    public void reflectMethod() {
        System.out.println("反射测试成功!!!");
    }
    public static void main(String[] args) {
        try {
            Class clz = Class.forName("cmo.ref.Test");// 创建Class对象
            Object m = clz.newInstance(); // 创建类实例对象
            Method method = clz.getMethod("reflectMethod"); // 获取reflectMethod方法
            method.invoke(m); // 调用类实例对象方法
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

利用反射构造Runtime类执行

为什么要特意提到Runtime类因为这个类里有个'exec'方法可以执行命令

image-20231015114246584

到这要重新回溯一下讲一下class.newInstance()

它的作用是就是调用这个类的无参构造函数,但是如果要出错了那就要注意:

  • 你使用的类是不是没有无参构造函数

  • 你使用的类构造函数是不是私有的

这就知道了为什么不能直接这样构造执行命令

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");

image-20231015114825539

image-20231015115113900

因为Runtime类的构造方法是私有的,是根据“单例模式”进行设计的,我们无法进行直接调用。但是同时也可以发现可以通过Runtime.getRuntime()来获取到Runtime对象

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

image-20231015115559426

getMethod 的作用是通过反射获取一个类的某个特定的公有方法。Java中 支持类的重载,我们不能仅通过函数名来确定一个函数。所以,在调用 getMethod 的时候,我们需要 传给他你需要获取的函数的参数类型列表。 比如这里的 Runtime.exec 方法有6个重载:

image-20231015115744005

我们可以实用最简单这种只有一个参数且类型是String。实用getMethod("exec",String.class)获取Runtime.exec方法,并且获取getRuntime方法来执行。

setAccessible(true)暴力访问权限

刚才我们发现如果一个方法或构造方法是私有方法,那么我们不嗯呢该利用反射机制对其进行操作,那么有没有一些特殊情况可以达到呢?

getDeclared 系列的反射

首先说明getDeclared 系列的反射了,与普通的getMethod 、getConstructor 区别是:getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法 getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了。 getDeclaredMethod 的具体用法和 getMethod 类似, getDeclaredConstructor 的具体用法和 getConstructor 类似

Java.lang.reflect.AccessibleObject类是Field,Method和Constructor类对象的基类,可以提供将反射对象标记为使用它抑制某人Java访问控制检查的功能,同时上述的反射类中的Field,Method和Constructor继承自AccessibleObject。所以我们在这些类方法基础上调用setAccessible()方法,既可对这些私有字段进行操作。

所有我们最开始的代码再修改简略下,就能成功命令执行了。

import java.lang.reflect.Constructor;
​
public class Test {
    public static void main(String[] args) throws Exception {
​
        Class clazz = Class.forName("java.lang.Runtime");
        Constructor m = clazz.getDeclaredConstructor();
        m.setAccessible(true);
        clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
​
    }
    }

ProcessBuilder

如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?

看p牛的

我们需要用到一个新的反射方法 getConstructor 。 和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。 比如,我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用 start() 来执行命令:

Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)
clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).star
t();
​

ProcessBuilder有两个构造函数:

public ProcessBuilder(List command)

public ProcessBuilder(String... command)

上面用到了第一个形式的构造函数,所以我在 getConstructor 的时候传入的是 List.class 。 但是,我们看到,前面这个Payload用到了Java里的强制类型转换,有时候我们利用漏洞的时候(在表 达式上下文中)是没有这种语法的。所以,我们仍需利用反射来完成这一步。

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("calc.exe")));

通过 getMethod("start") 获取到start方法,然后 invoke 执行, invoke 的第一个参数就是 ProcessBuilder Object了。

那么,如果我们要使用 public ProcessBuilder(String... command) 这个构造函数,需要怎样用反 射执行呢? 这又涉及到Java里的可变长参数(varargs)了。正如其他语言一样,Java也支持可变长参数,就是当你 定义函数的时候不确定参数数量的时候,可以使用 ... 这样的语法来表示“这个函数的参数个数是可变 的”。 对于可变长参数,Java其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价 的(也就不能重载):

public void hello(String[] names) {}

public void hello(String...names) {}

也由此,如果我们有一个数组,想传给hello函数,只需直接传即可:

String[] names = {"hello", "world"};
​
hello(names);

那么对于反射来说,如果要获取的目标函数里包含可变长参数,其实我们认为它是数组就行了。 所以,我们将字符串数组的类 String[].class 传给 getConstructor ,获取 ProcessBuilder 的第二 种构造函数:

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)

在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给 ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:

Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new
String[][]{{"calc.exe"}})).start();

后记

总算是java安全开始入门了,道阻且长,行将则至。

参考文章

JAVA安全基础(二)-- 反射机制

JAVA反射机制瞎扯篇

Java-反射

Java 反射机制 · 攻击Java Web应用-[Java Web安全]

深入理解Java反射机制

Java安全_反射

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

什么是反射

正向考虑

在java实际应用为什么要有反射

定义是:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用方法的功能成为反射机制。

正常在Java中,对象的类型是在编译期就确定下来的,而Java反射机制就可以在对象类型未知的情况下,动态的创建对象并调用其属性

举个栗子深入浅出

Test ts = new Test();\\正射,在编译的时候就知道这个对象的类型是Test
Class clz = Class.forname("类的全限定名");Object ts = clz.newInstance();\\反射,动态获取类的对象

这里new Student和clz.newInstance是等价的,后面再进行解释具体什么是什么

通过上面就可以理解一些实际用途如:

  • 框架和库开发:许多框架和库需要在不了解客户端代码的情况下与各种类和对象进行交互。反射允许这些框架在运行时检查、加载和操作客户端提供的类。

  • 插件系统:开发插件式应用程序时,你可能不知道哪些插件将被安装和运行。反射可用于在运行时加载和实例化插件类。

  • 配置和扩展性:通过配置文件或其他外部资源指定类名,反射允许你动态地创建对象。这对于实现可配置性和可扩展性非常有用。

  • 单元测试和模拟:在单元测试中,你可以使用反射来创建对象、调用私有方法和设置私有字段的值,以便模拟测试环境。

  • 数据库访问和ORM框架:许多ORM(对象关系映射)框架使用反射来将数据库表的行映射到Java对象,以便进行数据库操作。

  • 注解处理器:反射用于处理注解,例如自定义注解的处理器,以执行特定的操作。

  • 动态代理:反射可用于创建动态代理对象,允许你在运行时生成实现特定接口的代理类,以实现横切关注点(例如日志、性能监控等)。

说通俗一点:一个游戏需要更新一些配置总不能让用户把这个游戏卸载了再重新安装把

反向考虑

在Java安全中可以用反射做些什么

Java反射机制可以无视类方法、变量访问权限修饰符,可以调用任何类的任意方法、访问并修改成员变量值。不可访问也变的可以访问了,也就是说只要发现一处Java反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能控制反射的类名、方法名和参数。

反射组成的相关类

反射机制相关操作一般位于java.lang.reflect包中

image-20231014121558574

java反射机制组成需要重点注意以下的类:

java.lang.Class:类对象;

java.lang.reflect.Constructor:类的构造器对象;

java.lang.reflect.Field:类的属性对象;

java.lang.reflect.Method:类的方法对象;

反射的源头_Class对象

想要理解反射首先需要知道Class这个类,它的全称是java.lang.Class类。java是面向对象的语言,讲究万物皆对象,即使强大到一个类,它依然是另一个类(Class类)的对象,换句话说,普通类是Class类的对象,即Class是所有类的类(There is a class named Class)。

我们知道java世界是运行在JVM之上的,我们编写的类代码,在经过编译器编译之后,会为每个类生成对应的.class文件,这个就是JVM可以加载执行的字节码。

运行时期间,当我们需要实例化任何一个类时,JVM会首先尝试看看在内存中是否有这个类,如果有,那么会直接创建类实例;如果没有,那么就会根据类名去加载这个类,当加载一个类,或者当加载器(class loader)的defineClass()被JVM调用,便会为这个类产生一个Class对象(一个Class类的实例),用来表达这个类,该类的所有实例都共同拥有着这个Class对象,而且是唯一的。

也就是说,加载.class文件之后会生成一个对应的Class对象。

Snipaste_2023-10-14_16-33-26

Class是java.lang.Class类中,是所有类的的类。而类是java.lang.Class类的实例对象,所以反射就是得到Class对象后反向回去Test对象的各种信息。

获取Class对象的方法

对于普通的类我们这样创建实例

Test ts = new Test();

而我们在创建class类的实例对象却不能使用上述方法,运行会抛出错误

image-20231014172717838

跟进Class类的源码发现其构造器是私有的,所以只有JVM才能够创建Class对象

image-20231014172914659

因为Class类是private私有属性,我们也无法通过创建对象的方式来获取class对象,那么我们怎样才能够获取到class对象呢?

第一种"类名.class"的方式获取

比如有个类叫Test

public class MyClass {  }

那么可以通过

Class clz = Test.class

第二种通过类创建的实例对象的getClass方法取得

Test ts = new Test();
Class clz = ts.getClass();

第三种Class.forName(String className):动态加载类

第三种则是调用Class类中的forName方法,将字节码文件加载进内存,返回Class对象。

Class clz = Class.forName("cmo.ref.Test");

写个简单的demo来演示分别利用这三种方法获取当前类Class对象的当前类名。

package cmo.ref;
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 类的.class属性
        Class clz1 = Test.class;
        System.out.println(clz1.getName());
        // 实例化对象的getClass()方法
        Test demo = new Test();
        Class clz2 = demo.getClass();
        System.out.println(clz2.getName());
        // Class.forName(String className): 动态加载类
        Class clz3 = Class.forName("cmo.ref.Test");
        System.out.println(clz3.getName());
    }
    }
​
​
​

image-20231014175440183

但在这三种获取CLass类方式中,我们一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。

而使用类.class属性,需要导入类的包,依赖性太强,在大型项目中容易抛出编译错误;

而使用实例化对象的getClass()方法,需要本身创建一个对象,本身就没有了使用反射机制意义。

所以我们在获取class对象中,一般使用Class.forName方法去获取。

获取成员变量Field

获取成员变量Field位于java.lang.reflect.Field包中

Field[] getFields() :获取所有public修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符

Field getField(String name) 获取指定名称的 public修饰的成员变量

Field getDeclaredField(String name) 获取指定的成员变量

获取成员方法Method

Method getMethod(String name, 类<?>... parameterTypes) //返回该类所声明的public方法

Method getDeclaredMethod(String name, 类<?>... parameterTypes) //返回该类所声明的所有方法

//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型

Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

Method[] getDeclaredMethods() // 获取该类中的所有方法

获取构造函数Constructor

Constructor<?>[] getConstructors() :只返回public构造函数

Constructor<?>[] getDeclaredConstructors() :返回所有构造函数

Constructor<> getConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的public构造函数

Constructor<> getDeclaredConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的构造函数

通过反射创建类对象

在前面我们获取了Class对象,之后展示了一系列获取成员变量、成员方法和成员函数的方式后,我们现在可以通过反射来生成实例化对象,一般我们使用Class对象的newInstance()方法来进行创建类对象。

使用的方式也特别简单,只需要通过forname方法获取到的class对象中进行newInstance方法创建即可。

Class c = Class.forName("com.reflect.MethodTest"); // 创建Class对象
Object m1 =  c.newInstance(); // 创建类对象

invoke

通过上面的讲解,现在已经可以获得Class对象,创建类的实例对象,获得成员方法现在就差调用方法了

这就涉及到invoke方法,invoke方法位于java.lang.reflect.Method类中用于执行某个的对象的目标方法。 一般会和getMethod方法配合进行调用。

使用用法

public Object invoke(Object obj, Object... args)

第一个参数为类的实例,第二个参数为相应函数中的参数

obj:从中调用底层方法的对象,必须是实例化对象

args: 用于方法的调用,是一个object的数组,参数有可能是多个

但需要注意的是,invoke方法第一个参数并不是固定的:

  • 如果这个方法是一个普通方法,那么第一个参数是类对象

  • 如果这个方法是一个静态方法,那么第一个参数是类

这样就可以出来一个小demo了(借鉴

package cmo.ref;
import java.lang.reflect.Method;
public class Test {
    public void reflectMethod() {
        System.out.println("反射测试成功!!!");
    }
    public static void main(String[] args) {
        try {
            Class clz = Class.forName("cmo.ref.Test");// 创建Class对象
            Object m = clz.newInstance(); // 创建类实例对象
            Method method = clz.getMethod("reflectMethod"); // 获取reflectMethod方法
            method.invoke(m); // 调用类实例对象方法
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

利用反射构造Runtime类执行

为什么要特意提到Runtime类因为这个类里有个'exec'方法可以执行命令

image-20231015114246584

到这要重新回溯一下讲一下class.newInstance()

它的作用是就是调用这个类的无参构造函数,但是如果要出错了那就要注意:

  • 你使用的类是不是没有无参构造函数

  • 你使用的类构造函数是不是私有的

这就知道了为什么不能直接这样构造执行命令

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");

image-20231015114825539

image-20231015115113900

因为Runtime类的构造方法是私有的,是根据“单例模式”进行设计的,我们无法进行直接调用。但是同时也可以发现可以通过Runtime.getRuntime()来获取到Runtime对象

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

image-20231015115559426

getMethod 的作用是通过反射获取一个类的某个特定的公有方法。Java中 支持类的重载,我们不能仅通过函数名来确定一个函数。所以,在调用 getMethod 的时候,我们需要 传给他你需要获取的函数的参数类型列表。 比如这里的 Runtime.exec 方法有6个重载:

image-20231015115744005

我们可以实用最简单这种只有一个参数且类型是String。实用getMethod("exec",String.class)获取Runtime.exec方法,并且获取getRuntime方法来执行。

setAccessible(true)暴力访问权限

刚才我们发现如果一个方法或构造方法是私有方法,那么我们不嗯呢该利用反射机制对其进行操作,那么有没有一些特殊情况可以达到呢?

getDeclared 系列的反射

首先说明getDeclared 系列的反射了,与普通的getMethod 、getConstructor 区别是:getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法 getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了。 getDeclaredMethod 的具体用法和 getMethod 类似, getDeclaredConstructor 的具体用法和 getConstructor 类似

Java.lang.reflect.AccessibleObject类是Field,Method和Constructor类对象的基类,可以提供将反射对象标记为使用它抑制某人Java访问控制检查的功能,同时上述的反射类中的Field,Method和Constructor继承自AccessibleObject。所以我们在这些类方法基础上调用setAccessible()方法,既可对这些私有字段进行操作。

所有我们最开始的代码再修改简略下,就能成功命令执行了。

import java.lang.reflect.Constructor;
​
public class Test {
    public static void main(String[] args) throws Exception {
​
        Class clazz = Class.forName("java.lang.Runtime");
        Constructor m = clazz.getDeclaredConstructor();
        m.setAccessible(true);
        clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
​
    }
    }

ProcessBuilder

如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?

看p牛的

我们需要用到一个新的反射方法 getConstructor 。 和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。 比如,我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用 start() 来执行命令:

Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)
clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).star
t();
​

ProcessBuilder有两个构造函数:

public ProcessBuilder(List command)

public ProcessBuilder(String... command)

上面用到了第一个形式的构造函数,所以我在 getConstructor 的时候传入的是 List.class 。 但是,我们看到,前面这个Payload用到了Java里的强制类型转换,有时候我们利用漏洞的时候(在表 达式上下文中)是没有这种语法的。所以,我们仍需利用反射来完成这一步。

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(
Arrays.asList("calc.exe")));

通过 getMethod("start") 获取到start方法,然后 invoke 执行, invoke 的第一个参数就是 ProcessBuilder Object了。

那么,如果我们要使用 public ProcessBuilder(String... command) 这个构造函数,需要怎样用反 射执行呢? 这又涉及到Java里的可变长参数(varargs)了。正如其他语言一样,Java也支持可变长参数,就是当你 定义函数的时候不确定参数数量的时候,可以使用 ... 这样的语法来表示“这个函数的参数个数是可变 的”。 对于可变长参数,Java其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价 的(也就不能重载):

public void hello(String[] names) {}

public void hello(String...names) {}

也由此,如果我们有一个数组,想传给hello函数,只需直接传即可:

String[] names = {"hello", "world"};
​
hello(names);

那么对于反射来说,如果要获取的目标函数里包含可变长参数,其实我们认为它是数组就行了。 所以,我们将字符串数组的类 String[].class 传给 getConstructor ,获取 ProcessBuilder 的第二 种构造函数:

Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)

在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给 ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:

Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new
String[][]{{"calc.exe"}})).start();

后记

总算是java安全开始入门了,道阻且长,行将则至。

参考文章

JAVA安全基础(二)-- 反射机制

JAVA反射机制瞎扯篇

Java-反射

Java 反射机制 · 攻击Java Web应用-[Java Web安全]

深入理解Java反射机制