MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入会产生 SQL 注入 不会产生 SQL 注入问题 我们传递的参数是 aaa or 1 1 导致查询出来了全部的数据

欢迎大家来到IT世界,在知识的湖畔探索吧!

一、MyBatis中${}和#{}的区别

1.1${}和#{}演示

数据库数据:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

欢迎大家来到IT世界,在知识的湖畔探索吧!

dao接口:

List<User> findByUsername(String username); List<User> findByUsername2(String username); 

欢迎大家来到IT世界,在知识的湖畔探索吧!

Mapper.xml:

欢迎大家来到IT世界,在知识的湖畔探索吧!<!-- 使用#{} --> <select id="findByUsername" parameterType="java.lang.String" resultType="com.lscl.entity.User"> select * from user where username like #{username} </select> <!-- 使用${},注意${}中的值必须要填value --> <select id="findByUsername2" parameterType="java.lang.String" resultType="com.lscl.entity.User"> select * from user where username like '%${value}%' </select> 

执行测试代码:

@Test public void findByUsername() throws Exception { InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); // true:自动提交 SqlSession session = factory.openSession(true); UserDao userDao = session.getMapper(UserDao.class); List<User> userList = userDao.findByUsername("%小%"); List<User> userList2 = userDao.findByUsername2("小"); System.out.println("userList: "); for (User user : userList) { System.out.println(user); } System.out.println("userList2: "); for (User user : userList2) { System.out.println(user); } session.close(); in.close(); } 

查看执行结果:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

发现都能够查询出来

1.2 SQL注入问题

${}会产生SQL注入,#{}不会产生SQL注入问题

我们做一个测试:

欢迎大家来到IT世界,在知识的湖畔探索吧!List<User> userList2 = userDao.findByUsername2(" aaa' or 1=1 -- "); System.out.println("userList2: "); for (User user : userList2) { System.out.println(user); } 

查询生成的SQL语句:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

我们传递的参数是aaa’ or 1=1 –,导致查询出来了全部的数据。

大家可以想象一下,如果我是要根据id删除呢?

delete from user where id='${value}' 

如果我传递的是:1′ or 1=1; –,结果会是什么样,我想大家应该已经知道了。

我这里id是Integer类型,不好测试,就不带大家测试了,大家有兴趣可以自己私下测试。

如果上面使用的是#{}就不会出现SQL注入的问题了

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

1.3${}和#{}的区别

#{}匹配的是一个占位符,相当于JDBC中的一个?,会对一些敏感的字符进行过滤,编译过后会对传递的值加上双引号,因此可以防止SQL注入问题。

${}匹配的是真实传递的值,传递过后,会与sql语句进行字符串拼接。${}会与其他sql进行字符串拼接,不能预防sql注入问题。

查看#{}${}生成的SQL语句:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

欢迎大家来到IT世界,在知识的湖畔探索吧!String abc=“123”; #{abc}="123" ${value}=123; 

1.4#{}底层是如何防止SQL注入的?

1.4.1 网上的答案

网上关于这类问题非常多,总结出来就两个原因:

1)#{}底层采用的是PreparedStatement,会预编译,因此不会产生SQL注入问题;

其实预编译是MySQL自己本身的功能,和PreparedStatement没关系;而且预编译也不是咱们理解的那个预编译,再者PreparedStatement底层默认根本没有用到预编译(要我们手动开启)!详细往下看

2)#{}不会产生字符串拼接,${}会产生字符串拼接,因此${}会出现SQL注入问题;

这两个答案都经不起深究,最终答案也只是停留在表面,也没人知道具体是为什么。

1.4.2 为什么能防止SQL注入?

我们翻开MySQL驱动的源码一看究竟;

