在经历一系列面试之后,发现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存在函数重载,第一个参数是类名,第二个参数是是否初始化,第三个参数是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跳过对私有方法访问权限的检查,使私有方法也可以被从外部访问