抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

转载:https://www.cnblogs.com/loveyoulx/p/9526068.html

思考与理解

SQL注入本质上,是程序将用户输入的参数当成命令的一部分进行解析,从而使用户可以自由搜索我们的数据库中的记录。

解决方法,是将用户输入的内容进行解析和转义,将用户输入内容中的恶意代码转为普通字符串,从而保护数据库不被攻击。

SQL 注入攻击

SQL注入攻击,简称SQL攻击或注入攻击,是发生于应用程序之数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了检查,那么这些注入进去的指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。

最常见的就是我们在应用程序中使用字符串联结方式组合 SQL 指令,有心之人就会写一些特殊的符号,恶意篡改原本的 SQL 语法的作用,达到注入攻击的目的。

举个栗子:

比如验证用户登录需要 username 和 password,编写的 SQL 语句如下:

1
String querySql = "select * from user where (name = '"+ username +"') and (pw = '"+ password +"');";

username 和 password 字段被恶意填入

1
username = "1' OR '1'='1";

1
password = "1' OR '1'='1";

将导致原本的 SQL 字符串被填为:

1
select * from user where (name = '1' or '1'='1') and (pw = '1' or '1'='1');

实际上运行的 SQL 语句将变成:

1
select * from user;

也就是不再需要 username 和 password 账密即达到登录的目的,结果不言而喻。

mybatis 解决 SQL 注入问题

我们使用 mybatis 编写 SQL 语句时,难免会使用模糊查询的方法,mybatis 提供了两种方式 #{} 和 ${} 。

  • #{value} 在预处理时,会把参数部分用一个占位符 ? 替代,其中 value 表示接受输入参数的名称。能有效解决 SQL 注入问题
  • ${} 表示使用拼接字符串,将接受到参数的内容不加任何修饰符拼接在 SQL 中,使用${}拼接 sql,将引起 SQL 注入问题。

举个例子:

1、查询数据库 sample 表 user 中的记录,我们故意使用特殊符号,看能否引起 SQL 注入。使用 mybatis 在 mapper.xml 配置文件中编写 SQL 语句,我们先采用拼接字符串形式,看看结果如何:

1
2
3
4
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
<!-- 拼接 MySQL,引起 SQL 注入 -->
SELECT * FROM user WHERE username LIKE '%${value}%'
</select>

注意在配置文件中编写 SQL 语句时,后边不需要加分号。

调用配置文件,编写测试文件,查询数据库内容,采用特殊符号,引起 SQL 注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testFindUserByName() throws Exception{

SqlSession sqlSession=sqlSessionFactory.openSession();

//创建UserMapper代理对象
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);

//调用userMapper的方法
List<User> list=userMapper.findUserByName("' or '1'='1");

sqlSession.close();

System.out.println(list);
}

运行结果如下图所示:

可以看到执行语句其实变为了 select * from user ,将user 表中的全部记录打印出来了。发生了 SQL 注入。

2、如果将配置文件中的 SQL 语句改成 #{} 形式,可避免 SQL 注入。

1
2
3
4
<select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
<!-- 使用 SQL concat 语句,拼接字符串,防止 SQL 注入 -->
SELECT * FROM USER WHERE username LIKE CONCAT('%',#{value},'%' )
</select>

再次运行测试程序,控制台输出如下:

可以看到程序中参数部分用 ? 替代了,很好地解决了 SQL 语句的问题,防止了 SQL 注入。查询结果将为空。

用 ? 代替就能防止 SQL 注入的原理

参考这篇博客 https://www.cnblogs.com/greatfish/p/6067849.html

先看下面用占位符来查询的一句话

1
2
3
4
5
String sql = "select * from administrator where adminname=?";
psm = con.prepareStatement(sql);

String s_name ="zhangsan' or '1'='1";
psm.setString(1, s_name);

假设数据库表中并没有zhangsan这个用户名,

用plsql运行sql语句,可以查出来所有的用户名,但是在Java中并没有查出任何数据,这是为什么呢?

首先,setString()的源码中只有方法名字,并没有任何过程性处理,

那么答案肯定出现在Java到数据库这个过程中,也就是mysql和oracle驱动包中,在mysql驱动包中,PreparedStatement继承并实现了jdk中的setString方法,

也就是原因在于数据库厂商帮你解决了这个问题,下面就看看这个方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public void setString(int parameterIndex, String x) throws SQLException {
// if the passed string is null, then set this column to null
if (x == null) {
setNull(parameterIndex, Types.CHAR);
} else {
StringBuffer buf = new StringBuffer((int) (x.length() * 1.1));
buf.append('\'');

int stringLength = x.length();

//
// Note: buf.append(char) is _faster_ than
// appending in blocks, because the block
// append requires a System.arraycopy()....
// go figure...
//
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);

switch (c) {
case 0: /* Must be escaped for 'mysql' */
buf.append('\\');
buf.append('0');

break;

case '\n': /* Must be escaped for logs */
buf.append('\\');
buf.append('n');

break;

case '\r':
buf.append('\\');
buf.append('r');

break;

case '\\':
buf.append('\\');
buf.append('\\');

break;

case '\'':
buf.append('\\');
buf.append('\'');

break;

case '"': /* Better safe than sorry */
if (this.usingAnsiMode) {
buf.append('\\');
}

buf.append('"');

break;

case '\032': /* This gives problems on Win32 */
buf.append('\\');
buf.append('Z');

break;

default:
buf.append(c);
}
}

buf.append('\'');

String parameterAsString = buf.toString();

byte[] parameterAsBytes = null;

if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(parameterAsString,
this.charConverter, this.charEncoding, this.connection
.getServerCharacterEncoding(), this.connection
.parserKnowsUnicode());
} else {
// Send with platform character encoding
parameterAsBytes = parameterAsString.getBytes();
}

setInternal(parameterIndex, parameterAsBytes);
}
}

所以转义后的sql为’zhangsan' or '1'='1’;这个时候是查不出来的。

评论