新聞中心
0x01 Java 的代理模式

1. 靜態(tài)代理
簡單理解靜態(tài)代理
- 以租客找中介向房東租房子為例
想要實(shí)現(xiàn)租客找中介租房東,在 Java 中就需要4個(gè)文件,分別是房源、房東、中介、租客,其中房源應(yīng)該是接口,其余三項(xiàng)為類。
不明白房源為什么是接口的師傅,這與 Java 編程的設(shè)計(jì)思想有關(guān),我個(gè)人也喜歡把它與 c++ 里面的純虛函數(shù)做類比??梢砸撇街量裆竦囊曨l學(xué)習(xí)一下靜態(tài)代理。
- Rent.java:這是一個(gè)接口,可以抽象的理解為房源,作為房源,它有一個(gè)方法rent()為租房。
Rent.java
package src.JdkProxy.StaticProxy;
// 租房的接口
public interface Rent {
public void rent();
}
- Host.java:這是一個(gè)類,這個(gè)類就是房東,作為房東,他需要實(shí)現(xiàn)Rent.java這一個(gè)接口,并且要實(shí)現(xiàn)接口的rent()方法。
Host.java
package src.JdkProxy.StaticProxy;
public class Host implements Rent {
public void rent(){
System.out.println("房東要出租房子");
}
}
- Client.java:這是一個(gè)啟動(dòng)類,這個(gè)類其實(shí)就是租客,租客的想法也很簡單,就是找到中介,然后租房(為什么不直接找房東呢?因?yàn)榉繓|通常不想管那么多事,而且房源基本被中介壟斷)
因?yàn)樽饪褪且フ抑薪榭捶康?,而不是去找房東看房的,所以我們這里先把 Proxy.java 實(shí)現(xiàn)一下,也就是把中介相關(guān)的功能先實(shí)現(xiàn)一下。
Proxy.java:這是一個(gè)類,這個(gè)類是中介,也就是代理,他需要有房東的房源,然而我們通常不會(huì)繼承房東,而會(huì)將房東作為一個(gè)私有的屬性host,我們通過host.rent()來實(shí)現(xiàn)租房的方法。
Proxy.java
package src.JdkProxy.StaticProxy;
// 中介
public class Proxy {
private Host host;
public Proxy(){}
public Proxy(Host host){
this.host = host;
}
public void rent(){
host.rent();
}
}
Client.java租客去找中介看房。
package src.JdkProxy.StaticProxy;
// 啟動(dòng)器
public class Client {
public static void main(String[] args) {
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
這樣子,基本的看房就完成了 ~
但是,租房這一過程就結(jié)束了嗎?
不可能啊,因?yàn)橹薪檫€要收中介費(fèi)呢?
- 有一些行為是中介可以做的,而房東不能做的,比如看房,收中介費(fèi)等等。所以我們要在 Proxy.java當(dāng)中實(shí)現(xiàn)這些功能。
改進(jìn) Proxy.java
package src.JdkProxy.StaticProxy;
// 中介
public class Proxy {
private Host host;
public Proxy(){}
public Proxy(Host host){
this.host = host;
}
public void rent(){
host.rent();
contract();
fare();
}
// 看房
public void seeHouse(){
System.out.println("中介帶你看房");
}
// 收中介費(fèi)
public void fare(){
System.out.println("收中介費(fèi)");
}
// 簽租賃合同
public void contract(){
System.out.println("簽租賃合同");
}
}
優(yōu)點(diǎn):
- 可以使得我們的真實(shí)角色更加純粹 . 不再去關(guān)注一些公共的事情。
- 公共的業(yè)務(wù)由代理來完成 . 實(shí)現(xiàn)了業(yè)務(wù)的分工。
- 公共業(yè)務(wù)發(fā)生擴(kuò)展時(shí)變得更加集中和方便。
缺點(diǎn) :
- 一個(gè)真是類對應(yīng)一個(gè)代理角色,代碼量翻倍,開發(fā)效率降低。
我們想要靜態(tài)代理的好處,又不想要靜態(tài)代理的缺點(diǎn),所以 , 就有了動(dòng)態(tài)代理 !
深入理解靜態(tài)代理
深入到實(shí)際業(yè)務(wù)當(dāng)中,比如我們平常做的最多的 CRUD
- UserService.java,這是一個(gè)接口,我們定義四個(gè)抽象方法。
package src.JdkProxy.MoreStaticProxy;
// 深入理解靜態(tài)代理
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
我們需要一個(gè)真實(shí)對象來完成這些增刪改查操作。
UserServiceImpl.java
package src.JdkProxy.MoreStaticProxy;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一個(gè)用戶");
}
@Override
public void delete() {
System.out.println("刪除了一個(gè)用戶");
}
@Override
public void update() {
System.out.println("更新了一個(gè)用戶");
}
@Override
public void query() {
System.out.println("查詢了一個(gè)用戶");
}
}
需求來了,現(xiàn)在我們需要增加一個(gè)日志功能,怎么實(shí)現(xiàn)!
- 思路1 :在實(shí)現(xiàn)類上增加代碼 【麻煩!】
- 思路2:使用代理來做,能夠不改變原來的業(yè)務(wù)情況下,實(shí)現(xiàn)此功能就是最好的了!
處理手段:增加一個(gè)代理類來處理日志。
UserServiceProxy.java
package src.JdkProxy.MoreStaticProxy;
// 代理
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
// 增加日志方法
public void log(String msg){
System.out.println("[Debug]使用了 " + msg +"方法");
}
}
修改啟動(dòng)器 Client.java
package src.JdkProxy.MoreStaticProxy;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
如此一來,增加業(yè)務(wù)點(diǎn)的日志便成功了 。
2. 動(dòng)態(tài)代理
- 前文我們說到靜態(tài)代理的問題,還記得嗎?
每多一個(gè)房東就需要多一個(gè)中介,這顯然不符合生活認(rèn)知(對于租客來說,如果是用靜態(tài)代理模式,每當(dāng)想要換一個(gè)房東,那就必須要再換一個(gè)中介,在開發(fā)中,如果有多個(gè)中介代碼量就更大了)
動(dòng)態(tài)代理的出現(xiàn)就是為了解決上面靜態(tài)代理的缺點(diǎn)。
動(dòng)態(tài)代理的一些基礎(chǔ)知識(shí)
下面講的主要是一些源碼的東西吧,不看也可。我是不建議看的,但是為了文章內(nèi)容的完整性,我還是貼上來吧。
- 動(dòng)態(tài)代理的角色和靜態(tài)代理的一樣。需要一個(gè)實(shí)體類,一個(gè)代理類,一個(gè)啟動(dòng)器。
- 動(dòng)態(tài)代理的代理類是動(dòng)態(tài)生成的,靜態(tài)代理的代理類是我們提前寫好的。
JDK的動(dòng)態(tài)代理需要了解兩個(gè)類
核心 : InvocationHandler 調(diào)用處理程序類和 Proxy 代理類
InvocationHandler:調(diào)用處理程序
public interface InvocationHandler
InvocationHandler是由代理實(shí)例的調(diào)用處理程序?qū)崿F(xiàn)的接口
每個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理程序。
Object invoke(Object proxy, 方法 method, Object[] args);
當(dāng)在代理實(shí)例上調(diào)用方法的時(shí)候,方法調(diào)用將被編碼并分派到其調(diào)用處理程序的invoke()方法。
參數(shù):
- proxy– 調(diào)用該方法的代理實(shí)例
- method-所述方法對應(yīng)于調(diào)用代理實(shí)例上的接口方法的實(shí)例。方法對象的聲明類將是該方法聲明的接口,它可以是代理類繼承該方法的代理接口的超級接口。
- args-包含的方法調(diào)用傳遞代理實(shí)例的參數(shù)值的對象的陣列,或null如果接口方法沒有參數(shù)。原始類型的參數(shù)包含在適當(dāng)?shù)脑及b器類的實(shí)例中,例如java.lang.Integer或java.lang.Boolean。
Proxy : 代理
public class Proxy extends Object implements Serializable
Proxy提供了創(chuàng)建動(dòng)態(tài)代理類和實(shí)例的靜態(tài)方法,它也是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類的超類。
動(dòng)態(tài)代理類 (以下簡稱為代理類 )是一個(gè)實(shí)現(xiàn)在類創(chuàng)建時(shí)在運(yùn)行時(shí)指定的接口列表的類,具有如下所述的行為。 代理接口是由代理類實(shí)現(xiàn)的接口。 代理實(shí)例是代理類的一個(gè)實(shí)例。
public static Object newProxyInstance(ClassLoader loader, 類>[] interfaces, InvocationHandler h) throws IllegalArgumentException
返回指定接口的代理類的實(shí)例,該接口將方法調(diào)用分派給指定的調(diào)用處理程序。
參數(shù)
- loader– 類加載器來定義代理類
- interfaces– 代理類實(shí)現(xiàn)的接口列表
- h– 調(diào)度方法調(diào)用的調(diào)用處理函數(shù)
動(dòng)態(tài)代理的代碼實(shí)現(xiàn)
- 要寫動(dòng)態(tài)代理的代碼,需要抓牢兩個(gè)要點(diǎn)
①:我們代理的是接口,而不是單個(gè)用戶。
②:代理類是動(dòng)態(tài)生成的,而非靜態(tài)定死。
我只能說這種編程思想是真的牛逼,其實(shí)我們還可以實(shí)現(xiàn)任意接口的動(dòng)態(tài)代理實(shí)現(xiàn),在這里就不貼出來了。
首先是我們的接口類
UserService.java
package src.JdkProxy.DynamicProxy;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
接著,我們需要用實(shí)體類去實(shí)現(xiàn)這個(gè)抽象類
UserServiceImpl.java
package src.JdkProxy.DynamicProxy;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一個(gè)用戶");
}
@Override
public void delete() {
System.out.println("刪除了一個(gè)用戶");
}
@Override
public void update() {
System.out.println("更新了一個(gè)用戶");
}
@Override
public void query() {
System.out.println("查詢了一個(gè)用戶");
}
}
接著,是動(dòng)態(tài)代理的實(shí)現(xiàn)類
package src.JdkProxy.DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
// 動(dòng)態(tài)生成代理類實(shí)例
public Object getProxy(){
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), this);
return obj;
}
// 處理代理類實(shí)例,并返回結(jié)果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method);
Object obj = method.invoke(userService, args);
return obj;
}
- 最后編寫我們的 Client,也就是啟動(dòng)器
Client.java
package src.JdkProxy.DynamicProxy;
import src.JdkProxy.DynamicProxy.UserServiceImpl;
public class Client {
public static void main(String[] args) {
// 真實(shí)角色
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 代理角色,不存在
UserProxyInvocationHandler userProxyInvocationHandler = new UserProxyInvocationHandler();
userProxyInvocationHandler.setUserService((UserService) userServiceImpl); // 設(shè)置要代理的對象
// 動(dòng)態(tài)生成代理類
UserService proxy = (UserService) userProxyInvocationHandler.getProxy();
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
- 上述,我們的動(dòng)態(tài)代理便完成了。
0x02 在反序列化中動(dòng)態(tài)代理的作用
- 如果只是純講開發(fā),沒什么意義,我們重點(diǎn)來了,動(dòng)態(tài)代理是如何參與反序列化攻擊的。
回到之前文章的內(nèi)容,我們之前說要利用反序列化的漏洞,我們是需要一個(gè)入口類的。
我們先假設(shè)存在一個(gè)能夠漏洞利用的類為B.f,比如Runtime.exec這種。
我們將入口類定義為A,我們最理想的情況是 A[O] -> O.f,那么我們將傳進(jìn)去的參數(shù)O替換為B即可。但是在實(shí)戰(zhàn)的情況下這種情況是極少的。
回到實(shí)戰(zhàn)情況,比如我們的入口類A存在O.abc這個(gè)方法,也就是 A[O] -> O.abc;而 O 呢,如果是一個(gè)動(dòng)態(tài)代理類,O的invoke方法里存在.f的方法,便可以漏洞利用了,我們還是展示一下。
A[O] -> O.abc
O[O2] invoke -> O2.f // 此時(shí)將 B 去替換 O2
最后 ---->
O[B] invoke -> B.f // 達(dá)到漏洞利用效果
動(dòng)態(tài)代理在反序列化當(dāng)中的利用和readObject是異曲同工的。
readObject方法在反序列化當(dāng)中會(huì)被自動(dòng)執(zhí)行。
而invoke方法在動(dòng)態(tài)代理當(dāng)中會(huì)自動(dòng)執(zhí)行。
0x03 參考資料
- https://www.bilibili.com/video/BV1mc411h719?p=9
- https://www.bilibili.com/video/BV1mc411h719?p=10
- https://www.bilibili.com/video/BV1mc411h719?p=11
- https://www.bilibili.com/video/BV16h411z7o9?p=3
網(wǎng)站名稱:Java反序列化基礎(chǔ)篇-JDK動(dòng)態(tài)代理
標(biāo)題鏈接:http://www.dlmjj.cn/article/cooogoi.html


咨詢
建站咨詢
