sql注入攻击及其防止办法
一、sequlize.query防止sql注入
在nodejs中使用sequlize库来查询mysql数据库,提供了常用的方法有两种:
// 1、直接查询sql语句
sequelize.query();// 需要做sql防注入
// 2、通过接口
Project.findAll(); //在实现上就做了sql防注入处理,但是必须配合model使用,不灵活
sequelize.query() 原生查询使用replacements 防sql注入
sequelize.query('SELECT * FROM projects WHERE status = ?',
{ replacements: ['active'], type: sequelize.QueryTypes.SELECT }
).then(projects => {
console.log(projects)
})
sequelize的第2种查询方法在实现上做了防注入处理, 但该方法使用并不是很灵活, 对于经常使用原生sql语句的人来说, 总有一种不适应, 感觉手脚被束缚; 而对于使用方法1, 则必须注意防sql注入;
1、产生原因
下面的查询语句, 加入了用户输入的时间项: begin
, end
, 正常情况是:
begin = 20161101;
SELECT .. FROM tbl WHERE pdate>=20161101 AND ...;
而如果有人故意输入如下的参数, 则就会出现sql注入:
begin = '20161101 AND 1=1; -- hack';
SELECT .. FROM tbl WHERE pdate>=20161101 AND 1=1; -- hack ...;
2、解决办法
解决上面的办法, 通常有两种:
(1)对输入的参数进行转义, 因为sql注入通常需要'
符号来闭合前面的'
, 这样如果位面在输入的参数中的'
全部转义为\'
, 则查询时就不会出现中断(执行两条sql语句), 参看下面的例子;
(2)设置mysql只能一次执行一条sql语句;
3、测试
sequelize.query()
与Project.findAll()
对比,先看代码
var tblName = 'tbl_test', begin = '20160923 AND 1=1;-- hack', end = 20160928;
var sqlQuery = `SELECT * FROM ${tblName} WHERE pdate>=${begin} AND pdate<=${end} GROUP BY pdate`;
Promise.resolve([
// 第一条查询
sequelize.query(sqlQuery),
// 第一二条查询
Project.findAll({
attributes: { exclude:['id'] },
where: {
pdate: {
$and:{
// 参数中加上了'来闭合前面的', 后面的1=1为注入语句
$gte: "20160923' AND 1=1;-- hack",
$lte: end
}
}
}
})
]).spread(function(sql1, sql2){
})
(1)上面第一个为sequelize.query()
执行, 最终执行的查询语句为:
select * from tbl_test where padte>=20160923 and 1=1;-- hack and pdate<=....;
结果出现注入, pdate<=..
这一块的条件没有被执行;
(2)上面第二个查询为Project.findAll()
执行, 最终执行的语句为:
select * from tbl_test where pdate>'20160923\' AND 1=1;-- hack' AND pdate<='...'.....;
显然,这里参数里的20160923'
引号没有起作用, 被转换为了`20160923\”,所以有效避免了注入, 制查询了符合要求的数据或空数据;
4、sequelize.query()
+ 参数绑定
下面通过sequelize.query()
接口提供的参数绑定和查询方法指定来防止sql注入的发生。
- 下面的查询语句解析为:
select * from tbl_test where padte>='20160923 and 1=1;-- hack' and pdate<=....;
字符串被直接替换,没有被注入;- 注意,这里的表名table不能通过bind的参数传进去, 因为这样在语句里会表达为字符串: …. from ‘tbl_test’ where …, 这样是错误的语句,所以上面用字符串模板传入,也可以硬编码进去;
var tblName = 'tbl_test', begin = '20160923 AND 1=1;-- hack', end = 20160928;
var sqlQuery = `SELECT * FROM ${tblName} WHERE pdate>=$begin AND pdate<=$end GROUP BY pdate`;
Promise.resolve([
sequelize.query(sqlQuery,
type: ymModel.sequelize.QueryTypes.SELECT, // 指定sql为SELECT
bind: {
begin: begin,
end: end
}
),
]).spread(function(sql1){
})
二、浅析sql攻击
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过SQL语句,实现无帐号登录,甚至篡改数据库。
1、适用范围:
(1)如果一个系统是通过
SELECT * FROM accounts WHERE username='admin' and password = 'password'
这种显式的SQL来进行登陆校验,也就是执行这个SQL语句,如果数据库中存在用户名为admin, password为password的用户,就登陆成功,否则就登陆失败。
(2)系统没有对用户输入进行全面的过滤
(3)系统后台使用的是MYSQL数据库
2、攻击原理:
利用MYSQL的注释功能,也就是"/*",mysql执行SQL脚本时,如果遇到 /* 标示符,就会把之以后的SQL当做注释而不会执行,
正常情况下用户在用户名框内输入"admin",在password框内输入"password",后台执行的SQL语句就为:SELECT * FROM accounts WHERE username='admin' and password = 'password';
但是如果在用户名框内输入"admin' AND 1=1 /*", 在密码框内输入任意字符串,那么后台执行的SQL就为:SELECT * FROM accounts WHERE username='admin' AND 1=1 /* and password = 'aa',
可以看到数据库实际执行的SQL为:SELECT * FROM accounts WHERE username='admin' AND 1=1, 而 /* 后面的SQL就被当做注释而忽略掉了,登陆成功!
这就是sql注入攻击。
3、sql注入攻击的总体思路
(1)寻找到SQL注入的位置
(2)判断服务器类型和后台数据库类型
(3)针对不同的服务器和数据库特点进行SQL注入攻击
4、sql注入攻击实例
比如在一个登录界面,要求输入用户名和密码:
可以这样输入实现免帐号登录:
用户名: ‘ or 1 = 1 –
密 码:
点登陆,如若没有做特殊处理,那么这个非法用户就很得意的登陆进去了。(当然现在的有些语言的数据库API已经处理了这些问题)
这是为什么呢? 下面我们分析一下:
从理论上说,后台认证程序中会有如下的SQL语句:
String sql = "select * from user_table where username='" + userName + "' and password='" + password + "'";
当输入了上面的用户名和密码,上面的SQL语句变成:
SELECT * FROM user_table WHERE username=
'’or 1 = 1 -- and password='’
分析SQL语句:
条件后面 username='' or 1=1 用户名等于 '' 或 1=1 那么这个条件一定会成功;
然后后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都能正确执行,用户轻易骗过系统,获取合法身份。这还是比较温柔的,如果是执行
SELECT * FROM user_table WHERE
username='' ;DROP DATABASE (DB Name) --' and password=''
其后果可想而知。
5、应对方法
(1)采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
一般ORM框架都有提供,直接调用其内部方法即可。sql注入只对sql语句的准备(编译)过程有破坏作用,而比如PreparedStatement已经准备好了,执行阶段只是把输入串作为数据处理,而不再对sql语句进行解析准备,因此也就避免了sql注入问题。
(2)使用正则表达式过滤传入的参数或者比如具体的字符串过滤等
(3)进行参数转码解码等
总的说来,防范一般的SQL注入只要在代码规范上下点功夫就可以了。凡涉及到执行的SQL中有变量时,可以使用数据持久层提供的内置方法即可,切记不要用拼接字符串的方法;或者对所传参数进行过滤或转换处理。