警惕!铭飞CMS5.2.9重大SQL漏洞曝光及修复指南

作者:后端小肥肠

🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案

🍊 有疑问可私信或评论区联系我。

🥑  创作不易未经允许严禁转载。

目录

1. 前言

2.什么是SQL注入漏洞

2.1 SQL注入的原理

2.2 SQL注入的危害

2.3 SQL注入的类型

2.4 常见的SQL注入入口

2.5 SQL注入的预防方法

3. SQL注入漏洞修复代码实现

4. 结语


1. 前言

因为项目需求,需要搭建一个门户网站,经过一番技术选型,最终选择了 铭飞CMS

铭飞官网:

铭软・铭飞官网・低代码开发平台・免费开源Java Cms

铭飞源码:

MCMS: 🌈🌈🌈祝开发者国庆节快乐!免费可商用的开源Java CMS内容管理系统/基于SpringBoot 2/前端Vue3/element plus/提供上百套模板,同时提供实用的插件/每两个月收集issues问题并更新版本/一套简单好用开源免费的Java CMS内容管理系/一整套优质的开源生态内容体系。我们的使命就是降低开发成本提高开发效率,提供全方位的企业级开发解决方案。

然而,系统上线后被安全扫描发现存在 SQL注入漏洞向铭飞客服咨询后,对方建议升级系统。

于系统已上线,升级版本有一定的代价(配置成本),因此我选择了通过 源码改造 来解决问题。幸运的是,最终成功修复了漏洞。

如果你也遇到类似问题,接下来这篇文章将带你一步步解决!

2.什么是SQL注入漏洞

SQL注入(SQL Injection) 是一种常见的网络安全漏洞,主要发生在应用程序与数据库交互的过程中。攻击者通过构造恶意的SQL语句,将其注入到应用程序的输入参数中,从而达到执行未授权操作的目的,如篡改数据、泄露敏感信息,甚至破坏整个数据库系统。

2.1 SQL注入的原理

SQL注入的根本原因在于 应用程序没有对用户输入进行有效的过滤和校验,导致攻击者能够通过输入框、URL参数、表单等途径向数据库执行恶意SQL语句。

攻击示例:

假设有一个登录查询:

SELECT * FROM users WHERE username = 'user_input' AND password = 'pass_input';

如果攻击者输入如下内容:

  • 用户名:admin' --
  • 密码:任意内容

则查询变为:

SELECT * FROM users WHERE username = 'admin' --' AND password = '任意内容';

-- 表示SQL中的注释符,后面的部分被忽略。这使得SQL语句仅验证 username = 'admin',绕过了密码校验,直接登录成功。

2.2 SQL注入的危害

SQL注入漏洞的危害极大,包括但不限于:

  1. 数据泄露攻击者可以查询数据库中的敏感数据,比如用户密码、信用卡信息等。
  2. 数据篡改攻击者可以修改数据库中的数据,如篡改用户权限、删除数据记录等。
  3. 服务器破坏攻击者可以通过恶意SQL语句执行系统命令,甚至控制整个服务器。
  4. 业务逻辑破坏攻击者可利用SQL注入绕过权限验证,导致系统业务逻辑失效。
  5. 导致数据持久化损坏破坏或删除数据库,造成严重的业务数据丢失。

2.3 SQL注入的类型

根据SQL注入的攻击方式,可以分为以下几种类型:

  1. 基于错误回显的注入
    攻击者通过在输入中构造错误语句,利用数据库返回的错误信息来判断SQL执行情况。

  2. 基于联合查询的注入(Union-based)
    利用SQL的 UNION 语句,合并查询结果,达到数据窃取的目的。

  3. 基于盲注的注入(Blind SQL Injection)
    数据库不返回具体错误信息,攻击者通过布尔条件或时间延迟等方式判断注入结果。

  4. 基于时间的注入(Time-based)
    通过构造带有 SLEEP() 等时间延迟函数的语句,根据响应时间判断注入成功与否。

  5. 堆叠查询注入
    攻击者利用数据库支持的多条查询执行能力,通过分号 ; 执行多个SQL语句。

2.4 常见的SQL注入入口

