栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

mybatis中如何防止sql注入和传参

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

mybatis中如何防止sql注入和传参

环境

使用mysql,数据库名为test,含有1表名为users,users内数据如下

JDBC下的SQL注入

在JDBC下有两种方法执行SQL语句,分别是Statement和PrepareStatement,即其中,PrepareStatement为预编译

Statement

SQL语句

SELECt * FROM users WHERe username = '" + username + "' AND password = '" + password + "'

当传入数据为

username = admin
password = admin
SELECt * FROM users WHERe username = 'admin' AND password = 'admin';

即当存在username=admin和password=admin的数据时则返回此用户的数据

万能密码:admin’ and 1=1#

【一>所有资源获取<一】
1、200份很多已经买不到的绝版电子书
2、30G安全大厂内部的视频资料
3、100份src文档
4、常见安全面试题
5、ctf大赛经典题目解析
6、全套工具包
7、应急响应笔记
8、网络安全学习路线

最终的sql语句变为了

SELECt * FROM users WHERe username = 'admin' and 1=1#

即返回用户名为admin,同时1=1的所有数据,1=1恒为真,所以始终返回所有数据

如果输入的时:admin’ or 1=1#就会返回所有数据,因为admin’ or 1=1恒为真

所以JDBC使用Statement是不安全的,需要程序员做好过滤,所以一般使用JDBC的程序员会更喜欢使用PrepareStatement做预编译,预编译不仅提高了程序执行的效率,还提高了安全性

PreParedStatement

与Statement的区别在于PrepareStatement会对SQL语句进行预编译,预编译的好处不仅在于在一定程度上防止了sql注入,还减少了sql语句的编译次数,提高了性能,其原理是先去编译sql语句,无论最后输入为何,预编译的语句只是作为字符串来执行,而SQL注入只对编译过程有破坏作用,执行阶段只是把输入串作为数据处理,不需要再对SQL语句进行解析,因此解决了注入问题

因为SQL语句编译阶段是进行词法分析、语法分析、语义分析等过程的,也就是说编译过程识别了关键字、执行逻辑之类的东西,编译结束了这条SQL语句能干什么就定了。而在编译之后加入注入的部分,就已经没办法改变执行逻辑了,这部分就只能是相当于输入字符串被处理

而Statement方法在每次执行时都需要编译,会增大系统开销。理论上PrepareStatement的效率和安全性会比Statement要好,但并不意味着使用PrepareStatement就绝对安全,不会产生SQL注入。

PrepareStatement防御预编译的写法是使用?作为占位符然后将SQL语句进行预编译,由于?作为占位符已经告诉数据库整个SQL语句的结构,即?处传入的是参数,而不会是sql语句,所以即使攻击者传入sql语句也不会被数据库解析

String sql = "SELECt * FROM users WHERe username = ? AND password = ?";

//预编译sql语句

PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setString(1,username);
pstt.setString(2, password);

ResultSet resultSet = pstt.executeQuery();//返回结果集,封装了全部的产部的查询结果

首先先规定好SQL语句的结构,然后在对占位符进行数据的插入,这样就会对sql语句进行防御,攻击者构造的paylaod会被解释成普通的字符串,我们可以通过过输出查看最终会变成什么sql语句

可以发现还会对单引号进行转义,一般只能通过宽字节注入,下面将会在代码的层面展示为什么预编译能够防止SQL注入,同时解释为什么会多出一个转义符

不安全的预编译 拼接

总所周知,sql注入之所以能被攻击者利用,主要原因在于攻击者可以构造payload,虽然有的开发人员采用了预编译但是却由于缺乏安全思想或者是偷懒会直接采取拼接的方式构造SQL语句,此时进行预编译则无法阻止SQL注入的产生

代码(稍稍替换一下上面的代码):

//创建sql语句
String sql = "SELECt * FROM users WHERe username = '" + req.getParameter("username") + "' AND password = '" + req.getParameter("password") + "'";
System.out.println(sql);
//预编译sql语句
PreparedStatement pstt = connection.prepareStatement(sql);
ResultSet resultSet = pstt.executeQuery(sql);//返回结果集,封装了全部的产部的查询结果

这样即使使用了预编译,但是预编译的语句已经是被攻击者构造好的语句,所以无法防御SQL注入

又或者是前面使用?占位符后,又对语句进行拼接,也会导致SQL注入

想要做到阻止sql注入,首先要做到使用?作为占位符,规定好sql语句的结构,然后在后面不破坏结构

使用in语句

String sql = "delete from users where id in("+delIds+");

此删除语句大多用在复选框内,在in当中使用拼接而不使用占位符做预编译的原因是因为很多时候无法确定deIds里含有多少个对象

输入:1,2

正常只会输出id为1和2的值

如果此时输入:1,2) or 1=1#

就会形成SQL注入,输出苦库里所有的值

正确写法:

还是要用到预编译,所以我们要对传入的对象进行处理,首先确定对象的个数,然后增加同量的占位符?以便预编译

public int gradeDelete(Connection con, String delIds) throws Exception{
    String num = "";
    //将对象分割开来,分割的点以实际而定
    String[] spl = delIds.split(".");

    //根据对象的个数添加同量的占位符?,用来预编译
    for(int i = 0; i< spl.length; i++){
        if(i == 0){
            num += "?";
        } else {
            num += ".?";
        }
    }
    String sql = "delete from users where id in("+num+")";
    prepareStatement pstmt = con.prepareStatement(sql);
    try {
        for(int j = 0; j < spl.length; j++){
            pstmt.setInt(j+1, integer.parseint(spl[j]));
        }
        return pstmt.executeUpdate();
    } catch(Exception e){
        //
    }

    return 0;
}

