MySql sql注入问题
什么是SQL注入?
SQL注入是影响企业运营最具有破坏性的漏洞之一。
应用程序向后台数据库进行SQL查询时,如果为攻击者提供了影响该查询的能力,就会引起SQL注入。
示例:
name = "Robert');DROP TABLE students;--"
query = "INSERT INTO students (name) VALUES ('%s')" % (name)
conn.executescript(query)
也就是说,这段包含DROP TABLE关键字的数据项使得原有的简单的插入姓名信息的SQL语句:
INSERT INTO students (name) VALUES ('Robert')
变为了同时包含另外一条清除表单命令的语句:
INSERT INTO students (name) VALUES ('Robert');DROP TABLE students;
防护方法与解决方案
例如:
1使用PreparedStatement,避免直接使用Statement对象
Statement对象可能造成sql注入:
直接使用 JDBC 的场景,如果代码中存在拼接 SQL 语句,那么很有可能会产生注入,如
// concat sql
String sql = "SELECT * FROM users WHERE name ='"+ name + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
安全的写法是使用 参数化查询 ( parameterized queries ),即 SQL 语句中使用参数绑定( ? 占位符 ) 和 PreparedStatement,如
// use ? to bind variables
String sql = "SELECT * FROM users WHERE name= ? ";
PreparedStatement ps = connection.prepareStatement(sql);
// 参数 index 从 1 开始
ps.setString(1, name);
还有一些情况,比如 order by、column name,不能使用参数绑定,此时需要手工过滤,如通常 order by 的字段名是有限的,因此可以
使用白名单的方式来限制参数值
使用了 PreparedStatement 并不意味着不会产生注入,如果在使用 PreparedStatement之前,存在拼接 sql 语句,
那么仍然会导致注入,如
// 拼接 sql
String sql = "SELECT * FROM users WHERE name ='"+ name + "'";
PreparedStatement ps = connection.prepareStatement(sql);
PreparedStatement 是如何防止 SQL 注入的?
正常情况下,用户的输入是作为参数值的,而在 SQL 注入中,用户的输入是作为 SQL 指令的一部分,会被数据库进行编译/解释执行。
当使用了 PreparedStatement,**带占位符 ( ? ) 的 sql 语句只会被编译一次,**之后执行只是将占位符替换为用户输入,并不会再次编译/解
释,因此从根本上防止了 SQL 注入问题。
在 select * from table where 后面拼接一些条件来获取你的信息
Mybatis下注入防范
应尽量避免采用 的 形 式 进 行 参 数 传 递 。 果 无 法 避 免 ( 有 些 S Q L 如 l i k e 、 i n 、 o r d e r b y 等 , 程 序 员 可 能 依 旧 会 选 择 {}的形式进行参数传递。果无法避免(有些SQL如like、in、order by等,程序员可能依旧会选择 的形式进行参数传递。果无法避免(有些SQL如like、in、orderby等,程序员可能依旧会选择{}的方式传
参),那就需要对传入参数自行进行转义过滤。
使用 #{} 语法时,MyBatis 会自动生成 PreparedStatement ,使用参数绑定 ( ?) 的方式来设置值,上述两个例子等价的 JDBC 查询代码如下:
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1, id);
因此 #{} 可以有效防止 SQL 注入,
但有些时候,如 order by 语句,使用 #{} 会导致出错,如
ORDER BY #{sortBy}
sortBy 参数值为 name ,替换后会成为
ORDER BY "name"
即以字符串 “name” 来排序,而非按照 name 字段排序
这种情况就需要使用 ${}
ORDER BY ${sortBy}
使用了 ${}后,使用者需要自行过滤输入,方法有:
代码层使用白名单的方式,限制 sortBy 允许的值,如只能为 name, email 字段,异常情况则设置为默认值 name
在 XML 配置文件中,使用 if 标签来进行判断
Mapper 接口方法
List<User> getUserListSortBy(@Param("sortBy") String sortBy);
<select id="getUserListSortBy" resultType="org.example.User">
SELECT * FROM user
<if test="sortBy == 'name' or sortBy == 'email'">
order by ${sortBy}
</if>
</select>
因为 Mybatis 不支持 else,需要默认值的情况,可以使用 choose(when,otherwise)
<select id="getUserListSortBy" resultType="org.example.User">
SELECT * FROM user
<choose>
<when test="sortBy == 'name' or sortBy == 'email'">
order by ${sortBy}
</when>
<otherwise>
order by name
</otherwise>
</choose>
</select>
JPA注入防范
从SQL注入防范的角度来说,这种将安全责任抛给框架远比依靠程序员自身控制来的保险。因此如果项目使用JPA作为数据访问层,基本
上可以很大程度的消除SQL注入的风险。但是话不能说的太死,在我见过的一个Spring Boot项目中,虽然采用了JPA作为持久框架,但是
有一位老程序员不熟悉于使用JPQL来构建查询接口,依旧使用字符串拼接的方式来实现业务,而为项目安全埋下了隐患。
,程序员只要遵循JPA的开发规范,就无需担心注入问题,框架都为你做好幕后工作了。
sql注入的其他防范方法
-
一切输入都是不安全的:对于接口的调用参数,要进行格式匹配,例如admin的通过name查询的接口,与之匹配的Path应该使用正
则匹配(因为用户名中不应该存在特殊字符),从而确保传入参数是程序控制范围之内的参数,即只接受已知的良好输入值,拒绝不
良输入。注意:验证参数应将它与输出编码技术结合使用。
-
利用分层设计来避免危险:前端尽量静态化,尽量少的暴露可以访问到DAO层的接口到公网环境中,如果现有项目,很难修改存在注
入的代码,可以考虑在web服务之前增加WAF进行流量过滤,当然代码上就不给hacker留有攻击的漏洞才最好的方案。也可以在拥有
nginx的架构下,采用OpenRestry做流量过滤,将一些特殊字符进行转义处理。
-
尽量使用预编译SQL语句:由于动态SQL语句是引发SQL注入的根源。应使用预编译语句来组装SQL查询。
-
规范化:将输入安装规定编码解码后再进行输入参数过滤和输出编码处理;拒绝一切非规范格式的编码。
参考
漫画:什么是SQL 注入?:https://mp.weixin.qq.com/s/R6G89cY4QlqUyW3L7iAE3A
美团一面:如何干掉恶心的 SQL 注入?:https://mp.weixin.qq.com/s/2oEnztxm26bT67I66ayLQw
以Java的视角来聊聊SQL注入:https://mp.weixin.qq.com/s/0_m15WGpTtcxDJ2fg1HtFQ