Mybatis-plus sql注入及防止sql注入详解

目录
  • 一、SQL注入是什么?
  • 二、mybatis是如何做到防止sql注入的
    • 1. #{} 和 ${} 两者的区别
    • 2.PreparedStatement和Statement的区别
    • 3.什么是预编译
    • 4.mybaits-plus sql注入产生的原因
  • 三、Mybatis-plus是如何做到防止sql注入的
  • 补充:Mybatis Plus自定义全局SQL注入
  • 总结

一、SQL注入是什么?

SQL注入是一种代码注入技术,用于攻击数据驱动的应用,恶意的SQL语句被 插入到执行的SQL语句中来改变查询结果,例如: OR 1=1 或者 ;drop table sys_user;等等

二、mybatis是如何做到防止sql注入的

mybatis中我们所写的sql语句都是在xml只能完成,我们在编写sql会用到 #{},${} 这个两个表达式。那 #{} 和 ${}两者之间有什么区别嘞?下面我将用两个SQL语句例子来进行说明。

<select id="selectUserByUserName" parameterType="java.lang.String" resultType="com.domain.UserInfo">
	SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER
	<where>
		USER_ID= #{userName,jdbcType=VARCHAR}
	</where>
</select>
<select id="selectUserByUserName" parameterType="java.lang.String" resultType="com.domain.UserInfo">
	SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER
	<where>
		USER_NAME= ${userName,jdbcType=VARCHAR}
	</where>
</select>
  • 第一种SQL语句中使用的#{}方式,#{}中当传入的数据是字符串,会在使用" "双引号将值引起来。
  • 示例:例如 userName 传入的值是 9;DROP TABLE SYS_USER;那么#{}去取后得到的结果就是 USER_NAME="9;DROP TABLE SYS_USER;"就算传入删除表的命令也不会被执行,因为9;DROP TABLE SYS_USER;会帮当成一个完成的字符串去进行值匹配。
  • 第二种SQL${}方式取值,那就变成了USER_NAME=9;DROP TABLE SYS_USER; , 因 为 ${}直接将值拼接在SQL语句后面的,使其成为SQL,因此直接将值拼接在SQL语句后面的,因此${}是存在SQL注入的风险的,在使用时要注意手动处理。

1. #{} 和 ${} 两者的区别

  • #{}:解析为一个 JDBC 预编译语句,一个 #{} 被解析为一个参数占位符 ? ,#{}方式将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。 如:WHERE USER_NAME =#{username},如果传入的值是9,那么解析成sql时的值为WHERE USER_NAME =“9”,如果传入的值是12345678,则解析成的sql为WHERE USER_NAME =“12345678”,
  • ${} 仅 仅 为 一 个 纯 粹 的 s t r i n g 替 换 ,${}方式传入的变量直接拼接在sql中。如:WHERE USER_NAME = ${username},如果传入的值是9,那么解析成sql时的值为WHERE USER_NAME =9; 如果传入的值是;DROP TABLE SYS_USER;,则解析成的sql为:SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER WHERE USER_NAME="9;DROP TABLE SYS_USER;所以象 ORDER BY 或者 GROUP BY 等可以使用 ${}方式。
  • #{}方式底层采用预编译方式PreparedStatement,能够很大程度防止sql注入,因为SQL注入发生在编译时;${}方式底层只是Statement,无法防止Sql注入。
    $方式一般用于传入数据库对象,例如传入表名

2.PreparedStatement和Statement的区别

① PreparedStatement 在执行sql命令时,命令会先被数据库进行解析和编译,然后将其放到命令缓存区,然后,当每一个执行的相同的sql 命令时,若在缓存区发了编译命令,就不会再次进行解析和编译,这样就可以进行重复使用。PreparedStatement 在编译是会将每个#{}标记符号解析为参数参数占位符?,传入的变量就是做为参数,不会对sql语句进行修改,这样就能防止SQL注入的攻击。‘’

②Statement是直接将Sql命令直接交给数据库进行运行,不能做到拦截SQL注入的攻击,因为SQL注入时发生在运行时。Statement每次都会对SQL命令进行解析和编译,增加大数据库的开销,因此它效率不如PreparedStatement。

