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,才能形成纵深防御。面试中若能主动说出“参数化不是过滤,而是让数据库区分代码与数据”,往往能给面试官留下深刻印象。