当前位置:   article > 正文

彻底搞懂MyBatis中${}和#{}_springboot ${} sql注入

springboot ${} sql注入

目录

一、搭建模拟场景

二、SQL注入问题

三、#{}和${}的区别

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

五、那么问题来了:什么时候用#{},什么时候用${}呢?

一、搭建模拟场景

数据库数据

DAO接口

Mapper.xml

执行测试代码

  1. package com.luck.bookstore.ware;
  2. import ...
  3. @RunWith(SpringRunner.class)
  4. @SpringBootTest
  5. public class BookstoreWareApplicationTests {
  6. @Autowired(required = false)
  7. WareInfoDao wareInfoDao;
  8. @Test
  9. public void test() {
  10. List<WareInfoEntity> list1 = wareInfoDao.findByName1("李%");
  11. List<WareInfoEntity> list2 = wareInfoDao.findByName2("'李%'");
  12. System.out.println("使用#{}");
  13. for (WareInfoEntity entity : list1) {
  14. System.out.println(entity);
  15. }
  16. System.out.println("使用${}");
  17. for (WareInfoEntity entity : list2) {
  18. System.out.println(entity);
  19. }
  20. }
  21. }

执行结果(符合预期)

二、 SQL注入问题

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

做个测试

  1. List<WareInfoEntity> list2 = wareInfoDao.findByName2("'badBoy' or 1=1");
  2. System.out.println("使用${}");
  3. for (WareInfoEntity entity : list2) {
  4. System.out.println(entity);
  5. }

执行结果

我们传递的参数是"'badBoy' or 1=1",导致查询出来了全部的数据。

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

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

 

其实数据库执行的sql语句: SELECT * FROM wms_ware_info WHERE NAME LIKE  '\'badBoy\' or 1=1' 具体原因往下看第四大点

三、#{}和${}的区别

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

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

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

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

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

 setString()方法全部源码:

  1. @Override
  2. public void setString(int parameterIndex, String x) {
  3. if (x == null) {
  4. setNull(parameterIndex);
  5. } else {
  6. int stringLength = x.length();
  7. if (this.session.getServerSession().isNoBackslashEscapesSet()) {
  8. // Scan for any nasty chars
  9. boolean needsHexEscape = isEscapeNeededForString(x, stringLength);
  10. if (!needsHexEscape) {
  11. StringBuilder quotedString = new StringBuilder(x.length() + 2);
  12. quotedString.append('\'');
  13. quotedString.append(x);
  14. quotedString.append('\'');
  15. byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(quotedString.toString())
  16. : StringUtils.getBytes(quotedString.toString(), this.charEncoding);
  17. setValue(parameterIndex, parameterAsBytes, MysqlType.VARCHAR);
  18. } else {
  19. byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(x) : StringUtils.getBytes(x, this.charEncoding);
  20. setBytes(parameterIndex, parameterAsBytes);
  21. }
  22. return;
  23. }
  24. String parameterAsString = x;
  25. boolean needsQuoted = true;
  26. if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
  27. needsQuoted = false; // saves an allocation later
  28. StringBuilder buf = new StringBuilder((int) (x.length() * 1.1));
  29. buf.append('\'');
  30. //
  31. // Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure...
  32. //
  33. for (int i = 0; i < stringLength; ++i) {
  34. char c = x.charAt(i);
  35. switch (c) {
  36. case 0: /* Must be escaped for 'mysql' */
  37. buf.append('\\');
  38. buf.append('0');
  39. break;
  40. case '\n': /* Must be escaped for logs */
  41. buf.append('\\');
  42. buf.append('n');
  43. break;
  44. case '\r':
  45. buf.append('\\');
  46. buf.append('r');
  47. break;
  48. case '\\':
  49. buf.append('\\');
  50. buf.append('\\');
  51. break;
  52. case '\'':
  53. buf.append('\\');
  54. buf.append('\'');
  55. break;
  56. case '"': /* Better safe than sorry */
  57. if (this.session.getServerSession().useAnsiQuotedIdentifiers()) {
  58. buf.append('\\');
  59. }
  60. buf.append('"');
  61. break;
  62. case '\032': /* This gives problems on Win32 */
  63. buf.append('\\');
  64. buf.append('Z');
  65. break;
  66. case '\u00a5':
  67. case '\u20a9':
  68. // escape characters interpreted as backslash by mysql
  69. if (this.charsetEncoder != null) {
  70. CharBuffer cbuf = CharBuffer.allocate(1);
  71. ByteBuffer bbuf = ByteBuffer.allocate(1);
  72. cbuf.put(c);
  73. cbuf.position(0);
  74. this.charsetEncoder.encode(cbuf, bbuf, true);
  75. if (bbuf.get(0) == '\\') {
  76. buf.append('\\');
  77. }
  78. }
  79. buf.append(c);
  80. break;
  81. default:
  82. buf.append(c);
  83. }
  84. }
  85. buf.append('\'');
  86. parameterAsString = buf.toString();
  87. }
  88. byte[] parameterAsBytes = this.isLoadDataQuery ? StringUtils.getBytes(parameterAsString)
  89. : (needsQuoted ? StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charEncoding)
  90. : StringUtils.getBytes(parameterAsString, this.charEncoding));
  91. setValue(parameterIndex, parameterAsBytes, MysqlType.VARCHAR);
  92. }
  93. }

 最终传递的参数如下

咱们在数据库执行肯定是查不到的

 估计到这大家都明白了

五、那么问题来了:什么时候用#{},什么时候用${}呢?

sql语句只需要写入参数的时候强烈建议使用#{},

其余(列入需要写入表)可使用${}

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/612105
推荐阅读
相关标签
  

闽ICP备14008679号