3.什么是预编译

预编译是做些代码文本的替换工作。是整个编译过程的最先做的工作。处理以# 开头的指令 , 比如拷贝 #include 包含的文件代码,#define 宏定义的替换 , 条件编译等,就是为编译做的预备工作的阶段。主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。而SQL注入只能发生在运行时。

4.mybaits-plus sql注入产生的原因

Mybatisplus中的 PaginationInterceptor 主要用于处理数据库的物理分页,避免内存分页。
分析PaginationInterceptor 的源码可以发现

Orderby场景下的SQL注入

前面提到了分页中会存在Orderby的使用,因为Orderby动态查询没办法进行预编译,所以不经过安全检查的话会存在注入风险。PaginationInnerInterceptor主要是通过设置com.baomidou.mybatisplus.extension.plugins.pagination.page对象里的属性来实现orderby的,主要是以下函数的调用,因为直接使用sql拼接,所以需要对进行排序的列名进行安全检查:

page.setAscs();
page.setDescs();

源码:

可以看出,分页是通过字符串拼接的方式,所以出现SQL注入的风险

 public static String concatOrderBy(String originalSql, IPage<?> page, boolean orderBy) {
        if (!orderBy || !ArrayUtils.isNotEmpty(page.ascs()) && !ArrayUtils.isNotEmpty(page.descs())) {
            return originalSql;
        } else {
            StringBuilder buildSql = new StringBuilder(originalSql);
            String ascStr = concatOrderBuilder(page.ascs(), " ASC");
            String descStr = concatOrderBuilder(page.descs(), " DESC");
            if (StringUtils.isNotEmpty(ascStr) && StringUtils.isNotEmpty(descStr)) {
                ascStr = ascStr + ", ";
            }

            if (StringUtils.isNotEmpty(ascStr) || StringUtils.isNotEmpty(descStr)) {
                buildSql.append(" ORDER BY ").append(ascStr).append(descStr);
            }

            return buildSql.toString();
        }
    }

三、Mybatis-plus是如何做到防止sql注入的

在使用分页的controller,对传入的分页插件,对ascs与descs进行检查,判断是否有非法字符,如有,则提示参数中含有非法的列名:create_time aaaa

示例:

校验字段的util:

package com.koal.util;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.koal.exception.BizException;
import com.koal.web.ErrorCode;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Pattern;

/**
 * @author sunrj
 */
public class RegexUtils {

    /**
     * 对Page校验防止sql注入
     *
     * @param
     */
    public static void verifyPageFileld(Page page) {

        //asc校验
        Optional.ofNullable(page.ascs()).ifPresent(ascs ->  {
            Arrays.asList(ascs).forEach(asc -> {
                boolean rightfulString = RegexUtils.isRightfulString(asc);
                if (!rightfulString) {
                    throw new BizException(ErrorCode.COMMON_VERIFY_ERROR.getCode(), "ascs参数中含有非法的列名:" + asc);
                }
            });
        });
        //desc校验
        Optional.ofNullable(page.descs()).ifPresent(descs ->  {
            Arrays.asList(descs).forEach(desc -> {
                boolean rightfulString = RegexUtils.isRightfulString(desc);
                if (!rightfulString) {
                    throw new BizException("10011", "desc参数中含有非法的列名:" + desc);
                }
            });
        });
    }

    /**
     * 判断是否为合法字符(a-zA-Z0-9-_)
     *
     * @param text
     * @return
     */
    public static boolean isRightfulString(String text) {
        return match(text, "^[A-Za-z0-9_-]+$");
    }

    /**
     * 正则表达式匹配
     *
     * @param text 待匹配的文本
     * @param reg  正则表达式
     * @return
     */
    private static boolean match(String text, String reg) {
        if (StringUtils.isBlank(text) || StringUtils.isBlank(reg)) {
            return false;
        }
        return Pattern.compile(reg).matcher(text).matches();
    }
}

controller校验page中的字段:

