🌟 什么是 SQL 注入?
SQL 注入(SQL Injection)是指攻击者利用特殊输入,让数据库执行它本来不应该执行的代码,从而获取或篡改数据。
就像在考试的时候偷偷改题目,让老师改成你想要的内容! 😱
🌟 先来看一个正常的 SQL 查询
假设你的网站有一个登录功能,代码是这样的:
🔹 登录代码
$username = $_GET['username'];
$password = $_GET['password'];$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
✅ 这个代码的意思是:
👉 从 users
表里查找 用户名 = $username
且 密码 = $password
的用户。
如果数据库里有这条数据,用户就可以登录!
🌟 用户输入正常数据(正确方式)
假设用户输入:
username = admin
password = 123456
最终SQL语句:
SELECT * FROM users WHERE username = 'admin' AND password = '123456';
✅ 如果 admin
这个用户的密码是 123456
,那么数据库会返回这个用户的信息,表示登录成功!
🚨 但如果攻击者输入特殊字符呢?
攻击者可能会这样输入:
username = admin' --
password = (随便填)
那么最终SQL语句变成:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'xxxxx';
这里发生了什么?
-
--
是SQL中的注释符号,它会让后面代码(AND password='xxxxx'
)变成无效的注释。 -
结果就变成:
SELECT * FROM users WHERE username = 'admin'
相当于直接查询
admin
用户,而不检查密码! 😱 -
这样攻击者就可以绕过密码验证,直接登录
admin
账号!✅ -
🌟 现实世界的比喻
-
💡 想象你在银行办理取款业务,柜员要核对你的身份:
-
正常情况:
柜员问你:请告诉我你的用户名和密码,如果正确,我就给你取钱。-
你说:“我是
admin
,密码是123456
” -
银行查了一下,发现你的用户名和密码匹配,于是让你取钱。✅
-
-
被攻击的情况(SQL注入):
攻击者说:“我是admin
,但是密码这部分不重要,你不用检查。”-
银行糊涂了,以为你就是
admin
,直接给你取钱! -
🌟 关键点总结
-
1️⃣ SQL 注入的本质:利用特殊字符破坏 SQL 语句结构,让数据库执行本不应该执行的代码。
2️⃣ 常见注入方式: -
单引号
'
(破坏 SQL 结构) -
--
(注释掉后续代码) -
OR 1=1
(让查询条件永远成立) -
3️⃣ 为什么 SQL 注入危险?
-
可以绕过密码验证,直接登录
-
可以获取数据库里的所有数据(用户信息、密码、银行卡号)
-
甚至可以删除数据,破坏整个系统! 😱
-
🌟 2. 什么是预编译(Prepared Statement)?
🔹 预编译的作用
预编译(Prepared Statement)的核心思想是: 👉 把 SQL 语句和用户输入的参数分开处理,防止用户输入的数据被当成 SQL 代码执行。
就像去餐馆点菜时,菜谱是固定的,顾客只能填菜名,不能改动菜单的内容,这样就不会有欺骗行为。
🔹 普通的 SQL 语句(容易被 SQL 注入攻击)
🚨 下面是一个 危险的 SQL 代码
-
$name = $_GET['name'];
$query = "SELECT * FROM users WHERE name = '$name'";
$result = $pdo->query($query);👆 这里的问题是:
-
$name
直接拼接到 SQL 语句中,如果用户输入了恶意代码,就会被数据库当成 SQL 代码执行,导致 SQL 注入! - ✅ 使用预编译(防止 SQL 注入)
$stmt = $pdo->prepare('SELECT * FROM users WHERE name = ?');
$stmt->execute([$name]);execute([$name]);
个代码是安全的! 为什么?
prepare('SELECT * FROM users WHERE name = ?') 这一步先定义好 SQL 语句,但不插入具体的值。
execute([$name]) 这一步才把 $name 作为普通字符串插入,不会被当成 SQL 代码。
💡 无论用户输入什么,它都会被当成普通文本,不会影响 SQL 语句本身。
-
🌟 3. 为什么预编译不能 100% 防 SQL 注入?
虽然预编译能防止大部分 SQL 注入,但在某些特殊情况下,仍然可能被绕过。
下面是三个真实案例,让你彻底理解!😱 -
🚨 案例一:宽字节注入
🌍 发生场景
某些数据库(比如 MySQL)在处理GBK 编码时,可能会错误地解析特殊字符,从而导致 SQL 注入。💻 代码示例
-
$pdo->query('SET NAMES gbk'); // 设定 GBK 编码
$var = "\xbf\x27 OR 1=1 /*"; // 特殊构造的字符串
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute([$var]);❓ 为什么会有问题?
1️⃣
\xbf\x27
在 GBK 编码下,可能会被误解析为一个单独的字符。
2️⃣ 剩下的部分 "OR 1=1 /*" 会被数据库当成 SQL 代码执行!
3️⃣ 这样就变成: -
SELECT * FROM test WHERE name = '某些字符' OR 1=1 /*
OR 1=1
永远为真,所以查询会返回所有数据,导致信息泄露! 😱 -
🚨 案例二:表名注入
🌍 发生场景
预编译只能防止SQL参数注入,但如果动态拼接SQL,还是会有风险!😨💻 代码示例
-
$dbh = new PDO("mysql:host=localhost;dbname=test", "root", "password");
$name = $_GET['name']; // 例如输入:"users; DROP TABLE users;"
$stmt = $dbh->prepare('SELECT * FROM ' . $name . ' WHERE username = :username');
$stmt->execute([':username' => $_REQUEST['username']]);❓ 为什么会有问题?
1️⃣
$name
直接拼接进 SQL 语句,而不是作为参数传入prepare()
。
2️⃣ 攻击者可以输入恶意表名,比如: -
users; DROP TABLE users;
3️⃣ 这样 SQL 语句变成:
-
SELECT * FROM users; DROP TABLE users; WHERE username = 'admin'
🔴 结果:
users
表被删除!数据丢失! 😱 -
案例三:ORDER BY 注入
🌍 发生场景
有些 SQL 语句的排序字段不能使用预编译参数,这会导致 SQL 注入。💻 代码示例
-
$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');
$stmt->execute([':userSuppliedData' => $_GET['sort']]);为什么会有问题?
1️⃣
ORDER BY
后面的字段名 不能使用预编译参数。
2️⃣ 攻击者可以输入: -
id DESC; DROP TABLE foo;
3️⃣ 这样 SQL 语句变成:
-
SELECT * FROM foo ORDER BY id DESC; DROP TABLE foo;
4️⃣ 🔴 结果:
foo
表被删除!数据库数据被破坏! 😱🌟 4. 如何完全防止 SQL 注入?
虽然预编译是一个很好的安全措施,但它并不能 100% 防止 SQL 注入。
所以,我们还需要额外的安全措施!👇
✅ 方法 1:严格限制输入格式
💡 不要让用户输入影响 SQL 结构的内容!
🔹 例如,限制表名、列名、排序字段只能从“白名单”中选择! -
$allowedSortFields = ['id', 'name', 'date'];
if (!in_array($_GET['sort'], $allowedSortFields)) {
die("Invalid input"); // 直接终止,防止 SQL 注入
}✅ 方法 2:使用 ORM 框架
🔹 ORM(Object-Relational Mapping) 框架(比如 Laravel 的 Eloquent、Doctrine)会自动处理 SQL 语句,默认带有安全检查。
示例:
-
User::where('name', $name)->first();
ORM 会自动处理 SQL 注入问题,不需要手动写 SQL 语句。
✅ 方法 3:最小权限原则
🔹 不要使用数据库管理员账户(如
root
)运行 SQL 语句! 🔹 为应用创建权限受限的数据库账户,这样即使有 SQL 注入,也不会破坏整个数据库。CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'securepassword';
GRANT SELECT, INSERT, UPDATE, DELETE ON testdb.* TO 'webapp'@'localhost';这样,即使黑客注入了
DROP TABLE users;
,也无法执行,因为webapp
账号没有删除表的权限!😃
🌟 总结
✅ 预编译是 SQL 防注入的核心方法,但并不 100% 安全。
✅ SQL 注入绕过的三种方式: -
宽字节注入(特殊字符编码问题)
-
表名注入(拼接动态 SQL )
-
ORDER BY 注入(排序字段不能预编译)
✅ 想要 100% 防 SQL 注入,还需要额外措施!
✅ 最佳安全做法: -
严格限制用户输入
-
使用 ORM 框架
-
遵循最小权限原则
-
-