前言
类似于java版的__proto__
(?
虽然学校专业已经教了java的基础内容,但是对于安全方面并没有什么介绍
于是在各种ctf比赛被java题拷打了无数遍555,所以接下来开始对java安全进行一个比较系统的学习
我这里尚不清楚java的学习路线,那么就先从java反射开始学习摸索一下
据说java反序列化漏洞就是从反射开始的
概述
正射
我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射
通俗点就是主动去new
一个类
Student student = new Student();
student.doHomework("数学");
反射
一开始不知道我们要初始化的类对象是什么的时候,那么我们就需要运用反射,通过反射我们可以获取这个类的原型
这里可以联想到nodejs中的原型链,通过某个特定方法来调用反射以获取其原型
通俗点就是逆着正射获取一开始的类对象
以上面的代码为例:正射是Student -> student
,反射就是student -> Student -> 类中的所有信息(包括成员变量,成员方法,构造器等)
因为反射就相当于来到了类的内部,所以通过反射我们能直接操作类的私有属性,并且可以操纵类的字段、方法、构造器等部分
总之对象可以通过反射获取它的类,而类可以通过反射拿到所有的方法(包括私有的),拿到的方法可以调用
于是java这种静态语言就可以拥有动态特性
一段代码,改变其中的变量,将会导致这段代码产生功能性的变化,我称之为动态特性 —— phith0n
反射的调用
反射里有几个极为重要的方法:
- 获取类的方法:
forName
- 实例化类对象的方法:
newInstance
- 获取函数的方法:
getMethod
- 执行函数的方法:
invoke
这几个方法基本上包揽了java安全中各种和反射有关的payload
获取类
obj.getClass
如果上下文中存在某个类的实例obj,那么我们可以调用
obj.getClass()
方法来获取它的类,即:对象.getClass()
Person p = new Person();
Class clazz = p.getClass();
注:此处使用的是Object类中的getClass()方法,因为所有类都继承Object类,所以调用Object类中的getClass()方法来获取
本地测试:
class Person03 {
String name; // 声明姓名属性
int age; // 声明年龄属性
public void tell() { // 取得信息的方法
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class Test {
public static void main(String[] args) {
Person03 per = new Person03();
per.name = "张三";
per.age = 30;
per.tell();
System.out.println(per.getClass());
}
}
可以看到确实返回了被调用的类的类名
(不过我寻思这里对象都有了还要反射干什么)
Test.class
调用类的class属性类获取该类对应的
java.lang.Class
对象,即:类名.class
这里需要导入类的包才可用,不过这个方法本质上其实不属于反射,因为此时已经加载了这个类
Class clazz = Person.class;
本地测试:
class Person03 {
String name; // 声明姓名属性
int age; // 声明年龄属性
public void tell() { // 取得信息的方法
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class Test {
public static void main(String[] args) {
Person03 per = new Person03();
per.name = "张三";
per.age = 30;
per.tell();
Class personClass = Person03.class;
System.out.println(personClass);
}
}
forName
使用Class类中的
forName()
静态方法,即:Class.forName(“类的全路径”)
Class clazz = Class.forName("className"); // 里面要填:类所在的包名+类名
// 等价于
Class clazz = Class.forName("className", true, currentLoader);
forName
的第一个参数是类名;
第二个参数表示是否进行类的初始化,值得注意的是.class
创建对Class对象的引用时不会自动初始化该Class对象,而使用forName()
会自动初始化该Class对象;
第三个参数ClassLoad
是一个“加载器”,告诉Java虚拟机如何加载这个类,默认是根据类名来加载类,这个类名是类完整路径java.lang.Runtime
,关于这个点其实有不少漏洞利用的方法,这里先不展开
本地测试:(不知道是不是版本的原因,我这里必须throws处理错误才能使用forName)
test1.java
package test;
public class test1 {
public static void main(String[] args) throws Exception {
Class p = Class.forName("test.phone");
System.out.println(p);
}
}
phone.java(这里直接用了参考文章的代码,没有特殊说明的话接下来的测试代码phone.java部分都是这个)
package test;
public class phone {
public String name;
public double weight;
public phone(){
}
public phone(String name,double weight){
this.name=name;
this.weight=weight;
}
public void dianyuan(){ //定义一个无返回值的方法,调用会打印"开机"
System.out.println("开机");
}
public void setName(String name){ //定义一个形参为String类型的方法,调用后给name属性赋值
this.name=name;
}
public String getName(){ //定义一个调用后返回name属性的值的方法
return name;
}
public void setWeight(double weight){ //定义一个形参为double类型的方法,调用后给weight属性赋值
this.weight=weight;
}
public double getWeight(){ //定义一个调用后返回weight属性的值的方法
return weight;
}
}
运行test1.java得到
当我们获得了想要操作的类的Class对象后,可以通过Class类中的方法获取和查看该类中的方法和属性
//获取包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
//获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
//获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
//获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
//反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(222,"韦小宝");//执行有参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法
//反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
//反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
接下来的部分就是对上述的方法进行分析与测试
获取实例化对象
通过class的
newInstance()
方法Class p=Class.forName("类的全路径"); Object p1=p.newInstance(); //这里也有另一种写法,区别是要进行强制类型转化 Class p=Class.forName("类的全路径"); phone p1=(phone)p.newInstance();
本地测试
test1.java
package test; public class test1 { public static void main(String[] args) throws Exception { Class p=Class.forName("test.phone"); Object p1=p.newInstance(); System.out.println(p1); } }
通过constructor的
newInstance()
方法Class p=Class.forName("类的全路径"); Constructor constructor=p.getConstructor(); Object p1=constructor.newInstance();
这里一开始会觉得疑惑,因为和第一种方法比起来这里只是多了个调用无参构造方法的过程
但是因为class的
newInstance()
方法需要我们类中存在无参的构造方法,它通过无参的构造方法来实例化,而一旦我们类中不存在无参构造方法,那么第一种方法就不行了
获取类的构造器/构造方法
获取public类型的构造器:
getConstructor(class[]parameterTypes)
Class p=Class.forName("test.phone"); Constructor constructor=p.getConstructor();
获取全部public类型的构造器:
getConstructors()
Class p=Class.forName("test.phone"); Constructor[] constructor=p.getConstructors(); // 注意这里要用数组,因为全部构造器可能并不只有一个
获取public和private类型的构造器:
getDeclaredConstructor(class[]parameterTypes)
上面的两种方法是无法获取private类型的构造方法的
Class p=Class.forName("test.phone"); Constructor constructor=p.getDeclaredConstructor();
获取全部类型的构造器:
getDeclaredConstructors()
Class p=Class.forName("test.phone"); Constructor[] constructor=p.getDeclaredConstructors();
仅测试最后一个:
test1.java
package test; import java.lang.reflect.Constructor; public class test1 { public static void main(String[] args) throws Exception { Class p = Class.forName("test.phone"); Constructor[] constructor = p.getDeclaredConstructors(); for(int i=0;i<constructor.length;i++){ System.out.println(constructor[i]); } } }
获取类的属性
获取类的一个public类型属性:
getField(String name)
Class p=Class.forName("test.phone"); Field f=p.getField("name");
获取类的一个全部类型的属性:
getDeclaredField(String name)
Class p=Class.forName("test.phone"); Field f=p.getDeclaredField("weight");
获取类的全部public类型的属性:
getFields()
Class p=Class.forName("test.phone"); Field[] f=p.getFields(); //同样要注意改成数组
获取类的全部类型的属性:
getDeclaredFields()
Class p=Class.forName("test.phone"); Field[] f=p.getDeclaredFields(); //同样要注意改成数组
这里就测试最后一个:(
不一样应该是因为版本特性)test1.java
package test; import java.lang.reflect.Constructor; import java.text.DateFormat.Field; public class test1 { public static void main(String[] args) throws Exception { Class p=Class.forName("test.phone"); java.lang.reflect.Field[] f=p.getDeclaredFields(); //同样要注意改成数组 for(int i=0;i<f.length;i++){ System.out.println(f[i]); } } }
获取类的方法
获取类的一个特定public类型的方法:
getMethod(String name,class[] parameterTypes)
Class p=Class.forName("test.phone"); Method m=p.getMethod("setName", String.class); //要注意这里有两个参数,后面要传入的是方法形参的类型的原型,无参函数就不用填
获取类的一个特定无论什么类型的方法:
getDeclaredMethod(String name,class[] parameterTypes)
Class p=Class.forName("test.phone"); Method m=p.getDeclaredMethod("setName", String.class);
获取类的全部public的方法:
getMethods()
Class p=Class.forName("test.phone"); Method[] m=p.getMethods();//要注意改成数组
获取类的全部类型的方法:
getDeclaredMethods()
Class p=Class.forName("test.phone"); Method[] m=p.getDeclaredMethods(); //同样要注意改成数组
同样仅测试最后一种:
test1.java
package test; import java.lang.reflect.Method; public class test1 { public static void main(String[] args) throws Exception { Class p = Class.forName("test.phone"); Method[] m = p.getDeclaredMethods(); for (int i = 0; i < m.length; i++) { System.out.println(m[i]); } } }
利用Runtime类执行命令
Java中的一个系统类,一般会写作
java.lang.Runtime
,它封装了应用程序运行时的环境,并提供了一些与运行时环境相关的方法
- exec(String cmd): 在单独的进程中执行指定的命令或程序。
- availableProcessors(): 返回当前系统的CPU数量。
- totalMemory()和freeMemory():返回Java虚拟机中的总内存和空闲内存。
- addShutdownHook(Thread hook): 注册一个线程,在JVM关闭时会自动执行该线程的代码。
- halt(int status): 终止当前JVM,并返回一个指定的状态码。
我们可以发现这里的exec()
方法明显能用来进行命令执行
利用反射可以实现弹计算器(注:jdk版本要小于9)
Class p = Class.forName("java.lang.Runtime");
Constructor constructor = p.getDeclaredConstructor(); // 调用私有构造器
constructor.setAccessible(true); // 修改作用域
Method m = p.getMethod("exec", String.class); // 获取exec方法
Object o = constructor.newInstance(); // 实例化对象
m.invoke(o, "calc"); // 调用exec方法,执行calc命令
利用ProcessBuilder执行命令
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();