SQL注入通常发生在以下几种输入位置:

  1. 表单输入如登录表单、搜索框、评论输入框等。
  2. URL参数id=123name='test' 等。
  3. Cookie数据攻击者可以在Cookie中注入SQL语句。
  4. HTTP请求头User-AgentReferer 等头信息中隐藏恶意SQL代码。
  5. JSON请求体在前后端分离项目中,POST/PUT请求的JSON体中存在注入风险。

2.5 SQL注入的预防方法

为了防止SQL注入漏洞,开发者可以采取以下安全措施:

  1. 使用预编译的SQL语句
    使用 PreparedStatement 或ORM框架(如MyBatis、Hibernate)提供的参数化查询,确保SQL与用户输入分离。

    String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; PreparedStatement stmt = connection.prepareStatement(sql); stmt.setString(1, username); stmt.setString(2, password);
  2. 对输入数据进行严格校验与过滤
    通过正则表达式限制输入的内容,禁止特殊字符(如 --'/* 等)。

  3. 最小化数据库权限
    为数据库用户设置最小权限,防止攻击者执行敏感操作。

  4. 使用Web应用防火墙(WAF)
    通过安全工具拦截SQL注入攻击请求。

  5. 日志与监控
    记录异常SQL执行日志,及时发现并阻止潜在攻击。

3. SQL注入漏洞修复代码实现

铭飞的源码我就不拆解了,就是一个SpringBoot项目,直接贴修复代码:

1.编写SqlInjectionInterceptor

@Component
public class SqlInjectionInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(SqlInjectionInterceptor.class);

    private static final String SQL_INJECTION_PATTERN =
            "(?i)\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

    private static final Pattern URL_REQUEST_CHAR_PATTERN = Pattern.compile("[<>%;()&'\"]");

    private static final Pattern[] XSS_PATTERNS = {
            Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
    };

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 检查请求参数
        if (!checkParameters(request)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid input detected");
            return false;
        }

        // 检查请求体(对于POST和PUT请求)
        if ("POST".equalsIgnoreCase(request.getMethod()) || "PUT".equalsIgnoreCase(request.getMethod())) {
            if (!checkRequestBody(request)) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid input detected in request body");
                return false;
            }
        }

        return true;
    }

    private boolean checkParameters(HttpServletRequest request) {
        for (String paramName : request.getParameterMap().keySet()) {
            String[] values = request.getParameterValues(paramName);
            if (values != null) {
                for (String value : values) {
                    try {
                        // 对 URL 编码后的参数进行解码再检查
                        String decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8.name());
                        if (!isValidInput(decodedValue)) {
                            log.warn("Potentially malicious input detected in parameter '{}': {}", paramName, decodedValue);
                            return false;
                        }
                    } catch (Exception e) {
                        log.error("Error decoding parameter: {}", paramName, e);
                        return false;
                    }
                }
            }
        }
        return true;
    }

    private boolean checkRequestBody(HttpServletRequest request) throws IOException {
        String body = getRequestBody(request);
        if (StringUtils.isNotBlank(body)) {
            try {
                JSONObject json = JSONObject.parseObject(body);
                for (String key : json.keySet()) {
                    if (!isValidInput(json.getString(key))) {
                        log.warn("Potentially malicious input detected in request body for key '{}': {}", key, json.getString(key));
                        return false;
                    }
                }
            } catch (Exception e) {
                // 不是JSON格式时,直接检查整个body
                if (!isValidInput(body)) {
                    log.warn("Potentially malicious input detected in raw body: {}", body);
                    return false;
                }
            }
        }
        return true;
    }

    private boolean isValidInput(String input) {
        return !containsXSS(input) && !containsSQLInjection(input) && !containsIllegalURLCharacters(input);
    }

    private boolean containsXSS(String value) {
        if (StringUtils.isBlank(value)) {
            return false;
        }
        for (Pattern pattern : XSS_PATTERNS) {
            if (pattern.matcher(value).find()) {
                log.warn("XSS attack detected: {}", value);
                return true;
            }
        }
        return false;
    }

    private boolean containsSQLInjection(String value) {
        if (StringUtils.isBlank(value)) {
            return false;
        }
        if (Pattern.compile(SQL_INJECTION_PATTERN, Pattern.CASE_INSENSITIVE).matcher(value).find()) {
            log.warn("SQL Injection attack detected: {}", value);
            return true;
        }
        return false;
    }

    private boolean containsIllegalURLCharacters(String value) {
        if (StringUtils.isBlank(value)) {
            return false;
        }
        if (URL_REQUEST_CHAR_PATTERN.matcher(value).find()) {
            log.warn("Illegal URL character detected: {}", value);
            return true;
        }
        return false;
    }

    private String getRequestBody(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        }
        return sb.toString();
    }
}