@GetMapping
	@ApiOperation(value = "查询用户列表", notes = "查询用户列表")
	public ServerResponse<IPage<Account>> queryAccount(Page<Account> page) {
	    //校验page中的字段,防止sql注入
		RegexUtils.verifyPageFileld(page);
		return ServerResponse.successMethod(accountService.query(page));
	}

结果:

POST http://127.0.0.1:8080/account?current=1&size=10&ascs=create_time;DROP TABLE tb_account;

结果:
{
    "code": "10011",
    "msg": "ascs参数中含有非法的列名:create_time;DROP TABLE ag_account_info;",
    "timestamp": 1653547051505
}

补充:Mybatis Plus自定义全局SQL注入

实现步骤如下:

  • 在 Mapper接口中定义相关的 CRUD方法
  • 扩展 AutoSqlInjector inject 方法,实现 Mapper接口中方法要注入的 SQL
  • 在 MP全局策略中,配置 自定义注入器

① mapper中定义业务方法

如下所示:

public interface EmployeeMapper extends BaseMapper<Employee> {
    int  deleteAll();
}

② 实现自己的MySqlInjector

如下所示:

/**
 * 自定义全局操作
 */
public class MySqlInjector  extends AutoSqlInjector{
    /**
     * 扩展inject 方法,完成自定义全局操作
     */
    @Override
    public void inject(Configuration configuration, MapperBuilderAssistant builderAssistant, Class<?> mapperClass,
            Class<?> modelClass, TableInfo table) {
        //将EmployeeMapper中定义的deleteAll, 处理成对应的MappedStatement对象,加入到configuration对象中。

        //注入的SQL语句
        String sql = "delete from " +table.getTableName();
        //注入的方法名   一定要与EmployeeMapper接口中的方法名一致
        String method = "deleteAll" ;

        //构造SqlSource对象
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);

        //构造一个删除的MappedStatement
        this.addDeleteMappedStatement(mapperClass, method, sqlSource);
    }
}

③ 把自定义的MySqlInjector 配置到全局策略中

如果是xml配置方式,实例如下:

<!-- 定义MybatisPlus的全局策略配置-->
<bean id ="globalConfiguration" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<!-- 在2.3版本以后,dbColumnUnderline 默认值就是true -->
<property name="dbColumnUnderline" value="true"></property>

<!-- Mysql 全局的主键策略 -->
<property name="idType" value="0"></property> 

<!-- 全局的表前缀策略配置 -->
<property name="tablePrefix" value="tbl_"></property>

<!--注入自定义全局操作     -->
<property name="sqlInjector" ref="mySqlInjector"></property>
</bean>

<!-- 定义自定义注入器 -->
<bean id="mySqlInjector" class="com.jane.mp.injector.MySqlInjector"></bean>

总结

