static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);//在这里调用了defineClass
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();//此处调用defineTransletClasses方法
// The translet needs to keep a reference to all its auxiliary class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet)
_class[_transletIndex].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException | IllegalAccessException |
NoSuchMethodException | InvocationTargetException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString(), e);
}
}
不过还是private方法,继续找到newTransformer()方法:
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);//调用了getTransletInstance方法
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
Java中动态加载字节码
这个顺序是跟着p神的文章走的,学这个也是因为cc3中会用到这部分的知识,所以从这接着往后面弄了,抓紧时间弄完去深入一点渗透了!!
什么是Java的“字节码”
它是程序的一种低级表示,可以运行于
Java
虚拟机上。将程序抽象成字节码可以保证Java程序在各种设备上的运行,严格来说,Java
字节码(ByteCode)其实仅仅指的是Jav
a虚拟机执行使用的一类指令,通常被存储在.class
文件中。而.class
文件可以运行于各个计算机的JVM
虚拟机中。其实也就是实现了一次编译,各处使用的性质。甚至,开发者可以用类似Scala、Kotlin
这样的语言编写代码,只要你的编译器能够将代码编译成.class
文件,都可以在JVM
虚拟机中运行,来看一张图:在学习的过程中,把字节码的定义放大点:所有能够恢复成一个类并在JVM虚拟机里加
载的字节序列,都在我们的探讨范围内。
利用URLClassLoader加载远程class文件
ClassLoader 是什么呢?
与普通程序不同的是。
Java
程序(class文件)并不是本地的可执行程序。当运行Java
程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class
的这部分就叫做Class Loader
。而Class Loader
这个类的作用就是根据一个指定的类的全限定名,找到对应的Class
字节码文件,然后加载它转化成一个java.lang.Class
类的一个实例之前学习反射的过程中,我们知道它就是一个“加载器”,告诉
Java
虚拟机如何加载这个类。Java
默认的ClassLoader
就是根据类名来加载类,这个类名是类完整路径,如java.lang.Runtime
。URLClassLoader
URLClassLoader
实际上是我们平时默认使用的AppClassLoader
的父类,所以,我们解释URLClassLoader
的工作过程实际上就是在解释默认的Java
类加载器的工作流程。正常情况下,
Java
会根据配置项sun.boot.class.path
和java.class.path
中列举到的基础路径(这些路径是经过处理后的
java.net.URL
类)来寻找.class
文件来加载,而这个基础路径有分为三种情况:试试
http
协议,从远程http
服务器上加载.class
文件,先在本地编写个.class
文件然后编译成
.class
文件在
.class
文件目录本地开个服务接着编写远程加载代码:
这里还要注意一点:(也是补基础知识了-。-)
在
idea
中运行的时候他会将该项目里面的所有java
文件编译一次(也就是变成class
文件),接着当类加载的时候会先去本地里面查找相应的class
文件加载为相应类对象,如果找不到的话才会去远程加载。所以想要直观的看到是远程加载的
class
文件而不是加载的本来的class
文件的话,先编译生成一个class
文件,然后删除原来的java
文件,再在所在class
文件开个服务就行了所以作为攻击者,如果我们能够控制目标
Java ClassLoader
的基础路径为一个http服务器,则可以利用远程加载的方式执行任意代码了。利用ClassLoader##defineClass直接加载字节码
URLClassLoader
加载远程class
文件(也就是字节码)的过程已经了解了。其实,不管是加载远程
class
文件,还是本地的class
或jar
文件,Java
都经历的是下面这三个方法调用:ClassLoader#loadClass=>ClassLoader#findClass=>ClassLoader#defineClass
其中:
loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
findClass
的作用是根据基础URL
指定的方式来加载类的字节码,就像之前说到的,可能会在本地文件系统、
jar
包或远程http
服务器上读取字节码,然后交给defineClass
defineClass
的作用是处理前面传入的字节码,将其处理成真正的Java
类所以可见,真正核心的部分其实是
defineClass
,他决定了如何将一段字节流转变成一个Java
类,Java
默认的
ClassLoader#defineClass
是一个native
方法,逻辑在JVM
的C语言代码中。什么是
native
方法:来看一个列子:
里面的base64编码用linux命令完成
由于系统的
ClassLoader#defineClass
是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。
跟进一下
defineClass
具体实现return
里面套了一层,继续跟进看返回了一个类对象,也就是加载后的类对象,最后
newInstance()
实现该Hello
类就可以了在实际场景中,因为
defineClass
方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链TemplatesImpl
的基石。利用TemplatesImpl加载字节码
前面分析了
defineClass
方法并不好直接利用,但是Java底层还是有一些类用到了它,这就是TemplatesImpl
。com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类中定义了一个内部类TransletClassLoader
:这个类里重写了
defineClass
方法,并且这里没有显式地声明其定义域。Java
中默认情况下,如果一个方法没有显式声明作用域,其作用域为
default
。所以也就是说这里的defineClass
由其父类的protected
类型变成了一个default
类型的方法,可以被类外部调用。但是因为
TransletClassLoader
是内部类,所以说只允许TemplatesImpl
类中的方法调用,往上找找调用他的方法,看到TemplatesImpl
类中的defineTransletClasses()
方法:但它是一个
private
方法,还是不能直接调用,继续往上看到getTransletInstance()
,他是调用了defineTransletClasses()
的:不过还是
private
方法,继续找到newTransformer()
方法:这里就是
public
方法了,可以直接调用,所以一条调用链就出来了在调用的过程中需要满足一些条件,会用到一些值,所以我们还得设置
TemplatesImpl
对象的三个私有属性,这里我们用反射设置就行,三个属性:_bytecodes
、_name
和_tfactory
·
_name:
为任意字符串,只要不是null
才可以进入defineTransletClasses()
·
_bytecodes:
由字节码组成的数组,用来存放恶意代码,其值不能为null
,最后才可以进入到·
_tfactory:
需要一个TransformerFactoryImpl
对象(getExternalExtensionsMap是这个对象方法)
,不然调用其他对象会报错,这样才能继续执行下去插一嘴
我最后调试的时候发现并不是从图上的这个地方进去就获取了类对象的,这里只是为了不报错,因为要达到传入恶意代码的的操作,才能达到目的,所以是后面这里才获取了类对象的
再往后面跟一点点接着才实例化了对象
但是,
TemplatesImpl
中对加载的字节码还有一定的要求,这个字节码对应的类必须要是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类,所以我们还得构造一个特殊的类,用这个类生成字节码:简单解释一下为什么要多两个
transform
方法同样将其编译为
class
文件,然后base64
加密。最后就是写
poc
了,就新建一个TemplatesImpl
对象,把属性设置进去然后执行newTransformer
方法触发,主要是咱得先写一个利用反射给私有属性赋值的一个方法setFieldValue:
利用BCEL ClassLoader加载字节码
关于BCEL可以先看看p神的:https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel
BCEL
属于Apache Commons
项目下的一个子项目,它提供了一系列用于分析、修改和创建Java Class
文件的API
,从库功能来看,使用性远不及其他库,但它比Commons Collection
特殊一点的是它被包含在了JDK
原生库里,位于com.sun.org.apache.bcel
。BCEL
包中有个com.sun.org.apache.bcel.internal.util.ClassLoader
类,它是一个ClassLoader
,但重写了Java
内置的ClassLoader#LoadClass
方法。在
LoadClass
中,会判断类名是否是$$BCEL$$
开头,如果是的话,将会对这个字符串进行decode:来看一下
decode
的具体算法:可以理解为是传统字节码的
16
进制编码,然后将\
替换为$
,默认还会在最外层加上GZip
压缩尝试编写恶意类:
然后通过
BCEL
提供的两个类Repository
和utility
来利用:最后用
BCEL ClassLoader
加载这串特殊的字节码,并执行里面的代码:其实也可以简化一点代码
一样是可以利用的
最后
BCEL ClassLoader
在Fastjson
等漏洞的利用链构造时都有被用到,其实这个BCEL ClassLoader
类和前面的TemplatesImpl
都出自于同一个第三方库,Apache Xalan
。所以才要导入,还要导入maven
项目。还有一个重要的利用条件就是在Java 8u251
的更新中,这个ClassLoader
被移除了,所以之后只能在这个之前的版本才可以利用。参考链接
BCEL ClassLoader去哪了