这段代码实现了一个基于 Spring 的拦截器,用于防止 SQL 注入和 XSS 攻击。在每个 HTTP 请求到达控制器之前,通过拦截请求参数和请求体内容,利用正则表达式检测常见的 SQL 注入特征(如 UNION SELECTDROP TABLE 等)和 XSS 攻击特征(如 <script> 标签、javascript: 协议等)。一旦检测到恶意输入,立即阻止请求并返回 HTTP 400 错误,从而有效提升系统的安全性。 

 2. WebMvcConfigurer配置SqlInjectionInterceptor

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 排除配置
        registry.addInterceptor(actionInterceptor()).excludePathPatterns("/static/**", "/app/**", "/webjars/**",
                "/*.html", "/*.htm");
        // 添加新的 SQL 注入防护拦截器
        registry.addInterceptor(sqlInjectionInterceptor)
                .addPathPatterns("/**");
    }

这段代码通过 addInterceptors 方法将两个拦截器注册到 Spring 的拦截器链中,其中 actionInterceptor 拦截器对部分静态资源路径(如 /static/**/*.html 等)进行排除,而 sqlInjectionInterceptor 则对所有请求路径(/**)启用 SQL 注入防护拦截器,用于检测并阻止潜在的恶意请求,保障系统安全性。 

4. 结语

本文通过自定义 SqlInjectionInterceptor WebMvcConfigurer 配置,实现了对恶意输入的拦截,有效提升了系统安全性。

希望这篇文章能帮助到同样面临类似问题的开发者。安全漏洞不可忽视,及时排查与防护是保障项目稳定运行的关键。遇到类似安全挑战时,不妨深入源码,找到适合自身项目的最佳解决方案!

MCMScms建站系统完整开源!基于SpringBoot 2架构,前端基于vue、element ui。每月28定期更新版本,为开发者提供上百套免费模板,同时提供适用的插件(文章、商城、微信、论坛、会员、评论、支付、积分、工作流、任务调度等...),一套简单好用的开源系统、一整套优质的开源生态内容体系。的使命就是降低开发成本提高开发效率,提供全方位的企业级开发解决方案。 特点: 免费完整开源:基于MIT协议,源代码完全开源,无商业限制,MS开发团队承诺将MCMS内容系统永久完整开源; 标签化建站:不需要专业的后台开发技能,只要使用系统提供的标签,就能轻松建设网站; html静态化:系统支持全站静态化; 跨终端:站点同时支持PC与移动端访问,同时会自动根据访问的终端切换到对应的界面,数据由系统统一管理; 海量模版:通过MStore(MS商城)分享更多免费、精美的企业网站模版,降低建站成本; 丰富插件:为了让MCms适应更多的业务场景,在MStore用户可以下载对应的插件,如:站群插件、微信插件、商城插件等; 每月更新:团队承诺每月28日为系统升级日,分享更多好用等模版与插件; 文档丰富:为了让用户更快速的使用MCms系统进行开发,团队持续更新开发相关文档,如标签文档、使用文档、视频教程等; 开发环境: 建议开发者使用以下环境,这样避免版本带来的问题 Windows、Linux Eclipse、Idea Mysql5.7 JDK≧8 Tomcat≧8   MCMScms建站系统 更新日志: v5.2.1 【框架】sprintboot版本更新到2.2.13.RELEASE 【新增】全局异常处理 【优化】搜索功能优化 【优化】标签脚本优化 【修复修复MStore功能 【修复】具体参考开源中国ISSUES 5.2.0升级5.2.1步骤( 1、同步代码; 2、导入5.2.0-up-5.2.1.sql)
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端小肥肠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值