Java接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
Animal.java 文件代码:
/* 文件名 : Animal.java */
interface Animal {
public void eat();
public void travel();
}
MammalInt.java 文件代码:
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
Java 序列化/反序列化
在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)
或java.io.Externalizable(外部序列化)
接口即可被序列化,其中java.io.Externalizable
接口只是实现了java.io.Serializable
接口。
反序列化类对象时有如下限制:
- 被反序列化的类必须存在。
serialVersionUID
值必须一致。
除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了sun.reflect.ReflectionFactory.newConstructorForSerialization
创建了一个反序列化专用的Constructor(反射构造方法对象)
,使用这个特殊的Constructor
可以绕过构造方法创建类实例
序列化/反序列化方法
ObjectInputStream、ObjectOutputStream两个类。
java.io.ObjectOutputStream
类最核心的方法是writeObject
方法,即序列化类对象。
java.io.ObjectInputStream
类最核心的功能是readObject
方法,即反序列化类对象。
所以,只需借助ObjectInputStream
和ObjectOutputStream
类我们就可 以实现类的序列化和反序列化功能了。
java.io.Serializable接口
这是一个空接口,我们不需要实现java.io.Serializable
的任何方法:
public interface Serializable {
}
实现java.io.Serializable
接口仅仅只用于标识这个类可序列化
实现了java.io.Serializable
接口的类原则上都需要生产一个serialVersionUID
常量,反序列化时如果双方的serialVersionUID
不一致会导致InvalidClassException
异常。如果可序列化类未显式声明 serialVersionUID
,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID
值。
java.io.Externalizable接口
java.io.Externalizable
和java.io.Serializable
几乎一样,只是java.io.Externalizable
接口定义了writeExternal
和readExternal
方法需要序列化和反序列化的类实现,其余的和java.io.Serializable
并无差别。
java.io.Externalizable.java:
copypublic interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
自定义序列化(writeObject)和反序列化(readObject)
实现了java.io.Serializable
接口的类,还可以定义如下方法(反序列化魔术方法
),这些方法将会在类序列化或反序列化过程中调用:
private void writeObject(ObjectOutputStream oos)
,自定义序列化。private void readObject(ObjectInputStream ois)
,自定义反序列化。private void readObjectNoData()
。protected Object writeReplace()
,写入时替换对象。protected Object readResolve()
。
序列化时可自定义的方法示例代码:
copypublic class DeserializationTest implements Serializable {
/**
* 自定义反序列化类对象
*
* @param ois 反序列化输入流对象
* @throws IOException IO异常
* @throws ClassNotFoundException 类未找到异常
*/
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println("readObject...");
// 调用ObjectInputStream默认反序列化方法
ois.defaultReadObject();
// 省去调用自定义反序列化逻辑...
}
/**
* 自定义序列化类对象
*
* @param oos 序列化输出流对象
* @throws IOException IO异常
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
System.out.println("writeObject...");
// 省去调用自定义序列化逻辑...
}
private void readObjectNoData() {
System.out.println("readObjectNoData...");
}
/**
* 写入时替换对象
*
* @return 替换后的对象
*/
protected Object writeReplace() {
System.out.println("writeReplace....");
return null;
}
protected Object readResolve() {
System.out.println("readResolve....");
return null;
}
}
当我们对DeserializationTest
类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)
方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)
方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObject
和writeObject
方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private
。
对比Python,PHP,Java反序列化
- https://t.zsxq.com/yNvrjMf
PHP反序列化
PHP的序列化是开发者不能参与的,开发者调用serialize函数后,序列化的数据就已经完成了,你得到的是一个完整的对象,你并不能在序列化数据流里新增某一个内容, 你如果想插入新的内容,只有将其保存在一个属性中。也就是说PHP的序列化、反序列化是一个纯内部的过程, 而其__sleep
、__wakeup
魔术方法的目的就是在序列化、反序列化的前后执行一些操作。
PHP的反序列化漏洞,很少是由__wakeup
这个方法触发的,通常触发在析构函数__destruct
里。其实大部分PHP反序列化漏洞,都并不是由反序列化导致的,只是通过反序列化可以控制对象的属性,进而在后续的代码中进行危险操作。
所以PHP反序列化漏洞又被称为“对象注入”
Java反序列化
Java在序列化时一个对象, 将会调用这个对象中的writeobject
方法,参数类型是objectoutputstream,开发者可以将任何内容写入这个stream中;反序列化时,会调用readobject
,开发者也可以从中读取出前面写入的内容,并进行处理。
比如:
package org.vulhub.Ser; import java.io.IOException;
public class Person implements java.io.Serializable {
public String name;
public int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject("This is a object");
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
String message = (String) s.readObject();
System.out.println(message);
}
}
在执行完s.defaultWriteObject();
之后s.writeObject("This is a object");
这行代码向stream里面又写入了一个字符串"This is a object",使用工具SerializationDumper查看此时生成的序列化数据(此处代码太长就不看了)
可见,我们写入的字符串This is a object被放在objectAnnotation的位置
反序列化的时候可以读取这个字符串并且输出:
Python反序列化
Python反序列化和ava、PHP有个显著的区别,就是Python的反序列化过程实际上是在执行一个基于栈的虚拟机。我们可以向栈上增、删对象,也可以执行一些指令,比如函数的执行等,甚至可以用这个虚拟机执行一个完整的应用程序。
看这篇文章:https://xz.aliyun.com/t/7436
所以,Python的反序列化可以立即导致任意函数、命令执行漏洞,与需要gadget的PHP和Java相比更加危险。
Java-ysoserial审计环境搭建
https://www.shuzhiduo.com/A/MyJxmM1p5n/
https://bkfish.gitee.io/2020/05/29/java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%85%A5%E9%97%A83-ysoserial%E8%B0%83%E8%AF%95%E5%92%8C%E6%9E%84%E9%80%A0URLDNS%E7%9A%84pop%E9%93%BE/
踩坑:
ysoserial不支持1.6,我用的JDK是1.7版本,不然run不起来
是提取的JDK有问题(我是直接解压exe然后跑,果然有问题),建议从虚拟机安装之后把文件夹拖出来,而且建议使用jdk1.8,不然源码不一样
- 因为是maven项目,pom.xml文件里面有些依赖下载不下来可以去https://mvnrepository.com/这个里面找到jar和pom文件手动放到本地仓库,比如
<dependency>
<groupId>de.odysseus.juel</groupId>
<artifactId>juel-impl</artifactId>
<version>2.2.7</version>
</dependency>
所对应的就是repository\de\odysseus\juel\juel-impl\2.2.7\juel-impl-2.2.7.pom(jar)
调试JDK源码 https://blog.youkuaiyun.com/daerzei/article/details/79717717