想象一家只有固定菜单的餐厅:服务员手中永远拿着同一张“取货单”模板,只在“菜品编号”和“备注”两栏请顾客填写,然后交给后厨。数据库就像一个忙碌的后厨,只认取货单上的指令。“SQL注入”就是有客人不按规矩只用编号,而是在备注栏里写下“顺带把仓库所有账本复印一份”,而服务员毫不知情地照单全抄,让备注文字混进了取货指令。本篇文章就是一趟“厨房安全改造之旅”:从看清客人如何篡改单据,到学会用“二维码编号餐券”(参数化查询)、“门口安检”(WAF)等层层手段,让任何备注文字永远无法变成指令,从而彻底守住数据库这座后厨的大门。

岗位关联

对 AI 从业者是构建安全 AI 应用与数据管线的必备常识:理解注入原理能帮你设计更稳健的 Agent 工具调用与数据访问层,规避 OWASP Top 10 中最高发的漏洞风险。

目录
  1. 可视化演示
  2. 注入原理剖析
  3. 联合查询注入
  4. 盲注技术
  5. 堆叠与二次注入
  6. 参数化查询防御
  7. ORM框架防护
  8. WAF与输入校验
  9. 代码审计实践
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

注入原理剖析

餐厅服务员小 Web 每天的工作就是帮客人填写“取货单”——也就是构造 SQL 语句。取货单是固定模板:SELECT * FROM users WHERE name = '客人填写的名字'。服务员图省事,直接把客人递来的备注纸条原封不动贴进模板,根本没想到纸条上写的不只是“名字”,还藏着给后厨的额外指令。这就是注入的第一性原理:字符串拼接把用户输入当成了指令的一部分。

在计算机世界里,“取货单模板”由开发者在代码中写死,客人可控的只有输入框里的字符串。危险函数如 execute("SELECT ... WHERE name = '" + userInput + "'") 把用户输入直接拼进 SQL 文本。SQL 引擎在解析时只看到一串字符,无法分辨哪部分是“结构”哪部分是“数据”——这正是数据与指令混淆的本质。

Query=SELECT * FROM users WHERE name = ’+input+’.\displaystyle Query = \text{SELECT * FROM users WHERE name = '} + input + \text{'} .

1. SELECT 子句:指定要回显的列,例如 * 表示返回所有列。

SELECT ∗\displaystyle \text{SELECT } *

2. FROM 子句:告诉数据库从哪张表取数据,这里是 users 用户表。

FROM users\displaystyle \text{FROM } \text{users}

3. WHERE 条件:用等号过滤出目标行,单引号是 SQL 中字符串的边界符号。

WHERE name=′...′\displaystyle \text{WHERE } \text{name} = '...'

4. input 拼接点:这是漏洞的“病灶”——用户输入未经任何处理直接嵌入。

+input+\displaystyle + input +

5. 恶意输入改写:当 input = ' OR 1=1 -- 时,条件被改写为恒真式,-- 注释掉句尾。

