Java代码审计基础—反射(渗透测试)

4.2 反射

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

1.获取class类的几种方式

  • 1. class.forName(“全类名”),如果你知道某个类的名字,想获取到这个类,就可以使⽤用forName 来获取
  • 2. 类名.class,如果你已经加载了某个类,只是想获取到它的java.lang.Class 对象,那么就直接拿它的class 属性即可。这个方法其实不属于反射。
  • 3. 对象.getClass(),如果上下文存在某个实例对象,可以通过getClass获取他的类
  • 4.ClassLoader.getSystemClassLoader().loadClass(“全类名”)

forName有两个函数重载:

Class<?> forName(String name)
Class<?> forName(String name, boolean initialize, ClassLoader  loader)
  • 第一个就是我们最常见的获取class的方式,其实可以理解为第二种方式的一个封装:
Class.forName(className)
// 等于
Class.forName(className, true, currentLoader)

默认情况下, forName 的第一个参数是类名第二个参数表示是否初始化第三个参数就是ClassLoader

  • 使用功能 “.class” 来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象
  • static {} 就是在“类初始化”的时候调用的,而{} 中的代码会放在构造函数的super()后面,但在当前构造函数内容的前面。
  • forName 中的initialize=true 其实就是告诉Java虚拟机是否执行”类初始化“。

2.获取类之后的方法

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

3.类“初始化”执行顺序是什么

public class test {
    public static void main(String[] args) {
        Ref ref = new Ref();
    }
}

class Ref{
    static {
        System.out.println("最先执行\r\n");
    }
    {
        System.out.println("第二执行\r\n");
    }

    public Ref(){
        System.out.println("最后执行\r\n");
    }
}

其中, static {} 就是在“类初始化”的时候调⽤用的,而{} 中的代码会放在构造函数的super() 后面,但在当前构造函数内容的前面。

4.class.newInstance()
作用就是调用这个类的无参构造函数,我们有时候在写漏洞利用方法的时候,会发现使用newInstance 总是不成功,这时候原因可能是:

  • 1. 你使用的类没有无参构造函数
  • 2. 你使用的类构造函数是私有的

最常见的情况就是java.lang.Runtime,不能直接newInstance(),原因是Runtime 类的构造方法是私有的。因为Runtime类是单例模式,我们只能通过Runtime.getRuntime() 来获取到Runtime 对象,所以正常的payload如下:

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

5.getMethod()
通过反射获取一个类的某个特定的公有方法,因为java中类的重载,我们不能只通过一个方法名来确定一个函数,在调用getMethod 的时候,我们需要
传给他你需要获取的函数的参数类型列表。
比如的Runtime.exec 方法有6个重载:
在这里插入图片描述

6.invoke()
invoke的作用是执行方法,它的第一个参数是:

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

这也比较好理解了,我们正常执行方法是[1].method([2], [3], [4]…) ,其实在反射里就是method.invoke([1], [2], [3], [4]…)
所以上面的exec的代码拆分开即如下代码。

Class clazz = Class.forName("java.lang.Runtime");
Method exec = clazz.getMethod("exec", String.class);
Method getRuntime = clazz.getMethod("getRuntime");
Object invoke = getRuntime.invoke(clazz);        //这里的clazz就是我们第一步反射回来的  类
exec.invoke(invoke,"calc");          //这里的invoke就是上面通过clazz类创建出来的 类对象

7.class类的其他方法

  • 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,那么我们可以通过getConstructor()指定需要确定的构造函数
  • 如果一个方法或构造方法是私有方法,我们可以通过getDeclaredMethod 系列方法获取的是当前类中**“声明”**的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了

获取构造方法

cls3.getConstructor("aaa");        //返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。
cls3.getConstructors();           //返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。
cls3.getDeclaredConstructor("bbb");    //返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。
cls3.getDeclaredConstructors();        //返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组 类 。

获取方法

cls3.getMethod("setName", String.class);        //返回一个 方法对象,getMethod会返回当前类和父类的所有public方法
cls3.getMethods();       //返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。
cls3.getDeclaredMethod("setName", String.class);        //返回一个 方法对象,getDeclaredMethod返回的是当前的所有方法
cls3.getDeclaredMethods()        //返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。

获取成员变量

cls3.getField("aaa");        //返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象。
cls3.getFields();            //返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。
cls3.getDeclaredField("aaaa");        //返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 类对象。
cls3.getDeclaredFields();        //返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。

二、调用实验
User.java

public class User {
    private String name;
    private int age;

    static {
        System.out.println("静态");
    }

    {
        System.out.println("无函数");
    }

    public User(){
        System.out.println("无参构造");
    }

