2026-SUCTF-JDBC-Master:鉴权绕过与JDBC任意代码执行
字数 3281
更新时间 2026-03-26 14:32:13

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;
    }
}

规则说明:

  1. 如果路径包含suctf(不区分大小写)或经过正则模糊匹配,则返回403。
  2. 规则使用了toLowerCase(),将所有字符转为小写后再进行contains("suctf")判断。

2.1.3 路由匹配逻辑

  • WebConfig中,AntPathMatchersetCaseSensitive(false)被设置为false,意味着路由匹配不区分大小写
  • 路由匹配最终会调用String.regionMatches方法,该方法在ignoreCasetrue时,会尝试将字符转换为大写小写进行匹配。只要大小写转换后能匹配,即视为成功。

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连接。存在两重过滤:

  1. 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

  2. 参数黑名单过滤:禁止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参数指定的文件,并加载其中的属性。

利用链

  1. 利用JDBC URL参数 sys_properties 指向一个可控的临时文件。
  2. 在该文件中设置 socketFactorysocketFactoryArg 属性(虽然这两个参数在黑名单中,但通过文件加载可以绕过URL参数的黑名单检查)。
  3. socketFactory 被设置为 org.springframework.context.support.FileSystemXmlApplicationContext
  4. socketFactoryArg 指向一个包含恶意Spring XML配置的本地文件,从而触发Spring相关类的实例化,实现远程代码执行。

2.2.4 无文件上传场景下的文件写入
题目没有文件上传功能。这里利用了一个技巧:通过multipart/form-data上传文件时,服务器会先将文件内容写入临时文件,请求处理完毕后再删除

利用步骤

  1. 构造一个multipart/form-data请求,上传一个包含恶意Spring XML配置的文件(例如命名为a.txt)。
  2. 服务器会在${catalina.home}/work/(或其他临时目录)下生成一个临时文件(如/tmp/tomcat.8080.1234567890123456789/upload_xxx.tmp),其内容就是我们上传的XML。
  3. 在临时文件被删除前,通过爆破文件描述符或猜测临时文件路径的方式,在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.MLetMethodInvokingFactoryBean加载字节码的方式。

回显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. 完整利用步骤总结

  1. 鉴权绕过:访问URL /api/ſuctf/connection (使用ſ代替s)。
  2. 写入临时文件:向目标发送一个multipart/form-data的POST请求,上传包含第一阶段恶意XML(用于设置JDBC属性)的文件。
  3. 触发JDBC RCE:向/api/ſuctf/connection接口发送JSON请求,指定使用kingbase8驱动,并通过sys_properties参数指向服务器上刚刚生成的临时文件路径。该文件中的配置会引导JDBC驱动加载第二个恶意的Spring XML配置文件(包含RCE Payload),最终实现任意代码执行。

4. 参考链接

  1. 先知社区原文附件: https://github.com/team-su/SUCTF-2026/tree/main/web/SU_jdbc-master
  2. CVE-2022-21724相关分析
  3. 临时文件FD爆破技巧: https://xz.aliyun.com/news/17830
相似文章
相似文章
 全屏