50. SQL--SQL注入简介
1. 前言
SQL 注入是一种代码渗透技术,是最常用的网络黑客技术之一。SQL 注入非常危险,可能会导致数据库中的数据被暴露,甚至被损坏。
通过网页输入框(<input> 标签、<textarea> 标签等)将恶意 SQL 代码提交给服务器是最常见的 SQL 注入方式之一。
当网站要求输入诸如用户名(用户ID)之类的内容时,通常会发生 SQL 注入。黑客会输入一条 SQL 语句,而不是用户名/用户ID,当页面被提交以后,我们将不知不觉地在数据库上运行这条恶意的 SQL 语句。例如,下面的代码会将 userID 参数拼接到 SQL 语句中,从而构建 SELECT 查询,从数据库中获取当前用户的所有信息。
demoUserID = getrequestString("userID");
demoSQL = "SELECT * FROM users WHERE id =" + demoUserID;
2. SQL 注入的危害
SQL 注入会带来很多危害,包括但不限于:
- 骗过登录校验,查看用户登录后的详细信息(例如发布的评论、购买的商品、邮寄地址等),这是 SQL 注入的最简单形式;
- 更新、删除和插入记录,破坏数据库中的数据;
- 在服务器上执行命令,该命令可以下载和安装木马等恶意程序;
- 将有价值的用户数据(例如邮箱、密码、信用卡等)导出到攻击者的远程计算机。
3. SQL 注入示例
现在有一个查看员工信息的页面,该页面允许所有员工通过输入自己的 ID 来查看个人信息。假设员工 ID 在数据表中的字段名为 id,现在有黑客在 <input> 文本框中输入以下内容:
236893238 OR 1=1
它将被拼接成下面的 SQL 语句:
select * from employee where id = 236893238 or 1=1;
这条 SQL 代码是有效的,将从 employee 表中返回所有符合条件的记录。1=1
始终成立,这条 SQL 语句将返回 employee 表中的所有记录,这意味着,所有的员工信息都将被泄露。
类似的,黑客还可以骗过登录校验,使用无效的用户名和密码登录:
select * from employee where (username="" or 1=1) and (password="" or 1=1);
有些数据库支持批处理 SQL 语句,也即一组由分号;
分隔的两条或者多条 SQL 语句。下面给出的 SQL 语句将返回 employee 表的所有行,然后删除 employee_add 表:
select * from employee; drop table employee_add;
4. 防止 SQL 注入
SQL 注入不能杜绝,只能尽力防止,因为即使最优秀的程序员也会犯错。Web 防火墙可以检测和阻止最基本的 SQL 注入攻击,但是它仅仅是一种预防手段,我们还要从自己的代码入手,检测用户输入的内容。永远不要信任用户提供的数据,仅在校验通过后才能将数据提交给数据库。
通常使用模式匹配(Pattern Matching),借助正则表达式来校验用户输入的数据,几乎每种编程语言都提供了模式匹配函数。
下面是一段 PHP 代码,它使用 preg_match() 校验用户输入的数据,限定用户名只能包含汉字、字母、数字、下划线_
和连字符-
:
if (preg_match("/^[\x{4e00}-\x{9fa5}0-9A-Za-z_\-]{2,20}$/u", $_POST['username'], $matches)) {
$result = mysql_query("SELECT * FROM user WHERE name = $matches[0]");
} else {
echo "Tips from c.biancheng.net: User name not accepted!";
}
此外,您还可以结合 mysql_real_escape_string() 函数,它用来转义 SQL 语句中的特殊字符(在特殊字符前面加反斜杠\
),比如'
和"
,请看下面的例子:
// 去除斜杠 if (get_magic_quotes_gpc()) { $name = stripslashes($name); } // 对特殊字符进行转义 $name = mysql_real_escape_string($name); mysql_query("SELECT * FROM user WHERE name='{$name}'");
对于 LIKE 查询,应该使用 addcslashes() 函数对用户输入的%
和_
字符进行转义。addcslashes() 允许用户指定要转义的字符,请看下面的代码:
$sub = addcslashes(mysql_real_escape_string("%str"), "%_"); // 转换以后的 $sub == \%str\_ mysql_query("SELECT * FROM messages WHERE subject LIKE '{$sub}%'");