以bilibili的删除视频为例,当我取消收藏夹复数个视频的收藏时抓到的包为

892223071%3A2%2C542789708%3A2%2C507228005%3A2%2C422244777%3A2%2C549672309%3A2%2C719381183%3A2%2C976919238%3A2%2C722053417%3A2
解码后为
892223071:2,542789708:2,507228005:2,422244777:2,549672309:2,719381183:2,976919238:2,722053417:2

可以发现其以:2,分割,那我们只需在split中填写

String[] spl = delIds.split(":2,");

即可,结果为:

然后再使用预编译

使用like语句
boolean jud = true;
String sql = "select * from users ";
System.out.println("请输入要查询的内容:");
String con = sc.nextLine();
for (int i = 0; i < con.length(); i++){
    if(!Character.isDigit(con.charAt(i))){
        jud = false;
        break;
    }
}
if(jud){
    sql += "where password like '%" + con + "%'";
}else{
    sql += "where username like '%" + con + "%'";
}

当用户输入的为字符串则查询用户名和密码含有输入内容的用户信息,当用户输入的为纯数字则单查询密码,用拼接地方式会造成SQL注入

正常执行:

SQL注入

正确写法

首先我们要将拼接的地方全部改为?做占位符,但是使用占位符后要使用setString来把传入的参数替换占位符,所以我们要先进行判断,判断需要插入替换多少个占位符

boolean jud = true;
int v = 0;
String sql = "select * from users ";
System.out.println("请输入要查询的内容:");
String con = sc.nextLine();
for (int i = 0; i < con.length(); i++){
    if(!Character.isDigit(con.charAt(i))){
        jud = false;
        break;
    }
}
if(jud){
    sql += "where password like ?";
    v = 1;
}else{
    sql += "where username like ? and password like ?";
    v = 2;
}

//预编译sql语句
PreparedStatement pstt = connection.prepareStatement(sql);
if(v == 1){
    pstt.setString(1, "%"+con+"%");
}else if (v == 2){
    pstt.setString(1, "%"+con+"%");
    pstt.setString(2, "%"+con+"%");
}

尝试进行SQL注入

发现被转义了

使用order by语句

通过上面对使用in关键字和like关键字发现,只需要对要传参的位置使用占位符进行预编译时似乎就可以完全防止SQL注入,然而事实并非如此,当使用order by语句时是无法使用预编译的,原因是order by子句后面需要加字段名或者字段位置,而字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名,然而使用PreapareStatement将会强制给参数加上’,我在下面会在代码层面分析为什么会这样处理参数

所以,在使用order by语句时就必须得使用拼接的Statement,所以就会造成SQL注入,所以还要在过滤上做好防御的准备

调试分析PrepareStatement防止SQL注入的原理

进入调试,深度查看PrepareStatement预编译是怎么防止sql注入的

用户名输入admin,密码输入admin’,目的是查看预编译如何对一个合理的字符串以及一个不合理的字符串进行处理

由于我们输入的username和password分别是admin和admin’,而其中admin’属于非法值,所以我们只在

pstt.setString(2, password);

打上断点,然后步入setString方法

步过到2275行,这里有一个名为needsQuoted的布尔变量,默认为true

然后进入if判断,其中有一个方法为isEscapeNeededForString

步入后发现有一个布尔的needsHexEscape,默认为false,然后将字符串,也就是传入的参数admin’进行逐字解析,判断是否有非法字符,如果有则置needsHexEscape为true且break循环,然后返回needsHexEscape

由于我们传入的是admin’,带有’单引号,所以在switch的过程中会捕捉到,置needsHexEscape = true后直接break掉循环,然后直接返回needsHexEscape

向上返回到了setString方法,经过if判断后运行if体里面的代码,首先创建了一个StringBuilder,长度为传入参数即admin+2,然后分别在参数的开头和结尾添加’

简单来说,此switch体的作用就是对正常的字符不做处理,直接向StringBuilder添加同样的字符,如果非法字符,则添加转移后的非法字符,由于不是直接的替换,而是以添加的方式,简单来说就是完全没有使用到用户传入的的参数,自然就做到了防护

我们传入的为admin’,被switch捕捉到后’后会在StringBuilder添加和’,最终我们的admin’会变为’admin’,也就是’admin’,同样防止了SQL注入最重要的一环–闭合语句

然后根据要插入占位符的位置进行插入

Mybatis下的SQL注入 Mybatis的两种传参方式

首先要了解在Mybatis下有两种传参方式,分别是KaTeX parse error: Expected 'EOF', got '#' at position 5: {}以及#̲{},其区别是,使用{}的方式传参,mybatis是将传入的参数直接拼接到SQL语句上,二使用#{}传参则是和JDBC一样转换为占位符来进行预编译

在#{}下运行的结果:

select * from users where username = #{username} and password = #{password}

在${}下运行的结果:

select * from users where username = "${username}" and password = "${password}"

SQL注入 ${}

PeopleMapper设置


    select * from users where username = #{username} and password = #{password}

正常运行:

username:admin
password:admin

sql注入:

username:admin" and 1=1#
password:sef

成功sql注入

#{}

Mapper设置