再次深入学习动态加载字节码:

动态加载字节码

1、什么是Java的字节码

严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。

java的核心就是跨平台运行,Java编译的结果–字节码(.class文件)交给 JVM 去运行,同时如果其他语言可以编译为字节码文件,也可以交由 JVM 运行

2、动态加载字节码的方法

2.1 利用 URLClassLoader 加载远程 class 文件

解释 URLClassLoader 的工作过程实际上就是在解释默认的Java类加载器的工作流程。

正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

  • URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件
  • URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
  • URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类

我们正常开发的时候通常遇到的是前两者,那什么时候才会出现使用 Loader 寻找类的情况呢?当然是非 file 协议的情况下,最常见的就是 http 协议。

2.1.1 file 协议

先编译一个文件:

使用 URLClassLoader 加载:

2.1.2 http 协议

先用python 起一个 http 服务:

2.2 利用 ClassLoader#defineClass 直接加载字节码

不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:

ClassLoader#loadClass -> ClassLoader#findClass -> ClassLoader#defineClass

  • loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
  • findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

学习如何让系统的 defineClass 来直接加载字节码:

跟进 defineClass 看到它是一个 protected 属性,无法直接访问,所以上述例子中用反射调用。

2.3 利用 TemplatesImpl 加载字节码

defineClass 方法无法直接使用,但是呢,TemplatesImpl 类中给我们提供了一个入口:

TemplatesImpl 类继承了 ClassLoader ,并重写 defineClass 方法,并且是可以被外部调用

现在以 TemplatesImpl#defineClass 为终点跟踪一下这条链:

查找用法:TemplatesImpl.TransletClassLoader.defineClass

TemplatesImpl.defineTransletClasses.defineClass

defineTransletClasses 函数还是私有的,继续找

这里查找 defineTransletClasses 的用法后,找到三个结果

getTransletClasses 和 getTransletIndex() 都是返回了一个储存值,用于后续操作

而 getTransletInstance 中 .newInstance() 会调用无参构造创建一个实例,可以用于我们后续的代码执行所以重点跟它

来到 newTransformer 函数,它是公有的,到这里就可以了

小总结一下:

TemplatesImpl.TransletClassLoader.defineClass

TemplatesImpl.defineTransletClasses.defineClass

TemplatesImpl.getTransletInstance.defineTransletClasses

TemplatesImpl.newTransformer.getTransletInstance

TemplatesImpl

newTransformer

getTransletInstance

defineTransletClasses

defineClass

TransletClassLoader.defineClass

接下来构造 POC:

首先满足俩个条件:

进入 defineTransletClasses -> newInstance

前后结合一下,将 _bytecodes 构造为一维数组套二维数组:

_tfactory 这个变量被标记为 transient (不可序列化)

然后在 readObject 中找:看到已经被赋值了

先来一个要执行的类:将其编译后放在指定位置

执行后报错:NullPointerException

调试查找哪里出了问题:

可以看到 _transletIndex:-1。我们需要进入到 if 语句才能正常执行

所以要让执行类继承 AbstractTranslet 类(对应:ABSTRACT_TRANSLET),

最终完整的执行类:

2.4 利用 BCEL ClassLoader 加载字节码

BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目,但其因为被Apache Xalan所使用,而Apache Xalan又是Java内部对于JAXP的实现,所以BCEL也被包含在了JDK的原生库中。

关于BCEL的详细介绍:《BCEL ClassLoader去哪了》

执行后可以看到有一对乱码:BCEL ClassLoader 正是用于加载这串特殊的“字节码”,并可以执行其中的代码。

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

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

CC3 链分析

环境:

JDK :8u65

commons-collections 3.2.1

分析链

CC3 利用的就是上文分析的 ”利用 TemplatesImpl 加载字节码“这条链,然后结合 CC1/CC6 的前半部分形成可用 POC。

TemplatesImpl 链:

CC1 + CC3 (InvokerTransformer)

CC1:

结合一下啊:

CC6:

CC3 (InstantiateTransformer)

分析:

而在 ysoserial 中并没有使用 InvokerTransformer,这是因为黑名单过滤时很有可能会将 **InvokerTransformer **直接禁用,为了更广泛的使用, ysoserial 换了一条路:

《Java 安全漫谈》中也有提及

2015年初,@frohoff和@gebl发布了Talk《Marshalling Pickles: how deserializing objects will ruin your day》,以及Java反序列化利用工具ysoserial,随后引爆了安全界。开发者们自然会去找寻一种安全的过滤方法,于是类似 Serialkiller 这样的工具随之诞生。

SerialKiller是一个ava反序列化过滤器,可以通过黑名单与白名单的方式来限制反序列化时允许通过的类。在其发布的第一个版本代码中,我们可以看到其给出了最初的黑名单:

这个黑名单中InvokerTransformer赫然在列,也就切断了CommonsCollections1的利用链。有攻就有防,ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3。

CommonsCollections3的目的很明显,就是为了绕过一些规则对InvokerTransformer的限制。CommonsCollections3并没有使用到InvokerTransformer来调用任意方法,而是用到了另一个类,com.sun.org.apache.xalan.internal.xsltc.trax.TrAxFilter。

现在继续找 newTransformer 的用法:

这里的 Process 只是 exec() 的返回值,如果你不去读它的输出或等待它结束,它就没用了,一般对象,不用它

getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用。

TransformerFactoryImpl 不能序列化,并且构造函数传参困难

TrAXFilter 也不能序列化,但构造函数简单,想着调用 TrAXFilter 的构造函数

那么如何使用 TrAXFilter 的构造函数,这里用到了 InstantiateTransformer

InstantiateTransformer.transform 判断传入的参数是否为 Class 类型,如果是,获取指定构造器,调用构造函数。

构造POC:

总结:

本次就将 CC1、CC3、CC6放在一起吧: