3、SQL注入攻击原理:什么是SQL注入?攻击者如何利用它?经典案例演示。
3.1 先说说我理解的SQL注入
SQL注入,说白了就是攻击者把SQL代码塞到用户输入里,骗过数据库执行。
我刚开始做PHP那会儿,觉得这玩意儿离我很远。直到有一次,一个同事的网站被拖库了,几万条用户信息全被扒走。原因?就是一个登录框没做过滤。
嗯,从那以后,我对用户输入就特别敏感。
你想想看,正常的查询是这样的:
SELECT * FROM users WHERE username = 'admin' AND password = '123456';
但如果攻击者在用户名里输入:
admin' OR '1'='1
那SQL就变成了:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = '123456';
注意看,'1'='1'永远为真。所以这条语句会返回所有用户。攻击者不需要密码就能登录。
核心原理就一句话:用户输入的数据被当成了SQL代码执行。
3.2 攻击者是怎么利用的?
我见过最常见的利用方式,大概分这么几步:
- 探测注入点 —— 在输入框、URL参数里加个单引号
',看页面报不报错。 - 判断数据库类型 —— 用
version()、@@version这类函数试探。 - 获取表名和字段 —— 从
information_schema里查。 - 拖数据 —— 直接
SELECT用户表、密码字段。
举个例子,一个新闻详情页的URL可能是:
http://example.com/news.php?id=1
攻击者改成:
http://example.com/news.php?id=1' UNION SELECT 1,2,3,4--
如果页面显示了2、3、4这几个数字,说明有3个字段可以被利用。接下来就能替换成真实数据:
http://example.com/news.php?id=1' UNION SELECT 1,username,password,4 FROM users--
你看,用户名和密码就这么被暴露了。
注意:很多新手以为只要用 addslashes() 就安全了。我曾经也这么想,直到遇到宽字节注入。GBK编码下,一个 %bf%27 就能绕过转义。所以别迷信单个函数。
3.3 经典案例演示:一个登录框的沦陷
假设我们有这么一段代码:
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
echo "登录成功";
} else {
echo "用户名或密码错误";
}
?>
这段代码,我见过太多新手这么写了。问题在哪?
攻击者在用户名输入:
admin' --
密码随便填。SQL变成了:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '随便填'
-- 是SQL的注释符,后面的密码条件被注释掉了。只要 admin 这个用户存在,就能登录。
更狠一点的,攻击者直接输入:
' OR 1=1 --
SQL变成:
SELECT * FROM users WHERE username = '' OR 1=1 -- ' AND password = '随便填'
因为 1=1 永远为真,所以返回所有用户。登录成功。
我的建议:永远不要直接拼接SQL。用预处理语句(Prepared Statements)才是正道。PDO或MySQLi都支持。我个人的习惯是,只要涉及数据库操作,一律用参数绑定。
3.4 还有哪些注入方式?
除了上面说的,我还遇到过这些变种:
| 注入类型 | 说明 | 示例 |
|---|---|---|
| 报错注入 | 利用数据库报错信息获取数据 | id=1 AND extractvalue(1, concat(0x7e, (SELECT password FROM users))) |
| 布尔盲注 | 页面返回真/假,逐字符猜数据 | id=1 AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a' |
| 时间盲注 | 通过页面响应时间判断 | id=1 AND IF(ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>100, SLEEP(5), 0) |
| 堆叠查询 | 一次执行多条SQL | id=1; DROP TABLE users-- |
说实话,盲注最折磨人。我曾经帮一个朋友排查漏洞,对方就是用的时间盲注,一条数据猜了整整一天。但攻击者有的是耐心,你想想看,他们可以写脚本自动化跑。
3.5 怎么防?
嗯,这里我直接给结论:
- 用预处理语句 —— PDO的
prepare()+bindParam(),或者MySQLi的prepare()。这是最根本的解法。 - 输入验证 —— 数字就用
is_numeric()或intval()强转。字符串就限制长度和字符集。 - 最小权限原则 —— 数据库连接只用SELECT、INSERT、UPDATE权限,别给DROP、DELETE。
- 错误信息不要暴露 —— 生产环境关掉
display_errors,自定义错误页面。
一句话总结:SQL注入的本质是数据和代码没有分离。只要把用户输入永远当数据对待,不用它拼SQL,就安全了。
我见过太多因为偷懒直接拼接SQL导致的事故了。说实话,多写两行参数绑定的代码,比事后擦屁股省心得多。