2026-SUCTF-JDBC-Master 漏洞分析与利用教学文档
摘要
本文档基于先知社区发布的《2026-SUCTF-JDBC-Master:鉴权绕过与JDBC任意代码执行》文章,详细解析了一道CTF题目中涉及的安全漏洞。该漏洞包含两个核心部分:一个Spring MVC路由的鉴权绕过和一个PostgreSQL Kingbase8 JDBC驱动的任意代码执行。本文将逐步拆解漏洞原理、绕过思路和利用方法,并附上完整的利用脚本。
1. 环境搭建
题目提供了名为SU_jdbc-master.zip的源码压缩包。通过以下Docker命令构建和启动环境:
docker build -t jdbc-master .
docker-compose up
2. 漏洞分析
2.1 鉴权绕过
2.1.1 漏洞位置
- 入口点是一个明显的JDBC连接接口。
- 存在一个名为
PathInterceptor的Spring MVC拦截器,拦截所有路径并进行安全检查。
2.1.2 拦截器逻辑分析
拦截器核心代码如下:
public class PathInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpServletRequest r = request;
HttpServletResponse res = response;
String servletPath = r.getServletPath();
if (servletPath != null && (servletPath.matches("(?i).*s\W*u\W*c\W*t\W*f.*") || servletPath.toLowerCase().contains("suctf") || servletPath.toLowerCase().replaceAll("[^a-z0-9]", "").contains("suctf"))) {
res.setStatus(403);
res.getWriter().write("blocked by filter");
return false;
}
return true;
}
}
规则说明:
- 如果路径包含
suctf(不区分大小写)或经过正则模糊匹配,则返回403。 - 规则使用了
toLowerCase(),将所有字符转为小写后再进行contains("suctf")判断。
2.1.3 路由匹配逻辑
- 在
WebConfig中,AntPathMatcher的setCaseSensitive(false)被设置为false,意味着路由匹配不区分大小写。 - 路由匹配最终会调用
String.regionMatches方法,该方法在ignoreCase为true时,会尝试将字符转换为大写或小写进行匹配。只要大小写转换后能匹配,即视为成功。
2.1.4 绕过原理
关键在于路由匹配和拦截器过滤使用了不同的字符转换逻辑:
- 路由匹配 (
AntPathMatcher): 不区分大小写,会尝试将字符转大写进行匹配。 - 拦截器过滤 (
PathInterceptor): 先将所有字符转小写,再进行包含性检查。
绕过字符: Unicode字符 ſ (U+017F, "长s")。
- 在路由匹配时,
ſ被转换为大写S,因此路径如/api/ſuctf/connection能成功匹配到/api/suctf/connection路由。 - 在拦截器过滤时,路径被整体转为小写,
ſ的小写形式仍是ſ,字符串"ſuctf"转为小写后为"ſuctf",不包含子串"suctf",从而绕过过滤。
2.1.5 总结
利用字符大小写转换规则的不一致性,通过插入ſ字符,使其在路由匹配时被识别为S,在安全检查时却不被识别为suctf,实现鉴权绕过。
2.2 JDBC任意代码执行
2.2.1 漏洞入口与过滤
绕过鉴权后,访问/api/ſuctf/connection接口,该接口接收JSON数据用于建立JDBC连接。存在两重过滤:
-
URL格式过滤:禁止JDBC URL中包含
:/或/?。if (jdbcUrl.trim().toLowerCase().contains(":/") || jdbcUrl.trim().toLowerCase().contains("/?")) { throw new IllegalArgumentException("Cannot contain special characters"); }绕过方法:直接去掉
/,例如使用jdbc:kingbase8:127.0.0.1:54321/test?a=1。 -
参数黑名单过滤:禁止URL中包含一些危险参数。
private static final List<String> ILLEGAL_PARAMETERS = Arrays.asList("socketFactory", "socketFactoryArg", "sslfactory", "sslhostnameverifier", "sslpasswordcallback", "authenticationPluginClassName", "loggerFile", "loggerLevel");此黑名单旨在防御CVE-2022-21724(PostgreSQL JDBC驱动RCE)。
2.2.2 Driver覆盖漏洞
- 代码默认使用PostgreSQL驱动 (
org.postgresql.Driver),其高版本(42.3.6+)已修复相关RCE漏洞。 - 但题目同时提供了Kingbase8驱动(PostgreSQL的国产分支),其可能未修复漏洞。
- 连接参数通过Jackson反序列化。Jackson在反序列化时,若JSON中包含
driver字段,则会调用其setter方法覆盖默认的Driver。因此,可通过传入"driver": "com.kingbase8.Driver"来指定使用存在漏洞的Kingbase8驱动。
2.2.3 Kingbase8驱动文件加载RCE
在Kingbase8驱动的initJDBCCONF函数中,存在从文件加载配置的代码逻辑。该逻辑会读取sys_properties参数指定的文件,并加载其中的属性。
利用链:
- 利用JDBC URL参数
sys_properties指向一个可控的临时文件。 - 在该文件中设置
socketFactory和socketFactoryArg属性(虽然这两个参数在黑名单中,但通过文件加载可以绕过URL参数的黑名单检查)。 socketFactory被设置为org.springframework.context.support.FileSystemXmlApplicationContext。socketFactoryArg指向一个包含恶意Spring XML配置的本地文件,从而触发Spring相关类的实例化,实现远程代码执行。
2.2.4 无文件上传场景下的文件写入
题目没有文件上传功能。这里利用了一个技巧:通过multipart/form-data上传文件时,服务器会先将文件内容写入临时文件,请求处理完毕后再删除。
利用步骤:
- 构造一个
multipart/form-data请求,上传一个包含恶意Spring XML配置的文件(例如命名为a.txt)。 - 服务器会在
${catalina.home}/work/(或其他临时目录)下生成一个临时文件(如/tmp/tomcat.8080.1234567890123456789/upload_xxx.tmp),其内容就是我们上传的XML。 - 在临时文件被删除前,通过爆破文件描述符或猜测临时文件路径的方式,在JDBC URL的
sys_properties参数中指向这个临时文件。
恶意XML示例 (用于写入JDBC属性文件):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="poc" class="java.lang.String">
<constructor-arg value="
socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext
socketFactoryArg=file:/${catalina.home}/**/*.tmp
" />
</bean>
</beans>
此XML定义了一个String类型的Bean,其值是两行属性赋值。当被FileSystemXmlApplicationContext加载时,会将这些属性设置到系统环境中。
2.2.5 最终RCE Payload构造
最终,我们需要一个能执行命令的Spring XML配置文件。可以利用ClassPathXmlApplicationContext的不出网利用方式,或者如WP中给出的基于javax.management.loading.MLet和MethodInvokingFactoryBean加载字节码的方式。
回显Payload示例 (核心部分):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="decoder" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="javax.xml.bind.DatatypeConverter.parseBase64Binary"/>
<property name="arguments">
<list>
<value>...(Base64编码的恶意类字节码)...</value>
</list>
</property>
</bean>
<bean id="classLoader" class="javax.management.loading.MLet"/>
<bean id="clazz" factory-bean="classLoader" factory-method="defineClass">
<constructor-arg ref="decoder"/>
<constructor-arg type="int" value="0"/>
<constructor-arg type="int" value="5128"/>
</bean>
<bean factory-bean="clazz" factory-method="newInstance"/>
</beans>
其中Base64字节码是一个经过精心构造的Java类,用于执行命令并回显结果。
3. 完整利用步骤总结
- 鉴权绕过:访问URL
/api/ſuctf/connection(使用ſ代替s)。 - 写入临时文件:向目标发送一个
multipart/form-data的POST请求,上传包含第一阶段恶意XML(用于设置JDBC属性)的文件。 - 触发JDBC RCE:向
/api/ſuctf/connection接口发送JSON请求,指定使用kingbase8驱动,并通过sys_properties参数指向服务器上刚刚生成的临时文件路径。该文件中的配置会引导JDBC驱动加载第二个恶意的Spring XML配置文件(包含RCE Payload),最终实现任意代码执行。
4. 参考链接
- 先知社区原文附件: https://github.com/team-su/SUCTF-2026/tree/main/web/SU_jdbc-master
- CVE-2022-21724相关分析
- 临时文件FD爆破技巧: https://xz.aliyun.com/news/17830