    public User(String name,int age) {
        this.name=name;
        this.age=age;
        System.out.println("有参构造");
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name=name;
    }

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

CreateObject.java

Class UserClass = Class.forName("com.huawei.reflection.User");     //只调用了静态构造方法

在这里插入图片描述

Class UserClass = Class.forName("com.huawei.reflection.User");
User user = (User) UserClass.newInstance();            //调用无参构造方法

在这里插入图片描述

Class UserClass = Class.forName("com.huawei.reflection.User");
Constructor constructor= UserClass.getDeclaredConstructor(String.class,int.class);       //指定构造方法为有参构造,且参数为String和int的
User user = (User) constructor.newInstance("haha",18);            //调用有参构造函数创建对象

在这里插入图片描述

Class UserClass = Class.forName("com.huawei.reflection.User");
User user = (User) UserClass.newInstance();
Method setName = UserClass.getDeclaredMethod("setName", String.class);            //获取UserClass类的setname方法
setName.invoke(user,"yoyoy");                //通过上一步的setName方法,对上面创建的user对象进行调用,并且String值为yoyoy,此时user对象的name值被赋予为yoyoy
System.out.println(user.getName());        //通过user的getName方法展示出name值

在这里插入图片描述

**//实验修改原有对象的属性值**
User xiaoming = new User("xiaoming", 18);       //创建一个xiaoming对象
Class<? extends User> userclass = xiaoming.getClass();      //反射获取User类
Field name = userclass.getDeclaredField("name");    //获取User类的name属性
name.setAccessible(true);      //因为name属性为private私有属性,需要修改作用域
name.set(xiaoming,"xiaoli");    //修改原有对象xiaoming的name属性为xiaoli
System.out.println(xiaoming.getName());

在这里插入图片描述

三、调用实验2
问题:如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?
1、ProcessBuilder选择List.class构造函数进行反射执行命令

//ProcessBuilder反射利用
Class process = Class.forName("java.lang.ProcessBuilder");
ProcessBuilder calc = (ProcessBuilder) process.getConstructor(List.class).newInstance(Arrays.asList("calc"));
calc.start();

如上需要进行强转,有时候我们利用漏洞的时候(在表达式上下文中)是没有这种语法的。所以,我们仍需利用反射来完成这一步

Class process = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = process.getConstructor(List.class);
Object calc = constructor.newInstance(Arrays.asList("calc"));
Method start = process.getMethod("start");
start.invoke(calc);

2、ProcessBuilder选择String[].class构造函数进行反射执行命令
其中在newInstance时,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以要写成new String[][]{{“calc”}}

Class process = Class.forName("java.lang.ProcessBuilder");
Constructor constructor1 = process.getConstructor(String[].class);
Object calc2 = constructor1.newInstance(new String[][]{{"calc"}});
Method start1 = process.getMethod("start");
start1.invoke(calc2);

问题:如果一个方法或构造方法是私有方法,我们是否能执行它呢?
比如Runtime这个类的构造函数时私有方法,我们当时只能通过Runtime.getRuntime() 来获取对象,其实我们可以通过getDeclaredConstructor 来获取这个私有的构造方法来实例化对象。
其中我们用到了setAccessible这个方法,这个是必须的。我们在获取到一个私有方法后,必须用setAccessible 修改它的作用域,否则仍然不能调用。

Class runtime = Class.forName("java.lang.Runtime");
Constructor declaredConstructor = runtime.getDeclaredConstructor();
declaredConstructor.setAccessible(true);    //修改作用域
Method exec = runtime.getMethod("exec", String.class);
exec.invoke(declaredConstructor.newInstance(),"calc");

四、反射调用无关键字的命令执行
Java 反射机制实现无关键字执行命令

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;

public class ReflectionTest {
    public static void exec() {
        try {
            System.out.println(Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "curl -i localhost:8000"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            String str = "whoami";

            // java.lang.Runtime
            String runtime = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});

            // Runtime.class
            Class<?> c = Class.forName(runtime);

            // 获取getRuntime方法,Runtime.getRuntime()
            Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));

            // 获取Runtime的exec方法,rt.exec(xxx)
            Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);

            // Runtime.getRuntime().exec(str)
            Object obj2 = m2.invoke(m1.invoke(null), str);

            // 获取命令执行结果Process类的getInputStream()方法
            Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));
            m.setAccessible(true);

            // process.getInputStream()
            InputStream in = (InputStream) m.invoke(obj2, new Object[]{});

            // 输出InputStream内容到
            Scanner scanner = new Scanner(in).useDelimiter("\\A");
            System.out.println(scanner.hasNext() ? scanner.next() : "");
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

}

参考:
P牛《Java安全漫谈》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thunderclap_

点赞、关注加收藏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值