’ OR 1=1 –\displaystyle \text{' OR } 1=1\ \text{--}

识别注入点就像寻找餐厅里所有“直接张贴纸条”的服务员:凡是用户可控参数(URL 参数、表单字段、Cookie)被拼进 SQL 的地方都是注入点。回显差异则是判断依据——正常输入与恶意输入返回的数据条数不同,就是信号。理解了这张“拼接陷阱”,后面的防御手段便都有了靶心。

联合查询注入

既然服务员会把备注贴进取货单,聪明的攻击者决定“加塞”一整张并行的取货单:原来你让我点一份招牌菜,我偏要在单子上追加“同时把后厨的调味料配方表抄给我”。SQL 里这个“同时抄送”的语法叫 UNION,它能把两条查询的结果合并成一张表返回。

但 UNION 有个苛刻的条件:两个查询的列数必须一致。攻击者就像锲而不舍的试菜员,先用 ORDER BY n 逐级加数字试探列数——排序报错说明列数超了,不报错的临界点就是真实列数。列数探明后,再把 UNION SELECT 1,2,3 放进去,哪个位置数字回显了,哪个就是可利用的“回显窗口”。

SELECT id, username, age FROM usersUNIONSELECT 1, version(), database().\displaystyle \text{SELECT id, username, age FROM users} \quad \text{UNION} \quad \text{SELECT 1, version(), database()} .

1. UNION 关键字:把左右两条查询的结果纵向合并,前提是列数一致。

⋯ UNION ⋯\displaystyle \cdots \ \text{UNION} \ \cdots

2. 列数探测(ORDER BY):ORDER BY n 按第 n 列排序,n 超出列数会报错,借此确定列数。

ORDER BY n\displaystyle \text{ORDER BY } n

3. 数字占位:SELECT 1,2,3 用数字填充每列,观察哪个位置在页面回显。

SELECT 1,2,3\displaystyle \text{SELECT } 1,2,3

4. 函数替换:把占位数字替换成 version()、database() 等敏感函数获取情报。

SELECT 1, version(), database()\displaystyle \text{SELECT } 1,\ \text{version()},\ \text{database()}

联合查询的杀伤力在于“偷看+偷拿”一步到位。但要注意,很多代码审计工具直接搜索 UNION 关键字就能发现此类攻击痕迹,这也是为什么常说“拼接是万恶之源”。对付这种加塞,最干脆的办法就是让取货单模板固定——这正是后文预编译语句的用武之地。

盲注技术

有些餐厅后厨隔着一堵墙,服务员从不回显任何菜品之外的信息——页面既不报数据库错,也不把数据打在脸上。攻击者只能通过“这道菜做没做出来”或“上菜快不快”来一点一点猜测后厨的秘密。这就是盲注(Blind Injection),分布尔盲注和时间盲注两种。

布尔盲注像玩“是/否”猜谜:构造 AND 1=1 页面正常,AND 1=2 页面异常,说明注入点存在;接着把判断条件换成“数据库名的第一个字符的 ASCII 码是否大于 100”,通过页面状态逐字符逼问出密码与库名。时间盲注更隐蔽——把 SLEEP(5) 塞进条件里,如果页面卡顿 5 秒,说明条件为真。

IF(ASCII(SUBSTRING(database(),1,1))>100, SLEEP(5), 0).\displaystyle \text{IF}(\text{ASCII}(\text{SUBSTRING}(\text{database()},1,1)) > 100,\ \text{SLEEP}(5),\ 0) .

1. SUBSTRING 切割:逐位取出字符串的第 1 个字符,循环改变下标即可遍历整串。

SUBSTRING(database(),1,1)\displaystyle \text{SUBSTRING}(\text{database()},1,1)

2. ASCII 转码:把字符转成数字,以便用二分法比较大小。

ASCII(⋯ )\displaystyle \text{ASCII}(\cdots)

3. IF 分支:条件成立走一个分支,不成立走另一个分支,布尔盲注据此判断真伪。

IF(cond, true, false)\displaystyle \text{IF}(cond,\ \text{true},\ \text{false})

4. SLEEP 延时:时间盲注的“信号灯”,成立则强制数据库睡眠,用响应时长传递信号。

SLEEP(5)\displaystyle \text{SLEEP}(5)

盲注效率虽低,却致命——它不依赖任何回显,只要有“真/假”或“快/慢”两种状态,就能被自动化脚本破解。防御盲注的核心依旧是参数化查询加最小权限:即使猜出库名,没有权限也拿不到数据。

堆叠与二次注入

服务员小 Web 这次遇到了一位“连招”高手:客人在备注栏里写完正式需求后,加了一个分号 ;,又附上一句“把后厨所有签名菜谱全部烧掉”。分号在 SQL 中是语句结束符,引擎把分号后面的内容当作第二条独立语句执行——这就是堆叠注入,危害远超普通注入,因为它能执行增删改甚至 DROP TABLE。

二次注入则像“存档式诈骗”:第一次提交时,系统把恶意数据原样存进数据库(比如注册用户名 admin'--),但这次没有立刻拼接执行,所以侥幸过关。等后台管理功能把这条“存档”取出来拼进另一个 SQL 时,恶意代码第二次被唤醒,触发攻击。它最大的迷惑性在于攻击发生在数据存储之后,WAF 往往只拦第一棒。

SELECT * FROM users; DROP TABLE orders;\displaystyle \text{SELECT * FROM users;}\ \text{DROP TABLE orders;}

1. 分号分隔符:SQL 用分号结束一条语句,多个分号意味着多条独立语句。

;\displaystyle ;

2. 第一条语句:正常的查询或操作,用来迷惑系统。

SELECT * FROM users\displaystyle \text{SELECT * FROM users}

3. 第二条语句:攻击者的真实意图,如删除整张订单表。

DROP TABLE orders\displaystyle \text{DROP TABLE orders}

预编译语句能拦截绝大部分注入,但堆叠注入的防御难点在于——很多数据库驱动默认允许一次提交多条语句,需要在连接层显式关闭“多语句支持”。而二次注入揭示了一个残酷事实:单点防御永远不够,存储、读取、拼接每个环节都要一视同仁地做参数化。

参数化查询防御

终于来到了整趟旅程的高光时刻。与其堵住每个客人的嘴不让他们乱写备注,不如改变“取货单”的交互方式:把备注栏换成编号复选框,客人只能用“编号 1-10”选菜品,所有文字都放进一个带锁的“数据口袋”,由服务员原封不动交给后厨。后厨看到编号就知道该做什么菜,口袋里哪怕是写着“DROP TABLE”的纸条,也只会被当作调料名处理。这就是参数化查询(预编译语句)。

核心机制是预编译:数据库先把 SQL 语句的骨架(模板)解析编译好,留下 ? 或 $1 占位符;执行阶段才把用户输入作为参数绑定进去。此时输入已经被标记为“数据”,数据库绝不再尝试把它当语法解析。换句话说,指令的语法树在用户输入到达之前就已经定型了。

\displaystyle \text{SELECT * FROM users WHERE id} = \\$1,\quad \text{bind}(\\$1) = input .

1. 预编译模板:SQL 文本先被数据库编译成内部语法树,结构从此固定。

SELECT ∗ FROM users WHERE id=?\displaystyle \text{SELECT } * \text{ FROM users WHERE id} = \text{?}

2. 占位符:用 ? 或 $1 标明此处是“值的位置”而非“代码的位置”。

\displaystyle ? \quad \text{或} \quad \\$1

3. 参数绑定:把用户输入通过驱动 API 传给占位符,驱动负责转义与类型转换。

\displaystyle \text{bind}(\\$1, input)

4. 执行与返回:数据库在固定结构上填入值并执行,恶意字符失去语法身份。

execute(template,params)\displaystyle \text{execute}(template, params)

几何意义上,参数化查询把“输入空间”和“语法空间”彻底分离成两个不相交的集合:无论输入集合里包含什么奇怪字符,永远落在“数据点”上,无法触碰到“语法轴”。这一招不仅防单引号逃逸,对布尔盲注、时间盲注同样釜底抽薪——因为注入的“判断条件”根本进不了语法层。

ORM框架防护

如果说参数化查询是给服务员换上了编号餐券,那么 ORM(对象关系映射)就是给整个餐厅上了智能点餐平板。开发者在代码里写下 User.objects.filter(id=user_input),ORM 框架自动把对象操作翻译成带占位符的 SQL,并在底层完成参数绑定。开发者不需要手写一条 SQL,天然避开了字符串拼接这个“病灶”。

但“智能平板”也有盲区:如果你在 ORM 里写 raw()、execute() 或 native query,等于绕过平板直接手写取货单,拼接风险重新回来了。另外,ORM 的参数化只能处理“值”,表名、列名等数据库标识符无法参数化——如果你把用户输入拼进表名,ORM 也救不了你。

\displaystyle \text{ORM: } \text{User.objects.filter}(id = input) \Rightarrow \text{SELECT * FROM users WHERE id} = \\$1 .

1. 对象查询:用 Python/Java 等语言的类和链式方法表达查询意图,不出现 SQL 文本。

User.objects.filter(id=input)\displaystyle \text{User.objects.filter}(id = input)

2. 自动翻译:ORM 框架把对象操作翻译成 SQL 模板,值全部用占位符替代。

⇒SELECT ∗ FROM users WHERE id=?\displaystyle \Rightarrow \text{SELECT } * \text{ FROM users WHERE id} = \text{?}

3. 底层绑定:框架调用数据库驱动完成参数绑定,行为等同手工预编译。

params→placeholder\displaystyle \text{params} \to \text{placeholder}

用 ORM 时有一条铁律:凡是能不用原生 SQL 就不用,凡是涉及动态表名列名则必须走白名单映射。把用户输入变成“表 A”或“表 B”的索引,而不是直接拼进 FROM 子句——这等于给平板电脑也加了一把“编号锁”。

WAF与输入校验

即便厨房内部已经用上了编号餐券,餐厅门口仍要有安检员——这就是 WAF(Web 应用防火墙)。WAF 站在所有请求进入应用之前,用规则库识别恶意载荷,比如看到 ' OR 1=1、UNION SELECT、SLEEP( 等特征串就当场拦下。它仿佛是给后厨加了第一道警戒线,即使后面某个服务员的取货单模板有漏洞,恶意请求也根本走不到那一步。

但安检员不是万能的:攻击者可以用编码绕过——把关键字符 URL 编码、Unicode 编码或换成等价写法,让 WAF 的规则匹配不到。所以更可靠的策略是输入白名单校验:规定输入必须是纯数字、纯字母或符合邮件格式,白名单之外的字符一律拒绝,从源头上抹掉注入语法出现的可能。

block=1ifpattern_match(input,rules)=1.\displaystyle \text{block} = 1 \quad \text{if} \quad \text{pattern\_match}(input, rules) = 1 .

1. 规则匹配:正则或签名库扫描输入,检测 ' OR、UNION、SLEEP 等高危特征。

pattern_match(input,rules)\displaystyle \text{pattern\_match}(input, rules)

2. 拦截决策:命中规则即返回 1,WAF 直接返回 403 阻断请求。

block=1\displaystyle \text{block} = 1

3. 放行逻辑:未命中的请求进入应用层,继续接受参数化查询保护。

block=0⇒pass\displaystyle \text{block} = 0 \Rightarrow \text{pass}

纵深防御体系是“安检员 + 编号餐券 + 白名单”的多层嵌套:WAF 拦掉 90% 的脚本小子,白名单把可输入空间压缩到最小,参数化查询兜住最后的结构边界。记住,WAF 只是补充不是主力,真正的安全必须做在应用层。

代码审计实践

最后,每隔一段时间,餐厅总部会派“卫生稽查员”来检查所有门店——这就是代码审计。稽查员拿着检查清单,逐个查找服务员手上的“手写取货单”:搜索代码中所有 execute、query 等危险函数调用,看其参数来源是否经过拼接,这就是危险函数排查。

高级一点的稽查员用污点追踪:把用户可控的输入(请求参数、表单字段、请求头)标记为“污点”,顺着变量传递路径追踪,看污点是否流入了 SQL 拼接点。只要发现“污点 → 无净化 → 拼接执行”的路径,就判定为漏洞。在 AI 辅助时代,这类追踪可以由静态分析工具扫描上千个文件自动完成,审计人员只需复核确认。

vulnerable=taint(input)=1∧concat(query)∧¬parameterized(query).\displaystyle \text{vulnerable} = \text{taint}(input) = 1 \wedge \text{concat}(query) \wedge \neg \text{parameterized}(query) .

1. 污点源:用户输入被标记为不可信源。

taint(input)=1\displaystyle \text{taint}(input) = 1

2. 汇聚点:污点数据流入 SQL 字符串拼接位置。

concat(query)\displaystyle \text{concat}(query)

3. 缺防护:该位置没有使用参数化查询或预处理语句。

¬parameterized(query)\displaystyle \neg \text{parameterized}(query)

4. 漏洞结论:三者与(∧)成立则确认漏洞,进入修复流程。

vulnerable=1\displaystyle \text{vulnerable} = 1

审计的终点是形成防护加固清单:统一改为参数化查询、开启 WAF、限制数据库账号权限、启动 CICD 安全扫描门禁。安全开发的流程由此闭环——需求阶段做威胁建模,编码阶段用 ORM 与预编译,测试阶段跑自动化扫描,上线前再人工审计复核,把“注入”这个老对手彻底挡在门外。

AI 应用场景

场景一:AI 辅助代码审计

阶段:安全分析与漏洞检测

解决的问题:人工审计数千条 SQL 查询语句耗时巨大且容易遗漏,传统正则工具误报率较高。

具体案例:某金融科技公司引入大模型驱动的代码审计助手,对 GitHub 代码库做污点追踪:模型将用户输入标记为污染源,通过 AST 分析判断是否流入 cursor.execute() 拼接位置,自动生成带修复建议的漏洞报告,审计周期从两周压缩到一天。

场景二:AI 增强 WAF 规则优化

阶段:安全检测与响应

解决的问题:WAF 的硬编码规则无法覆盖不断演变的绕过编码,导致漏报或误报。

具体案例:安全运营团队用机器学习模型分析历史攻击日志,自动学习正常流量与攻击流量的特征分布,动态生成新的 WAF 拦截规则,同时利用模糊测试自动生成编码绕过样本反向验证规则有效性,使拦截率从 82% 提升至 97%。

场景三:智能聊天机器人的安全数据访问

阶段:Agent 工具调用与数据访问

解决的问题:大模型 Agent 在生成 SQL 查询时可能把自然语言中的恶意指令混入查询文本,形成新型注入风险。

具体案例:某企业 BI 机器人在接收“查询上季度销售数据”时,由底层数据访问层强制走参数化查询,任何模型生成的输入都只能作为绑定参数;同时对 Agent 进行输入约束,把表名列名映射到白名单,从架构上杜绝了 Prompt 注入演变为 SQL 注入的可能。

Python 演示

python39 行
import sqlite3 def init_db():    conn = sqlite3.connect(':memory:')    conn.executescript('''        CREATE TABLE users (            id INTEGER PRIMARY KEY,            username TEXT,            password TEXT        );        INSERT INTO users (username, password) VALUES            ('alice', 'secret123'),            ('bob', 'bob@2024'),            ('admin', 'admin_root');    ''')    return conn def unsafe_login(conn, username, password):    sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"    print('[危险] 拼接SQL: ' + sql)    return conn.execute(sql).fetchall() def safe_login(conn, username, password):    sql = 'SELECT * FROM users WHERE username = ? AND password = ?'    print('[安全] 参数化SQL: ' + sql)    return conn.execute(sql, (username, password)).fetchall() def main():    conn = init_db()    evil_user = "' OR 1=1 -- "    rows = unsafe_login(conn, evil_user, 'anything')    print('拼接查询返回行数:', len(rows))     rows = safe_login(conn, evil_user, 'anything')    print('参数化查询返回行数:', len(rows))    conn.close() if __name__ == '__main__':    main()
目的:用 SQLite 内存数据库演示同样的恶意用户名下,字符串拼接与参数化查询两者截然不同的结果,直观证明预编译防注入的威力。
  1. sql = "SELECT * FROM users WHERE username = '" + username + "'"危险写法,把用户输入直接拼进 SQL 文本,恶意载荷可以逃逸单引号。
  2. return conn.execute(sql).fetchall()带有用户输入的拼接 SQL 被直接执行,攻击者可绕过登录校验。
  3. sql = 'SELECT * FROM users WHERE username = ? AND password = ?'安全写法,用 ? 占位符表明这里只是“数据位置”。
  4. conn.execute(sql, (username, password))Python 数据库驱动自动完成参数绑定,用户输入被当作纯数据处理。
  5. evil_user = "' OR 1=1 -- "经典注入载荷,单引号闭合、OR 恒真、-- 注释掉后续条件。

常见误区

误区很多初学者认为把 `'` 替换成 `''` 或者直接禁止单引号就能防住 SQL 注入,因为攻击必须用引号闭合字符串。

正解单引号过滤只是脆弱的编码层补救。攻击者可以改用无引号数字型注入、UNION SELECT 配合十六进制编码、宽字节编码绕过转义等手法。而且误替换合法内容会导致业务数据损坏。真正可靠的防线是参数化查询——不是跟特殊字符玩捉迷藏,而是从结构上让字符失去语法身份。

为何常见:初学者从最简单的注入例子(' OR 1=1 --)入手,容易把“过滤引号”误当成通用解法,而忽略了注入的本质是“拼接结构”。

误区不少团队认为上线前挂了 WAF 就代表 SQL 注入已防御完毕,后端可以继续拼 SQL。

正解WAF 是基于特征规则的黑名单工具,攻击者用注释符、URL 编码、Chunked 分块传输、大小写混写等手段即可绕过。WAF 的作用是“减少攻击面、拖延攻击时间”,不是安全终点。只有应用层参数化查询 + 最小权限数据库账号 + 输入白名单共同作用,才算构建了纵深防御。

为何常见:WAF 厂商的“一键防护”营销让团队误以为安全是买来的产品,而非开发流程的纪律。

误区用了 Django ORM、SQLAlchemy 就认为所有查询都自动免疫注入,可以放心大胆使用 `raw` 或 `text` 原生查询。

正解ORM 的安全承诺只限于框架封装的查询 API。一旦开发者调用 raw()、execute() 或自定义原生 SQL,就会闪回字符串拼接的老路,ORM 完全无法干涉。此外,动态表名、列名或排序字段如果直接拼进去,同样会引发注入。ORM 只是帮你默认走了参数化,并不等于你不可能手写危险代码。

为何常见:框架的便利性让开发者把“默认安全”误当成“绝对安全”,忽略了自己对原生接口的控制力。

误区认为只要全局启用预编译语句,就可以对所有输入高枕无忧,连表名、列名和白名单校验都不需要了。

正解参数化查询只能保护“值”的位置。数据库标识符(表名、列名)、ORDER BY 的排序字段、LIKE 模式的通配符位置都仍然是拼接语法的高危区。这些场景必须靠白名单映射——把用户输入映射到预先定义好的合法标识符集合中,而非直接拼进 SQL。参数化是最好用的武器,但不是唯一武器。

为何常见:安全教材侧重于预编译对比拼接的演示,容易让学员误以为它是一把万能钥匙,忽略了 SQL 语法结构的多样拼接场景。

面试题

Q1. SQL 注入的本质原因是下列哪项?

单选题 · easy

  • A.数据库密码设置过于简单被暴力破解
  • B.用户输入拼接进 SQL 导致数据与指令混淆← 正确答案
  • C.服务器没有安装防火墙软件
  • D.数据库端口直接暴露在公网环境中

答案:B。注入的核心是用户可控输入被拼成 SQL 语句的一部分,使输入获得“指令身份”。选项 A 属于暴力破解,C、D 属于网络层安全问题,都不涉及 SQL 语句构造。就像备注纸条被贴进取货单变成了指令,而不是因为厨房钥匙被偷。

Q2. 联合查询注入中,攻击者先用 ORDER BY n 探测数字 n 的目的究竟是什么?

单选题 · mid

  • A.检测返回结果的排序方式是否稳定
  • B.探测原查询的列数以构造对齐的 UNION← 正确答案
  • C.获取数据库当前的时间与版本信息
  • D.判断目标表是否包含敏感数据字段

答案:B。UNION 合并查询要求左右两侧列数一致,否则报错。攻击者通过不断增大 ORDER BY n 的 n,找到不报错的临界值即真实列数。A 是排序业务,C 是 version() 函数的用途,D 与 ORDER BY 探测无直接关系。

Q3. 以下哪些技术属于 SQL 盲注的范畴?

多选题 · easy

  • A.利用布尔条件真假差异判断的布尔盲注← 正确答案
  • B.利用延时函数判断条件的时间盲注← 正确答案
  • C.利用 UNION 直接回显数据的联合注入
  • D.利用报错信息反馈数据的报错注入

答案:AB。盲注指页面无直接数据回显,只能靠布尔真假或时间延迟旁路推断信息,因此 A、B 正确。C 的 UNION 注入属于有回显注入,D 的报错注入依赖数据库报错信息反馈,也不属于盲注定义范畴。

Q4. 预编译语句(参数化查询)为什么能有效防御 SQL 注入?

单选题 · easy

  • A.它会自动删除输入中的所有特殊字符
  • B.它将用户输入绑定为数据而非参与语法解析← 正确答案
  • C.它会加密数据库中的所有存储数据
  • D.它会自动备份数据库防止数据被删

答案:B。预编译先把 SQL 骨架编译成语法树,再让输入以“参数值”身份绑定进占位符,输入字符再无机会变成指令。A 属于过滤转义思路,C 属于加密范畴,D 属于备份机制,均与预编译原理无关。

Q5. 二次注入与普通注入最本质的区别是什么?

单选题 · mid

  • A.攻击载荷第一次就被数据库直接执行
  • B.恶意数据先被存储、后在其他拼接点触发← 正确答案
  • C.攻击者必须使用时间延迟函数才能成功
  • D.攻击者必须通过 UNION 才能获取数据

答案:B。二次注入的载荷第一次入库时未被拼接执行,看似无害;当系统把存储数据取出拼进另一条 SQL 时,恶意代码二次生效。A 是普通注入,C 是时间盲注,D 是联合注入的特征。

Q6. 使用 ORM 框架时,下列哪些操作仍可能引入 SQL 注入风险?

多选题 · mid

  • A.调用框架提供的 raw 原生查询接口← 正确答案
  • B.将用户输入直接拼接到动态表名中← 正确答案
  • C.使用框架标准链式过滤方法查询
  • D.对查询结果做分页与序列化处理

答案:AB。ORM 的 raw/原生接口绕过了自动参数化,是常见风险点;表名等标识符无法用占位符绑定,拼接同样危险。C 的标准链式过滤是 ORM 安全场景,D 是结果处理,两者不涉及 SQL 构造,故选 A、B。

Q7. 时间盲注中 IF(condition, SLEEP(5), 0) 的判定逻辑是?

单选题 · hard

  • A.条件为真时立即返回,为假时延时
  • B.条件为真时休眠 5 秒,为假时立即返回← 正确答案
  • C.无论如何都休眠 5 秒等待结果
  • D.根据数据库版本决定是否执行休眠

答案:B。IF(cond, SLEEP(5), 0) 的含义是条件成立则执行 SLEEP 产生可观测的响应延迟,条件不成立则立即返回。攻击者通过计时差反推条件真假,因此真分支对应延迟,假分支对应快速响应。

Q8. 堆叠注入之所以比普通注入危害更大,主要原因是?

单选题 · mid

  • A.一条请求可以执行多条独立 SQL 语句← 正确答案
  • B.它可以绕过所有类型的 Web 应用防火墙
  • C.它不需要任何数据库操作权限就能改数据
  • D.它只能用来查询数据无法执行删除操作

答案:A。堆叠注入利用分号分隔多条语句的特性,一次提交即可执行任意数量的 SQL,包括 DROP、INSERT、UPDATE。B 无法保证绕过,C 仍需权限,D 与实际相反——堆叠注入恰恰能执行增删改。

Q9. 以下哪些措施构成了 SQL 注入的纵深防御体系?

多选题 · hard

  • A.应用层统一使用参数化查询或 ORM← 正确答案
  • B.数据库账号遵循最小权限原则← 正确答案
  • C.部署 WAF 并开启输入白名单校验← 正确答案
  • D.把数据库表结构文档公开展示

答案:ABC。纵深防御要求多个独立层次协同:应用层参数化(A)、数据库层最小权限(B)、网络层 WAF 与白名单(C)层层设防。D 属于主动泄露信息,是严重安全反面操作,故选 A、B、C。

Q10. 代码审计中识别 SQL 注入最典型的危险代码模式是?

单选题 · easy

  • A.用户输入被拼接进字符串后传入 execute← 正确答案
  • B.程序使用常量 SQL 字符串并定长查询
  • C.数据库连接启用了超时重连机制
  • D.代码中大量使用事务回滚操作函数

答案:A。危险模式的核心是“用户可控输入 + 字符串拼接 + 执行函数”三者同框出现。B 的常量 SQL 没有用户输入,C、D 属于数据库连接与事务管理范畴,不构成注入条件。审计时首要搜索拼接型 execute 调用。