乘风破浪会有时,直挂云帆济沧海
什么是序列化和反序列化?
为了java对象在网络介质中传输,将其转化为字节流,并从字节流转化为对象的过程。
下面举个栗子感受一下:
1.首先需要创建一个被操作的类的对象
编写一个people类:
import java.io.Serializable;
public class People implements Serializable {
private String name;
private int age;
public People(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
这是一个普通的people类,其中包含name和age属性,构造方法,get,set方法
不同的是,这个类实现了Serializable接口,只有实现了该接口的类才能够被序列化和反序列化。
2.序列化people类
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
public class Serilizerable {
public static void main(String[] args) throws Exception{
People people = new People("Marry", 20);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("p1.ser"));
out.writeObject(people);
}
}
上述代码逻辑:
实例化people类;创建一个文件接受序列化数据;将people类序列化输出到p1.ser
执行完成后,在IDEA右边栏可以看到生成了一个p1.ser文件,即序列化成功
3.反序列化p1.ser
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class unSerilizerable {
public static void main(String[] args) throws Exception{
ObjectInputStream in = new ObjectInputStream(new FileInputStream("p1.ser"));
People p1 = (People)in.readObject();
System.out.println(p1);
}
}
上述代码逻辑:
读取p1.ser文件字节流并创建一个people对象接收,输出对象p1.
执行结果如下:
也可以使用p1调用people的get方法:
4.在反序列化过程中为什么会导致安全问题?
由于序列化的数据是用户可控的,导致可能会出现安全问题。
这里举一个例子(people类重写了readObject函数,该函数本身存在危险函数)
上述情况基本不会在实际情况中出现,此处仅举例使用。
private void readObject(java.io.ObjectInputStream a) throws Exception{
java.lang.Runtime.getRuntime().exec("calc.exe");
}
在people类中添加上述方法后,反序列化执行一下:
成功弹窗!
除了上述people类直接调用readObject方法,并且方法中存在危险函数的情况,还可能有一下三种可能。
1.入口类中调用包含了可控类,这个类中有危险方法,readObject时调用;
2.入口类中包含可控类,可控类中调用了其他类的危险方法,readObject时调用;
3.构造函数或者静态代码加载时隐式执行。
以上总结参考白日梦组长视频,也是我java安全的开始,感谢组长。
接下来学习利用URLDNS链的前置知识——反射。
什么是反射?
反射就像一面镜子,映照出类的属性和方法等,可以在运行时动态的修改它们的值以及调用类的方法。
我们还是用people类的例子来看。
people.java代码:
import java.io.IOException;
import java.io.Serializable;
public class People implements Serializable {
private String name;
private int age;
public People(String name, int age){
this.name = name;
this.age = age;
}
public People(){}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void action(String name){
System.out.println("I bit "+name+"'s ass");
}
}
反射的基础是Class类,它用于封装到被装入JVM中的类的信息,当一个类被装入到JVM中时会产生一个与之关联的java.lang.Class对象,通过这个Class对象对被装入的类的详细信息进行访问。
Reflection.java代码:
Constructor ppconstructor = clazz.getConstructor(String.class, int.class); //传入有参构造方法的类型的calss类的对象
People p2 = (People) ppconstructor.newInstance("Marry", 11); //传值
System.out.println(p2);
//获取类的属性
Field ppfield = clazz.getDeclaredField("name"); //获取name属性,getDeclaredField表示获取(私有)属性,getDeclaredFields表示所有属性
ppfield.setAccessible(true); //获取私有属性时设置
ppfield.set(p2, "Joe"); //赋值
System.out.println(p2);
//获取类的方法
Method method = clazz.getDeclaredMethod("action", String.class); //根据方法名获取类的方法,String.class表示方法参数类型
method.invoke(p2, "eat"); //赋值
Method[] methods = clazz.getDeclaredMethods(); //获取类的所有方法
for(Method m: methods){
System.out.println(m);
}
输出:
可以看到,这里已经为类的属性成功赋值,并获取了类中的方法。