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 攻击者是怎么利用的?

我见过最常见的利用方式,大概分这么几步:

  1. 探测注入点 —— 在输入框、URL参数里加个单引号 ',看页面报不报错。
  2. 判断数据库类型 —— 用 version()@@version 这类函数试探。
  3. 获取表名和字段 —— 从 information_schema 里查。
  4. 拖数据 —— 直接 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导致的事故了。说实话,多写两行参数绑定的代码,比事后擦屁股省心得多。