打开PreparedStatement类的setString()方法(MyBatis在#{}传递参数时,是借助setString()方法来完成,${}则不是):

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

setString()方法全部源码:

public void setString(int parameterIndex, String x) throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) { if (x == null) { this.setNull(parameterIndex, 1); } else { this.checkClosed(); int stringLength = x.length(); StringBuilder buf; if (this.connection.isNoBackslashEscapesSet()) { boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength); Object parameterAsBytes; byte[] parameterAsBytes; if (!needsHexEscape) { parameterAsBytes = null; buf = new StringBuilder(x.length() + 2); buf.append('\''); buf.append(x); buf.append('\''); if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(buf.toString()); } this.setInternal(parameterIndex, parameterAsBytes); } else { parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(x); } this.setBytes(parameterIndex, parameterAsBytes); } return; } String parameterAsString = x; boolean needsQuoted = true; if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) { needsQuoted = false; buf = new StringBuilder((int)((double)x.length() * 1.1D)); buf.append('\''); for(int i = 0; i < stringLength; ++i) { //遍历字符串,获取到每个字符 char c = x.charAt(i); switch(c) { case '\u0000': buf.append('\\'); buf.append('0'); break; case '\n': buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\u001a': buf.append('\\'); buf.append('Z'); break; case '"': if (this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '¥': case '₩': if (this.charsetEncoder != null) { CharBuffer cbuf = CharBuffer.allocate(1); ByteBuffer bbuf = ByteBuffer.allocate(1); cbuf.put(c); cbuf.position(0); this.charsetEncoder.encode(cbuf, bbuf, true); if (bbuf.get(0) == 92) { buf.append('\\'); } } buf.append(c); break; default: buf.append(c); } } buf.append('\''); parameterAsString = buf.toString(); } buf = null; byte[] parameterAsBytes; if (!this.isLoadDataQuery) { if (needsQuoted) { parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor()); } } else { parameterAsBytes = StringUtils.getBytes(parameterAsString); } this.setInternal(parameterIndex, parameterAsBytes); this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12; } } } 

我们执行#{}的查询语句,打断点观察:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

最终传递的参数如下:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

最终传递的参数为:‘aaa\’ or 1=1 —

咱们在数据库中执行如下SQL语句(肯定是查询不到数据的):

欢迎大家来到IT世界,在知识的湖畔探索吧!select * from user where username like 'aaa\' or 1=1 -- ' 
MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

如果把PreparedStatement加的那根”/”去掉呢?我们执行SQL试试:

select * from user where username like 'aaa' or 1=1 -- ' 
MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

我们也可以通过MySQL的日志来观察#{}${}产生的SQL语句来分析问题:

1)开启MySQL日志:

在MySQL配置文件中的[mysqld]下增加如下配置:

欢迎大家来到IT世界,在知识的湖畔探索吧!# 是否开启mysql日志 0:关闭(默认值) 1:开启 general-log=1 # mysql 日志的存放位置 general_log_file="D:/query.log" 
MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

2)重启MySQL服务(要以管理员身份运行):

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

net stop mysql net start mysql 

使用mybatis分别执行如下两条SQL语句:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

查看MySQL日志:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

1.5#{}和${}的应用场景

既然#{}${}好那么多,那为什么还要有${}这个东西存在呢?干脆都用#{}不就万事大吉吗?

其实不是的,${}也有用武之地,我们都知道${}会产生字符串拼接,来生成一个新的字符串

1.5.1 ${}和#{}用法上的区别

例如现在要进行模糊查询,查询user表中姓张的所有员工的信息

sql语句为:select * from user where name like ‘张%’

此时如果传入的参数是 “张”

如果使用${}select * from user where name like ‘${value}%’

生成的sql语句:select * from user where name like ‘张%’

如果使用#{}select * from user where name like #{value}”%”

生成的sql语句:select * from user where name like ‘张'”%”

如果传入的参数是 “张%”

使用#{}select * from user where name like #{value}

生成的sql语句:select * from user where name like ‘张%’

使用${}select * from user where name like ‘${value}’

生成的sql语句:select * from user where name like ‘张%’

通过上面的SQL语句我们能够发现#{}是会加上双引号,而${}匹配的是真实的值。

还有一点就是如果使用${}的话,里面必须要填value,即:${value}#{}则随意

1.5.2 什么情况下用${}?

场景举例:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

代码测试:

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

执行之后,发现执行成功

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

我们可以切换一下,把${}改成#{},会出现SQL语法错误的异常

MyBaits中{}和${}的真正区别,${}的使用场景,{}如何防止注入

1.6 总结

1.6.1 SQL注入问题

MyBatis的#{}之所以能够预防SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个’/’代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。

其次${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。

1.6.2 #{}${}用法总结 1)#{}在使用时,会根据传递进来的值来选择是否加上双引号,因此我们传递参数的时候一般都是直接传递,不用加双引号,${}则不会,我们需要手动加

2)在传递一个参数时,我们说了#{}中可以写任意的值,${}则必须使用value;即:${value}

3)#{}针对SQL注入进行了字符过滤,${}则只是作为普通传值,并没有考虑到这些问题

4)#{}的应用场景是为给SQL语句的where字句传递条件值,${}的应用场景是为了传递一些需要参与SQL语句语法生成的值。

来源:blog.csdn.net/Bb/article/details/

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/122686.html

(0)
上一篇 14小时前
下一篇 13小时前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信