0、导读
在MySQL里,如何识别并且避免发生SQL注入风险
1、关于SQL注入
互联网很危险,信息及数据安全很重要,SQL注入是最常见的入侵手段之一,其技术门槛低、成本低、收益大,颇受各层次的黑客们所青睐。
一般来说,SQL注入的手法是利用各种机会将恶意SQL代码添加到程序参数中,并最终被服务器端执行,造成不良后果。
例如,我们访问接口 http://imysql.com/user.php?userid=123 来根据userid获取用户信息,假设程序中是这么处理的:
$sql = “SELECT * FROM user WHERE userid = $_GET[userid]“;
上面这段代码看起来既low有很xx对吧,尤其是在双引号里面还可以直接引用数据类型变量,所以说php是世界上最好的语言一点不为过,哈哈(其实我早期也写过几年php的)。
这时候,如果我们传递进去的参数改成这样:http://imysql.com/user.php?userid=123 or 1=1,这就会导致SQL条件永远成立,所有的数据都会被读取出来。又或者可以传递这样的参数:http://imysql.com/user.php?userid=123 or if(now()=sysdate(),sleep(5),1),这时候不但所有的数据都会被读取到,也会让这个SQL执行完毕后再等待5秒才能返回,黑客可据此来判断这个SQL注入探测是否成功。
在上面这个例子中,其实我们只需要对用户输入的参数进行简单的类型判断和控制,即可快速避免被注入的风险,例如改成下面这样就可以了:
$userid = intval(strim($_GET[‘userid’]));
$sql = “SELECT * FROM user WHERE userid = “ . mysql_real_escape_string($userid);
可见,至少基础的SQL注入并不难防范,只要在各个层面都做足工作就可以。而简单的SQL盲注(就是乱拳打死老师傅的玩法)已经可以采用sqlmap之类的辅助工具来做了,完全不需要人工执行。
2、如何防范
上面提到过sqlmap,它既可以作为SQL盲注的工具,也可以在新项目上线前内部扫一次,提前发现潜在漏洞,及时修补,反过来为我们所用。其他可以检测sql注入漏洞的知名扫描工具有:SQLIer、SQLID、SQL Power Injector、SQLNinja。
我们也可以自己通过频繁扫描当前执行的SQL列表,根据一些关键字来判断是否发生了SQL注入或潜在风险,常见的关键字有:
SLEEP() — 一般的SQL盲注都会伴随SLEEP()函数出现,而且一般至少SLEEP 5秒以上
MID()
CHAR()
ORD()
SYSDATE()
SUBSTRING()
DATABASES()
SCHEMA()
USER()
VERSION()
CURRENT_USER()
LOAD_FILE()
OUTFILE/DUMPFILE
INFORMATION_SCHEMA
TABLE_NAME
fwrite()/fopen()/file_get_contents() — 这几个是PHP文件操作函数
我们可以以较高频率检查当前的活跃SQL命令,一旦发现上述关键字,可以立即记录下来并触发告警,通知管理员及时人工确认处理,甚至也可以先直接自动杀掉这些SQL查询(可以用 pt-kill 工具来做到这点,也可以自行开发),以防万一,少给黑客留机会。
还有,我们建议把选项 safe-update/sql_safe_updates 设置为 1,防止没有任何 WHERE 条件的误操作更新,将全表数据都写错。
3、其他建议
防范SQL注入只是数据安全保护工作很小的一部分,只要做好基本功就可以防住至少80%以上的SQL注入探测。
在app server层,以PHP开发语言为例,除了上面提到的规范用户输入类型外,还可以改成用 sprintf() 函数来格式化构造 SQL 语句,也可以一定程度防范SQL注入。还可以修改 php cgi 程序的运行属主为普通用户,最起码不能使用 root 用户,避免因为代码层不严谨导致被黑客上传可执行 php 程序代码文件。还可以把php中的远程文件调用权限关闭,把选项 allow_url_fopen、allow_url_include 均设置为 off,并限定php可以打开的文件目录,不允许跨区域访问敏感文件。
除了在代码层面做好数据类型判断、用户输入判断外,还可以在web server层加上过滤策略,比如在nginx上启用WAF插件。或者,也可以购买IDC运营商、云主机提供商提供的商业解决方案。对于重视数据安全的企业来说,花点钱保平安更为重要。
4、附录
下面是一些常见SQL注入参考案例:
案例1:SELECT * FROM t WHERE a LIKE ‘%xxx%’ OR (IF(NOW=SYSDATE(), SLEEP(5), 1)) OR b LIKE ‘1=1‘;
案例2:SELECT * FROM t WHERE a > 0 AND b IN(497 AND (SELECT * FROM (SELECT(SLEEP(20)))a));
案例3:SELECT * FROM t WHERE a=1 and b in (1234,(SELECT (CASE WHEN (5=5) THEN SLEEP(5) ELSE 5*(SELECT 5 FROM INFORMATION_SCHEMA.CHARACTER_SETS) END)));
Pingback: 【转载】ourmysql | LittleBill
Pingback: 【转载】大牛文章 | LittleBill
这篇文章感觉没有说到重点。sql注入的最重要原因是前端的参数被作为sql关键字了,所以防止sql注入的最重要的方法是预编译,也就是sql语句经过语法解析已经生产了语法树,执行计划之后,再插入前端的参数,这个时候,因为编译已经完成,那么前端的参数就不会作为sql关键字进行处理,仅仅只会作为字面量,作为参数进行处理了。比如:http://imysql.com/user.php?userid=123 or 1=1这个例子,如果预编译了,那么后面的参数 ’123 or 1=1′ 中的 ‘or‘ 不会作为sql关键字进行处理,所以也就不会产生sql注入了。
文章只强调了参数的过滤,其实采用PDO 的PERPARE方法不是更好,PHP5已经不推荐使用mysql系列函数了
Pingback: 【转载】大牛文章 – Little bill