java安全学习-反射

发布于 2022-07-17  654 次阅读


在经历一系列面试之后,发现java安全已经是个逃不开的话题了。因此在这学期开始了Java安全相关的学习。

在男神yyz的指导下,我也开始跟随着p神学习起了java安全,就当做个笔记

反射概述

java的反射从官方文档来看,它是Java语言的一种特性,它可以让Java程序在运行过程中检查、操作程序的内部属性。例如,它可以让一个Java类获取并显示其所有的成员变量名称。

什么意思呢?

java的反射让java能够在运行的过程中实现对一个未知类的调用,使其能够在无需实例化某个类的情况下完成对类中的方法的调用。

反射给java这种静态语言赋予了动态特征,让代码更灵活的同时也带来了代码的安全问题。

例子

public class xm {
    public String name="lihua";
    public void get_name(){
        System.out.print("yes,you get my name:"+name+"\n");
    }
}
//被调用的类,xm
import java.lang.reflect.InvocationTargetException;

public class test {
    public static void main(String[] args) throws Exception{
        execute("xm", "get_name");
    }

    public static void execute(String a, String b) throws Exception{
            Class clazz = null;
            clazz = Class.forName(a);
            clazz.getMethod(b).invoke(clazz.newInstance());
    }
}
//反射实现

运行结果:

发现我们并没有实例化xm这个类,但是我们依然调用了其中的属性和方法。我们会发先,我们在test中并没有import xm这个类,但我们还是调用了它,这个特性对我们攻击者也是十分有利的

反射方法

反射中极为重要的方法:

  • 获取类的方法:forName
  • 实例化类对象的方法:newInstance
  • 获取函数的方法:getMethod
  • 执行函数的方法:invoke
class.forName(name)
class.forName(name,true,currentLoader)
forName源码

可见forName存在函数重载,第一个参数是类名,第二个参数是是否初始化,第三个参数是classloader

反射的初始化

我们将xm类代码修改一下

public class xm {
    {
        System.out.println("括号");
    }
    static {
        System.out.println("static");
    }
    public String name="lihua";
    public void get_name(){
        System.out.print("yes,you get my name:"+name+"\n");
    }
}

运行test

可见,static{}是最先运行的,其次是{},最后才是调用的函数。

其实static{}是类的”类初始化”时调用的,也就是最先调用,因此我们可以在恶意代码放在static里面执行

单例模式的反射

当我们想用exec函数执行命令时会发现一个问题

public class test {
    public static void main(String[] args) throws Exception{
    Class<?> clazz=Class.forName("java.lang.Runtime");
    clazz.getMethod("exec", String[].class).invoke(clazz.newInstance(),"clac.exe");
    }
}

执行结果

报错了,我们看看runtime类的源代码

它的构造方法是私有的,也就是我们呢没办法直接构造这个类,需要他的一个静态方法来构造

也就是代码应该如下

import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws Exception{
    Class<?> clazz=Class.forName("java.lang.Runtime");
    Method execMethod=clazz.getMethod("exec", String.class);
    Method getRuntimeMethod = clazz.getMethod("getRuntime");
    Object runtime = getRuntimeMethod.invoke(clazz);//只需要类
    execMethod.invoke(runtime,"calc.exe");
    }
}

值得一提的是invoke是用于执行方法:

  • 如果方法是非静态方法,那么第一个参数就是类对象
  • 如果方法是静态方法,那么第一个参数就是类

有参构造的反射

在有参构造中反射是通过getConstructor()实现的,括号内跟上参数是类型。因为构造方法是支持重载的,因此我们需要传入类型来确定唯一的一个构造方法

在ProcessBuillder中有两个构造函数

  • public ProcessBuilder(List command)
  • public ProcessBuilder(String… command)

我们就需要传入List.class来告诉java我们想要调用第一个构造方法

可变长参数的构造方法

如果我们想用string的构造方法来进行构造,那我们就需要注意的是newinstance本身就是就收的可变参数,因此在这种情况下 在newinstance中就应该传入二维数组,就相当于两个一维数组叠加成了一个二维数组

私有方法或私有构造方法的调用

私有相关的方法就会涉及到declared相关的一系列方法,比如getDeclaredMothed,getDeclaredconstructor等等,这种方法的意思就是获取类中所有方法或者构造方法,但是不会获取从父类继承来的public方法。

setAccessible的值默认为false,改成true会使java跳过对私有方法访问权限的检查,使私有方法也可以被从外部访问