到此这篇关于Mybatis-plus sql注入及防止sql注入的文章就介绍到这了,更多相关Mybatis-plus防止sql注入内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SQL注入宽字节注入由浅到深学习

    目录 前言 基础知识 宽字节 宽字节注入 例子 例题一 例题二 SQLMAP应用 结语 前言 突然想起来之前讲SQL注入时忘记讲一下这个宽字节注入了,因为这个知识点还是挺重要的,所以本文就带大家了解一下宽字节注入的原理以及应用方法,下面由我来给大家详细讲解一下. 基础知识 宽字节 在了解宽字节注入之前,我们要了解一下什么是宽字节,相对于单字节,我们引入一个字符数大小为两个字节的为宽字节,比如GBK编码,我们汉字通常使用的就是GBK编码,也就是说一次性会读取两个字节. 宽字节注入 产生宽字节注入的

  • Web网络安全解析宽字节注入攻击原理

    目录 宽字节注入攻击 宽字节注入代码分析 宽字节注入攻击 宽字节注入攻击的测试地址:http://127.0.0.1/sqli/kuanzijie.php?id=1. 访问id=1',页面返回的结果如图46所示,程序并没有报错,反而多了一个转义符(反斜杠). 图46 单引号被转义 从返回的结果可以看出,参数id=1在数据库查询时是被单引号包围的.当传入id=1'时,传入的单引号又被转义符(反斜杠)转义,导致参数ID无法逃逸单引号的包围,所以在一般情况下,此处是不存在SQL注入漏洞的.不过有一个特

  • SQL注入篇学习之盲注/宽字节注入

    目录 盲注 布尔盲注 时间盲注 盲注函数 length()函数返回字符串的长度 演示语句 burp抓包演示 宽字节注入 php魔术函数 开启方式 开启效果 作用 绕过方法 宽字节注入 总结 盲注 有时目标存在注入,但在页面上没有任何回显,此时,我们需要利用一些方法进行判断或者尝试得到数据,这个过程称之为盲注. 时间盲注其实和布尔盲注其实没有什么太大的区别,只不过是一个依靠页面是否正常判断,一个是否延时判断,在操作上其实也差不多,只不过时间注入多一个if() 布尔盲注 布尔很明显就是true和fa

  • SQL注入报错注入函数图文详解

    目录 前言 常用报错函数 用法详解 updatexml()函数 实例 extractvalue()函数 floor()函数 exp()函数 12种报错注入函数 总结 前言 报错注入的前提是当语句发生错误时,错误信息被输出到前端.其漏洞原因是由于开发人员在开发程序时使用了print_r (),mysql_error(),mysqli_connect_error()函数将mysql错误信息输出到前端,因此可以通过闭合原先的语句,去执行后面的语句. 常用报错函数 updatexml()        

  • SQL注入的四种防御方法总结

    目录 前言 限制数据类型 正则表达式匹配传入参数 函数过滤转义 预编译语句 总结 前言 最近了解到安全公司的面试中都问到了很多关于SQL注入的一些原理和注入类型的问题,甚至是SQL注入的防御方法.SQL注入真的算是web漏洞中的元老了,著名且危害性极大.下面这里就简单的分享一下我总结的四种SQL注入防御手段,加深理解,方便下次遇到这种问题时可以直接拿来使用.(主要是怕面试中脑壳打铁,这种情况太常见了) SQL注入占我们渗透学习中极大地一部分,拥有这很重要的地位.随着防御手段的不段深入,市面上存在

  • Mybatisplus详解如何注入自定义的SQL

    目录 SQL 注入器 第一步重写getMethodList 第二步定义自己的SQL方法类 第三步定义添加了自定义方法的Mapper类 测试调用 遗留问题 SQL 注入器 官方文档提供了一个小案例 自定义 Mapper 示例 解读:DefaultSqlInjector就是一个注册类,其中注册了一系列 mybatis-plus 内置的 update,insert,select SQL 语句方法, 并且对表主键是否存在进行了判定:如果设置了主键,那么会注册 DeleteById 等方法,没有则不注册.

  • springboot整合mybatis将sql打印到日志的实例详解

    在前台请求数据的时候,sql语句一直都是打印到控制台的,有一个想法就是想让它打印到日志里,该如何做呢? 见下面的mybatis配置文件: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-

  • MyBatis动态Sql之if标签的用法详解

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解如何使用if标签生成动态的Sql,主要包含以下3个场景: 1.根据查询条件实现动态查询 2.根据参数值实现动态更新某些列 3.根据参数值实现动态插入某些列 1. 使用if标签实现动态查询 假设有这样1个需求:根据用户的输入条件来查询用户列表,如果输入了用户名,就根据用户名模糊查询,如果输入了邮箱,就根据邮箱精确查询,如果同时输入了

  • Microsoft Sql server2005的安装步骤图文详解及常见问题解决方案

    一:安装sql server 2005过程中出现如下问题:"选择的功能中没有任何功能可以安装或升级": 解决方案:Microsoft SQL Server 2005→配置工具→SQL配置管理器→SQL Server 2005服务→右边的两个服务启动SQL Server FullTest Search() 和服务SQl Sever(计算机名) 二:无法将数CLSID写入\Software\Classes\PROTOCOLS\Handler\ms-help. 解决办法:退出电脑安全软件 三

  • sql server 2008 数据库管理系统使用SQL语句创建登录用户步骤详解

    废话不多说了,直接给大家贴代码了,具体代码如下所示: --服务器角色: --固定服务器角色具有一组固定的权限,并且适用于整个服务器范围. 它们专门用于管理 SQL Server,且不能更改分配给它们的权限. --可以在数据库中不存在用户帐户的情况下向固定服务器角色分配登录. --按照从最低级别的角色(bulkadmin)到最高级别的角色(sysadmin)的顺序进行描述: --1.Bulkadmin:这个服务器角色的成员可以运行BULKINSERT语句.这条语句允许从文本文件中将数据导入到SQL

  • PHP 5.6.11 访问SQL Server2008R2的几种情况详解

    PHP天生支持MySQL,但是有时候也想让它访问SQL Server,该怎么办呢? 最近找了点资料,测试成功了PHP访问SQLSvr的几种情况,限于时间,还没有测试更多不同环境,把测试过的记录如下: 测试环境:win7 x64 sp1,IIS 7.5, Apache 2.4 32位版本,PHP 5.2.6 win32, PHP 5.6.11 win32 ts(线程安全版) 注:由于"Microsoft Drivers for PHP for SQL Server"驱动程序目前只有32位

  • SQL Server 2012 FileTable 新特性详解

    FileTable是基于FILESTREAM的一个特性.有以下一些功能: •一行表示一个文件或者目录. •每行包含以下信息: • •file_Stream流数据,stream_id标示符(GUID). •用户表示和维护文件及目录层次关系的path_locator和parent_path_locator •有10个文件属性 •支持对文件和文档的全文搜索和语义搜索的类型列. •filetable强制执行某些系统定义的约束和触发器来维护命名空间的语义 •针对非事务访问时,SQL Server配置FIL

  • OraclePL/SQL单行函数和组函数详解

    正在看的ORACLE教程是:OraclePL/SQL单行函数和组函数详解. 1 函数是一种有零个或多个参数并且有一个返回值的程序.在SQL中Oracle内建了一系列函数,这些函数都可被称为SQL或PL/SQL语句,函数主要分为两大类:  2  3 单行函数  4  5 组函数   6  7 本文将讨论如何利用单行函数以及使用规则.  8  9 SQL中的单行函数 10 11 SQL和PL/SQL中自带很多类型的函数,有字符.数字.日期.转换.和混合型等多种函数用于处理单行数据,因此这些都可被统称

  • SQL开窗函数的具体实现详解

    开窗函数:在开窗函数出现之前存在着很多用 SQL 语句很难解决的问题,很多都要通过复杂的相关子查询或者存储过程来完成.为了解决这些问题,在 2003 年 ISO SQL 标准加入了开窗函数,开窗函数的使用使得这些经典的难题可以被轻松的解决.目前在 MSSQLServer.Oracle.DB2 等主流数据库中都提供了对开窗函数的支持,不过非常遗憾的是 MYSQL 暂时还未对开窗函数给予支持. 开窗函数简介:与聚合函数一样,开窗函数也是对行集组进行聚合计算,但是它不像普通聚合函数那样每组只返回一个值

  • sql server 交集,差集的用法详解

    概述 为什么使用集合运算: 在集合运算中比联接查询和EXISTS/NOT EXISTS更方便. 并集运算(UNION) 并集:两个集合的并集是一个包含集合A和B中所有元素的集合. 在T-SQL中.UNION集合运算可以将两个输入查询的结果组合成一个结果集.需要注意的是:如果一个行在任何一个输入集合中出现,它也会在UNION运算的结果中出现.T-SQL支持以下两种选项: (1)UNION ALL:不会删除重复行 -- union allselect country, region, city fr

  • SQL的各种连接Join案例详解

    最常见的 JOIN 类型:SQL INNER JOIN(简单的 JOIN).SQL LEFT JOIN.SQL RIGHT JOIN.SQL FULL JOIN,其中前一种是内连接,后三种是外链接. 假设我们有两张表,Table A是上边的表,Table B是下边的表. Table A id name 1 Google 2 淘宝 3 微博 4 Facebook Table B id address 1 美国 5 中国 3 中国 6 美国 一.INNER JOIN 内连接是最常见的一种连接,只连接

随机推荐