目录
1. 原理
SQL注入漏洞是指由于应用程序未能正确过滤用户提交的数据,在用户提交包含SQL语句的内容时,会被应用程序接收并在数据库上执行,从而达到非法操作数据库的目的。这种攻击手段被称为SQL注入攻击。
这种漏洞主要发生在应用程序与数据库交互的过程中,比如登录验证、数据查询等功能。攻击者通过在用户输入框内输入非法的SQL语句,使得系统误将这些语句作为操作数据库的命令执行,从而获取、修改或者删除数据。
防止SQL注入漏洞的方法包括使用参数化查询、使用预编译的SQL语句、限制SQL查询的权限等。同时,对用户输入的数据进行严格的检查和过滤也是非常重要的防护措施。
SQL注入是一种代码注入技术,攻击者利用应用程序的安全漏洞,将恶意的SQL代码插入到原始SQL查询中,从而在目标数据库中执行非授权的命令。
以下是SQL注入的基本原理:
1. 应用程序将用户输入的数据直接合并到SQL查询中,而没有进行适当的校验或转义。
2. 攻击者尝试通过特殊字符(例如单引号)或特定的SQL关键字,修改原始SQL查询的结构,从而在执行时达到非预期的效果。
3. 攻击者可以利用这种技术,执行各种数据库操作,例如数据泄漏、数据篡改甚至数据库管理权限的获取等。
考虑以下SQL查询:
String query = "SELECT * FROM users WHERE username =
'" + username + "' AND password = '" + password + "'";
如果攻击者输入“admin' --”作为用户名,原始的SQL查询将变为:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
在SQL中,"--"表示注释,所以在执行上面的查询时,密码检查将被忽略,攻击者因此可以以管理员身份登录。
为了预防SQL注入,应始终使用参数化查询或预编译的查询,并避免直接将用户输入合并到SQL查询中。
2. SQL注入常见构造子句
以下是一些常见的SQL注入的例子。请注意,这些只是为了理解SQL注入的方式,并非攻击行为的推广或煽动。请不要尝试在任何非授权的环境中使用这些技术。
1. ' OR '1'='1
2. '; DROP TABLE users; --
3. ' OR 'a'='a
4. '; SELECT * FROM users; --
5. ' AND email IS NULL; --
6. ' OR 'x'='x'; --
7. " OR ""="
8. ' OR username='admin'
9. ' UNION SELECT NULL, NULL, NULL; --
10. ' UNION SELECT * FROM users; --
11. ' UNION ALL SELECT * FROM users WHERE 'a' = 'a
12. ' AND 1=0 UNION ALL SELECT null, null, null, CONCAT(USERNAME, ':', PASSWORD) FROM USERS; --
13. ' OR 'text' = N'text'; --
14. ' OR 'something' IN ('something'); --
15. ' OR 'text' LIKE 'text'; --
16. " OR ""=""; DROP TABLE users; --
17. ' OR '1'='1'--
18. ' OR '1'='1'/*
19. '' OR '1'='1';
20. '' OR '1'='1'; DROP TABLE users; --
在攻击者输入的数据中,有些包含了SQL的控制字符如',",;和--,这些字符的存在可以使得攻击者成功地修改原有的SQL语句,达到注入的目的。比如' OR '1'='1可以使得原有的查询语句总是返回真,'; DROP TABLE users; --则可以删除整个users表。
为了防止SQL注入一般系统进行拦截,还有一些相对比较隐蔽的写法如下
1. 利用与操作符--和#来注释掉后面的SQL语句: ' OR 'x'='x' -- ' OR 'x'='x' #
2. 利用UNION操作符来从其他表中获取信息: ' UNION SELECT * FROM users WHERE 'a' = 'a
3. 使用16进制值: ' OR 0x50=0x50
4. 使用字符编码: ' OR 'a'='a
5. 利用LIKE操作符来模糊匹配: ' OR 'x' LIKE '%
6. 使用SQL中的空格代替: ' OR%20'x'='x
7. 利用SQL函数: ' OR ASCII('a')=97
8. 基于时间的SQL注入: 1' WAITFOR DELAY '0:0:10'-- 1' AND BENCHMARK(5000000,ENCODE('MSG','by 5 seconds'))--
9. 使用双重编码: %253Cscript%253Ealert(1)%253C/script%253E
10. 利用数据库的特殊功能(比如Oracle中的Dual表,SQL Server的xp_cmdshell等)
3.Mybatis 常见错误写法导致的SQL注入
虽然MyBatis帮助我们处理了许多SQL操作,但是如果使用不当,也可能会导致SQL注入的风险。这里有一些常见的MyBatis SQL注入的漏洞:
1. 动态SQL:在MyBatis中,你可以编写动态SQL,如 `<if>`、`<choose>`、`<when>`、`<foreach>`等,但是如果在这些动态SQL中携带的参数没有进行合适的转义或者检查,就可能导致SQL注入。
2. `${}`和`#{}`的不当使用:在MyBatis中,`${}`和`#{}`都可以用来传递参数,但是两者的处理方式不同。`#{}`会将传入的数据看作一个参数,MyBatis会对其进行预处理,从而避免SQL注入;而`${}`则会直接将传入的数据拼接到SQL语句中,如果传入的数据中包含恶意代码,那么就可能导致SQL注入。
3. 使用了MyBatis的`@SelectProvider`、`@UpdateProvider`、`@InsertProvider`和`@DeleteProvider`注解来生成SQL语句,但是在生成SQL语句的方法中没有对参数进行检查和转义。
4. 在`<script>`标签中拼接SQL,若未对参数进行校验或转义,也可能导致SQL注入。
预防措施:
1. 参数处理方式上,尽量使用`#{}`方式传递参数,而避免使用`${}`。
2. 在动态SQL中,要对参数进行必要的检查和转义。
4. 严格控制和过滤用户的输入,对于不符合预期的输入要进行拦截。
例如,有一个动态的 SQL 语句,它基于用户的搜索查询来生成:
<select id="findBooksByTitle" resultType="Book">
SELECT * FROM books
<if test="title != null">
WHERE title = ${title}
</if>
</select>
在这个例子中,`title` 是一个从用户那里获取的参数。如果用户输入的是 `A Tale of Two Cities' OR '1'='1`,那么生成的 SQL 会变成:
SELECT * FROM books
WHERE title = 'A Tale of Two Cities' OR '1'='1'
这个 SQL 语句的意思是选择所有的书,因为 `'1'='1'` 总是为真,所以这就绕过了原本只选择指定 `title` 的限制。这就是一个 SQL 注入的例子。
为了避免这种情况,应该使用 `#{}` 来替换 `${}`,如下所示:
<select id="findBooksByTitle" resultType="Book">
SELECT * FROM books
<if test="title != null">
WHERE title = #{title}
</if>
</select>
这样,MyBatis 就会对 `title` 参数进行预处理,并使用参数化的查询,防止了 SQL 注入攻击。
in`或者like些错误的示例:
错误的`in`操作 `${ids}`直接将参数进行字符串替换,如果攻击者将`ids`设置为`1 or 1=1`,则会导致所有的用户数据被查询出来。
<select id="findByIds" parameterType="List" resultType="User">
SELECT * FROM user WHERE id in (${ids})
</select>
正确的做法是使用`<foreach>`标签,如下:
<select id="findByIds" parameterType="List" resultType="User">
SELECT * FROM user WHERE id in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
错误的`like`操作:
<select id="findByName" parameterType="String" resultType="User">
SELECT * FROM user WHERE name like '%${name}%'
</select>
在上面的语句中,`${name}`直接将参数进行字符串替换,如果攻击者将`name`设置为`%' or '1'='1`,则会导致所有的用户数据被查询出来。
正确的做法是对用户输入进行转义,如下:
<select id="findByName" parameterType="String" resultType="User">
SELECT * FROM user WHERE name like CONCAT('%', #{name}, '%')
</select>
在上面的语句中,`#{name}`将参数作为预编译的语句处理,可以防止SQL注入。
Spring JdbcTemplate 的这种写法也是同样会导致SQL注入
3. 使用JPA和Hibernate时
当使用JPA和Hibernate时,如果不正确处理查询参数,就有可能导致SQL注入漏洞。以下是一些存在问题的示例:
1. 直接拼接用户输入的值:
public boolean validateUser(String username, String password) {
String query = "SELECT COUNT(u) FROM User u WHERE u.username = '" + username + "' AND u.password = '" + password + "'";
// ...
}
用户名和密码直接被拼接到查询语句中,没有任何验证和处理。这种实现容易受到SQL注入攻击,攻击者可以通过输入恶意的值来改变查询的语义。
2. 使用字符串拼接代替参数化查询:
public boolean validateUser(String username, String password) {
String query = "SELECT COUNT(u) FROM User u WHERE u.username = '" + username + "' AND u.password = '" + password + "'";
Query jpaQuery = entityManager.createQuery(query);
// ...
}
在这个示例中,虽然使用了JPA查询,但是仍然使用字符串拼接来构建查询语句。这种做法与直接拼接用户输入的值没有本质区别,同样容易受到SQL注入攻击。
3. 不使用参数化查询:
public boolean validateUser(String username, String password) {
String query = "SELECT COUNT(u) FROM User u WHERE u.username = :username AND u.password = :password";
Query jpaQuery = entityManager.createQuery(query);
jpaQuery.setParameter("username", username);
jpaQuery.setParameter("password", password);
// ...
}
在这个示例中,虽然使用了查询参数,但是没有使用命名参数。相反,直接将参数值传递给`setParameter`方法。这种做法仍然存在SQL注入的风险,因为参数值没有经过正确的转义和预编译。
要避免这些问题,应该始终使用参数化查询,并正确地使用命名参数来代替直接拼接用户输入的值。这样可以确保查询参数的安全性,并有效地防止SQL注入攻击。