JNDI高版本bypass
JNDI lookup()调用过程
InitialContext.lookup()
入口函数。
1 | |
- 作用:决定是用 URL Context(如果
name有协议前缀)还是用默认的 Initial Context。 - 例:
"rmi://127.0.0.1:1099/evil"→ 识别前缀rmi://→ 进入 RMI 的RegistryContext。
getURLOrDefaultInitCtx()
- 如果名字是 URL(例如
"rmi://..."),则走NamingManager.getURLContext("rmi", env)。
否则走默认的初始上下文。
NamingManager.getURLContext("rmi")
- 找到
com.sun.jndi.url.rmi.rmiURLContextFactory,实例化rmiURLContext。
rmiURLContext.lookup("rmi://.../evil") → GenericURLContext.lookup()
- 解析 URL,拆出
"host:port"和"name"。 - 调用
RegistryContext.lookup("evil")。
GenericURLContext.lookup()
负责 分发 URL 前缀。
源码逻辑类似:
1 | |
- 作用:把
"rmi://..."转交给RegistryContext;把"ldap://..."转交给LdapCtx。 - 这是 JNDI 的多协议适配器。
RegistryContext.lookup()
进入 RMI 部分:
1 | |
- 作用:连接远程 RMI Registry,调用其
lookup()方法,取出对象引用。 - 结果可能是:
- 真正的远程对象 stub
- Reference 对象(告诉 JNDI 怎么实例化目标对象)
- 序列化数据
RegistryContext.decodeObject()
处理从 RMI 返回的对象。
1 | |
作用:判断对象类型:
- 如果是 普通 Java 对象 → 直接返回。
- 如果是 Reference → 交给
NamingManager来根据工厂类加载并实例化。
危险点:Reference 里可能带有远程 factoryClassLocation(恶意类下载)。
NamingManager.getObjectInstance()
核心的对象工厂加载逻辑:
1 | |
- 作用:
- 根据 Reference 里的
factoryClassName找到对应的ObjectFactory。 - 如果本地没这个类 → 尝试从远程 codebase 加载(⚠️ 新版 JDK 默认禁用)。
- 调用
ObjectFactory.getObjectInstance()→ 生成最终对象并返回给调用者。
- 根据 Reference 里的
ObjectFactory.getObjectInstance(...)
由开发者或攻击者提供的工厂类:
1 | |
- 作用:最终创建返回给应用程序的对象。
- 漏洞点:攻击者的
ObjectFactory里可能有恶意静态代码块,甚至直接 RCE。
整体调用链总结
1 | |
高版本 JDK 的安全判断点
从 JDK 8u121 / 7u131 / 6u141 开始,引入了安全补丁,主要是限制 远程 Codebase 加载类。判断逻辑主要出现在 RegistryContext.decodeObject → NamingManager.getObjectInstance → Reference 处理部分。
远程 Codebase 加载禁止
在 NamingManager.getObjectInstance 处理 Reference 时:
如果
Reference里有 factoryClassLocation(远程 URL),JNDI 会尝试下载类。新版 JDK 检查系统属性:
com.sun.jndi.rmi.object.trustURLCodebase(默认false)com.sun.jndi.ldap.object.trustURLCodebase(默认false)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24if (factoryClass != null) {
Class<?> cls;
if (factoryCodebase != null && !isTrustURLCodebaseAllowed(nameCtx, env)) {
// 🚨 高版本新增的“远程 codebase 不可信”报错
throw new ConfigurationException(
"The object factory is untrusted. " +
"Set the system property 'com.sun.jndi.<proto>.object.trustURLCodebase' to 'true'."
);
}
if (factoryCodebase != null) {
// ✅ 只有在 trustURLCodebase==true 时才会走到这里的远程加载
ClassLoader urlCl = new URLClassLoader(new URL[]{ new URL(factoryCodebase) });
cls = Class.forName(factoryClass, true, urlCl);
} else {
// ✅ 本地类路径里直接找(高版本仍然允许的“绕过点”)
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) cl = NamingManager.class.getClassLoader();
cls = Class.forName(factoryClass, true, cl);
}
factory = (ObjectFactory) cls.getDeclaredConstructor().newInstance();
}绕过
1
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
本地类仍然允许
如果 Reference 里的 factoryClassName 在本地 CLASSPATH 已存在:
- 不需要远程下载,直接
Class.forName(factoryClassName)成功。 - JNDI 仍会实例化这个工厂并执行
getObjectInstance()。
“高版本绕过点”:
虽然禁止了远程下载,但 如果攻击者能利用本地现有类(反序列化 Gadget),仍能构造恶意链。
依赖
1 | |
服务端
1 | |
客户端
1 | |
JNDI高版本bypass
http://xiaowu5.cn/2025/12/04/JNDI高版本bypass/