3. SQL注入面试题精讲
SQL注入是Web安全面试中出现频率最高的考点之一。面试官往往不会只问“什么是SQL注入”,而是会沿着“原理—利用条件—具体手法—防御方案”这条链路层层追问。本章围绕联合注入、报错注入、布尔盲注、时间盲注、堆叠注入、二次注入、宽字节注入七种典型类型展开,帮助你在面试中既能讲清原理,又能说出可落地的判断依据和防御措施。
一、联合注入:最直接的“回显型”利用
联合注入的核心是借助UNION SELECT把攻击者构造的查询结果拼接到原查询的结果集中,从而直接回显在页面上。它的利用条件比较明确:页面有回显位、原查询与注入查询的列数一致、数据类型兼容。
实战中第一步是判断列数。常用手法是ORDER BY递增试探:
?id=1 ORDER BY 3 --+
?id=1 ORDER BY 4 --+
当页面从正常变为报错或空白,说明列数为3。接着用UNION SELECT 1,2,3确认回显位,把数字替换为database()、user()、version()等函数,逐步获取库名、用户名和版本。若目标库支持,还可通过information_schema.tables和information_schema.columns枚举表名与字段名,最终拖出账号密码等敏感数据。
面试中常被追问“为什么用--+而不是--”。原因是URL中空格会被截断或转义,+在URL解码后代表空格,配合--注释掉后续SQL,保证语句闭合。防御上,首选参数化查询(预编译),其次对输入做白名单校验,并关闭数据库报错回显。
二、报错注入:没有回显位时的突破口
当页面不直接显示查询结果,但会显示数据库报错信息时,报错注入就派上用场。它的原理是构造让数据库主动抛出错误的表达式,并把要查询的数据拼进错误信息中。
MySQL中常见函数有extractvalue()和updatexml()。例如:
?id=1 AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e)) --+
其中0x7e是波浪号~,用于在报错信息中标记数据边界。执行后页面会返回类似“XPATH syntax error: '~security~'”的错误,从而泄露库名。利用条件包括:数据库版本支持该函数、页面回显报错、注入点可执行子查询。
防御报错注入的关键是关闭生产环境的详细报错,统一返回自定义错误页;同时对输入进行严格过滤,禁止extractvalue、updatexml等危险函数出现在用户输入中。但根本方案仍是参数化查询。
三、布尔盲注与时间盲注:无回显、无报错的“盲猜”
布尔盲注适用于页面既不显示数据也不显示报错,但会根据SQL真假返回不同状态(如“存在/不存在”“正常/异常”)的场景。攻击者通过构造真/假条件,逐字符推断数据。例如:
?id=1 AND substr((SELECT database()),1,1)='s' --+
若页面返回“用户存在”,说明首字母为s;否则继续换字符试探。实际利用中会配合二分法提高效率,比如用ascii(substr(...))>100逐步缩小范围。
时间盲注则更进一步:页面连真假状态都不区分。此时利用SLEEP()或BENCHMARK()制造响应延迟,通过观察响应时间判断条件真假:
?id=1 AND IF(substr((SELECT database()),1,1)='s',SLEEP(3),0) --+
若请求耗时约3秒,说明条件成立。时间盲注的利用条件最宽松,但代价是请求量大、耗时长,面试中常被问“如何优化”——答案是二分法、多线程和结合CASE WHEN减少请求次数。
防御盲注的核心仍是参数化查询,同时可对单IP请求频率做限制,对异常延迟请求进行监控告警。
四、堆叠注入:一次提交多条语句
堆叠注入指在注入点后使用分号;拼接多条SQL语句,使数据库依次执行。例如:
?id=1'; DROP TABLE users; --+
它的利用条件较为苛刻:数据库驱动或中间件必须支持多语句执行。MySQL的mysqli_query()默认不支持多语句,而mysqli_multi_query()支持;PHP的PDO在默认配置下也不支持。因此面试中若被问“堆叠注入为什么少见”,应从驱动限制角度回答。
堆叠注入的危害在于可执行任意SQL,如删表、改数据、创建管理员账号。防御上,除了参数化查询,还应在数据库账户权限上做最小化控制,禁止应用账户拥有DROP、FILE等高危权限。
五、二次注入:存储与触发的“延迟攻击”
二次注入的特点是:恶意输入在第一次写入时被转义,安全存入数据库;但在后续被读取并再次拼接到SQL中时,转义失效,从而触发注入。典型场景是注册用户名时写入admin' --,注册时被转义为普通字符串存储;当该用户修改密码时,系统从数据库读出该用户名并拼接更新语句,此时'重新生效,导致修改了admin的密码。
二次注入的利用条件包括:存在“存储—读取—再拼接”的流程、读取后的数据未做二次校验、数据库连接未使用参数化。防御方案是:所有SQL一律参数化,不信任任何来自数据库的数据;对用户名等关键字段做白名单限制,禁止特殊字符。
六、宽字节注入:字符集编码引发的绕过
宽字节注入发生在数据库使用GBK等多字节字符集,且开发者用addslashes()或mysql_real_escape_string()转义单引号时。转义会在'前加\,即%5c%27。攻击者输入%df%27,与%5c组合成%df%5c,在GBK中%df%5c被解析为一个合法汉字,剩下的%27即单引号成功“逃逸”。
?id=1%df%27 UNION SELECT 1,2,3 --+
利用条件是:数据库字符集为GBK/GB2312等宽字节、连接层未设置SET NAMES utf8或mysql_set_charset('utf8')、转义函数未考虑多字节。防御方案是统一使用UTF-8字符集,并在连接建立后立即执行SET NAMES utf8mb4;更根本的仍是参数化查询,因为参数化不依赖转义。
七、面试答题框架与防御总表
面对“请讲讲SQL注入”这类开放题,建议按“类型—原理—条件—利用—防御”五步作答,并主动对比不同注入的适用场景。下表可作为快速复习工具:
| 类型 | 核心原理 | 关键利用条件 | 首选防御 |
|---|---|---|---|
| 联合注入 | UNION拼接结果集 | 有回显、列数一致 | 参数化查询 |
| 报错注入 | 错误信息携带数据 | 回显报错、函数可用 | 关闭报错、参数化 |
| 布尔盲注 | 真假状态差异 | 页面状态可区分 | 参数化、频率限制 |
| 时间盲注 | 响应延迟判断 | 可执行SLEEP | 参数化、超时监控 |
| 堆叠注入 | 分号执行多语句 | 驱动支持多语句 | 参数化、最小权限 |
| 二次注入 | 存储后二次拼接 | 存储—读取—再拼接 | 全链路参数化 |
| 宽字节注入 | 多字节字符吃掉反斜杠 | GBK字符集、转义函数 | UTF-8、参数化 |
最后强调一点:无论哪种注入,防御的“第一性原理”都是把用户输入当作数据而非SQL代码。参数化查询(预编译)能从机制上杜绝绝大多数注入;在此之上,配合最小权限、关闭报错、统一字符集、输入校验和WAF,才能形成纵深防御。面试中若能主动说出“参数化不是过滤,而是让数据库区分代码与数据”,往往能给面试官留下深刻印象。