结合Mybatis聊聊对SQL注入的见解

Mybatis聊聊对SQL注入的见解

1.sql注入是什么

sql注入见名思意,是指一些非法用户通过将一些特殊字符或者sql语句插入到要提交的表单之中,从而让服务器在不知情的情况下执行恶意的sql命令,从而引发一系列的安全隐患。

讲的通俗一点就是说,用户利用sql语法将一些sql语句加在某些字段后面,提交表单的时候,服务器执行sql命令并未达到想要的结果反而引发异常和数据泄露。

这就是典型的系统漏洞,因此sql注入对系统的危害是非常大的,做好防止sql注入也是系统必须完善的。

2.sql注入实例

我写了个简单的登录程序,框架是Spring+SpringMVC+Mybatis。表结构如下:

(1)特殊符号,如 " 和 .等

可以看到当我输入一个特殊符号,而后台未经过过滤的时候,便会抛出sql异常:

并且如果没有进行全局异常处理,用户便可以通过浏览器的开发者模式中获取到数据库和查询语句的相关信息。

而这个对系统来说是很危险的漏洞。

(2)sql语句的注入

当输入正确的账号密码的时候,可以看到执行时成功的。

但是如果加上这么一句sql语句在表单,我们可以看到结果依然可以执行成功

非法用户可以通过这个来测试数据库的表名字、该表的其他字段名、数据的量级等等。

举个例子:尝试获取到该表的其他字段名

只需要将刚才的表单中的“1=1”替换成测试sql语句即可,如果执行成功则代表该表中有这个字段存在。

看一下最后执行的sql语句:

就一目了然了。这通用是因为注入引起的。

类似的还有:"or""=" 不仅能执行成功,同时还有查询多全部的数据。

(3)通过sql语句的in和order by进行注入

以上的三点主要是因为在mybatis中使用了${}, sql语句没有执行预编译,无法防止sql注入。

3.mybatis框架下如何解决sql注入问题

mybatis框架本身就有防止sql注入的特性,这就要求我们必须在写sql语句时候遵循框架的书写规范。

(1)用#{param}替换所有的${param}

因为${}是拼接sql字符实现没有预编译的查询,因为是无法防御sql注入,而#{}则需要进行预编译,可以很大程度上防止sql注入。

在一些#{}使用时候会报错的地方,如like 查询、in 查询、order by排序等,

a) like:

select * from Users where username like "%${username}%"

替换成:

select * from Users where username like concat("%", #{username}, "%")

b) in

select * from Users where id in (${id})

替换成mybatis框架自带的foreach循环:

c) order by

不要直接使用:拼接排序

如:

select *from Users order by ${id}

而是在java层面做映射,然后用<if>来做判断

其他的类似的情况请按照相同的处理方式进行处理即可。

(2)对用户输入的数据进行sql注入校验

SqlServer本身的防sql注入机制,利用存储过程可以避免sql注入。应该禁止用户输入一些关键的特殊符号,如分号、分隔符、单引号等,同时对于一些关键位置进行sql关键字的屏蔽,如or、and等。必须对用户输入的内容的类型、长度、格式、范围进行校验。

(3)要对用户的权限进行区分

普通用户的权限和管理员的权限之间,必须严格区分开来,对管理员实现安全级别更高的验证,从而防止人为获取到更高权限时候的sql注入攻击。

(4)更高级别的验证

在后端代码和数据库中都开启对sql注入的验证,同时用专业的注入工具查找本系统的漏洞进行修复,也可以进行账号诱骗,将一些如“admin”之类的容易受到攻击的用户设置上千位的密码,让攻击者的软件因为解析量大而负载过大,从而耗尽资源而宕机。

一种常见的Mybatis的SQL注入

一、场景重现

现在有一张名为student的表,表结构如下:

在这里插入图片描述

其中id为自增主键,余下字段分别为英语成绩、数学成绩、美术成绩、学生的学号、学生的姓名、学生的电话。

目前表里面有6条数据:

在这里插入图片描述

二、代码展示

使用mybatis框架实现根据美术成绩查找相应的记录,在mapper.xml文件里,代码大概会这么写:

 <!--通过美术成绩作为筛选条件查询-->
    <select id="queryByArtGrade" resultMap="StudentMap">
        select
        id, english_grade, math_grade, art_grade, number, name, telephone
        from student
        <where>
            <if test="artGrade != null">
                and art_grade = #{artGrade}
            </if>
        </where>
    </select>

当然也可以这么写:

 <!--通过美术成绩作为筛选条件查询-->
    <select id="queryByArtGrade" resultMap="StudentMap">
        select
        id, english_grade, math_grade, art_grade, number, name, telephone
        from student
        <where>
            <if test="artGrade != null">
                and art_grade = ${artGrade}
            </if>
        </where>
    </select>

这两种写法的区别在于对传入参数的接收方式不同,前者是#{property},后者是${property}。

三、模拟请求&回应结果

想查询美术成绩为70的记录,使用postman进行请求,传入参数json为:

{

"artGrade":"70"

}

测试结果两者均为:

在这里插入图片描述

看起来好像是一样的结果,两种写法都正确。

但是我把参数改成下面的这个样子:

{

"artGrade":""70" and id="4""

}

结果就完全不同啦!

使用#{property}返回的结果为空:

在这里插入图片描述

通过日志,发现实际执行的SQL为:

select id, english_grade, math_grade, art_grade, number, name, telephone from classroom.student WHERE art_grade = ""70" and id="4""

使用${property}返回的结果为一条:

在这里插入图片描述

通过日志,发现实际执行的SQL为:

select id, english_grade, math_grade, art_grade, number, name, telephone from classroom.student WHERE art_grade = "70" and id="4"

四、小结一下

这就是常见的sql注入了,因为后端接收传入参数的时候没有经过任何处理,直接到了mybatis层,而mybatis层接收参数的时候又使用了${property}这种方式,导致参数被直接拼接至预执行的SQL中,最后返回了一些恶意请求者需要的信息。

所以,mapper中尽量避免使用${property}来接收参数,因为你不知道上一层传进来的东西到底是什么。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持云海天教程。