传统(不安全)的 SQL 执行流程(直接拼接字符串):
-
连接器 (Connector): 客户端连接成功,获得权限。
-
(跳过查询缓存): 假设缓存未命中或未使用。
-
分析器 (Analyzer):
- 应用程序将拼接好的、可能包含恶意输入的完整 SQL 字符串(如 "SELECT user_id FROM users WHERE username = '' OR '1'='1' -- ' AND password = '...';")发送给 MySQL 服务器。
- 词法分析: 将整个字符串拆分成 Token。OR, '1', =, '1', -- 都会被识别为独立的 Token。
- 语法分析: 这是关键点! 分析器根据语法规则,将这些 Token 构建成一个抽象语法树 (AST)。由于 ' OR '1'='1' -- 符合 SQL 语法(一个 OR 条件和一个注释符),它会被成功地解析并融入 AST 的逻辑结构中。此时,用户输入已经扭曲了原始 SQL 的意图。
-
执行器 (Executor):
- Prepare 阶段: 检查被扭曲后的 AST 中涉及的表、字段是否存在,并验证权限。
- Optimize 阶段: 优化器根据这个被注入、扭曲了的 AST 生成执行计划。它会发现 '1'='1' 是恒真条件,并且 -- 注释掉了后续条件,从而生成一个可能绕过验证的、非预期的执行计划。
- Execute 阶段: 执行器按照这个恶意的执行计划去调用存储引擎接口。它可能会告诉存储引擎:“给我所有用户的 user_id,因为条件是 username = '' OR TRUE”。
-
存储引擎 (Storage Engine): 根据执行器基于恶意计划发出的指令,返回了非预期的数据(比如所有用户的 ID)。
使用预编译语句(Prepared Statements)的安全流程:
这个过程分为两个主要阶段:准备 (Prepare) 和 执行 (Execute)。
阶段一:准备 (Prepare)
-
连接器 (Connector): 客户端连接成功,获得权限。(同上)
-
应用程序发送 SQL 模板: 客户端发送的是带有占位符的 SQL 模板(如 "SELECT user_id FROM users WHERE username = ? AND password = ?;")给 MySQL 服务器,请求进行“准备”。注意:此时不包含任何用户输入的数据!
-
(跳过查询缓存): 准备语句通常不会利用查询缓存。
-
分析器 (Analyzer):
- 词法分析: 将模板拆分成 Token,? 被识别为占位符。
- 语法分析: 只针对模板进行分析。检查模板的语法是否正确,并构建一个代表模板结构的 AST。这个 AST 清晰地知道 username = ? 和 password = ? 是两个独立的比较条件,? 的位置是需要填入数据值的地方。此时用户输入完全没有参与,AST 的结构是固定的、安全的。
-
执行器 (Executor) - Prepare & Optimize 阶段:
- Prepare 阶段: 检查模板中涉及的表、字段是否存在,验证权限。
- Optimize 阶段: 优化器根据这个安全的、基于模板的 AST 生成一个参数化的执行计划,并缓存起来。这个计划知道它需要比较 username 和 password 列,并等待具体的值。
阶段二:执行 (Execute)
-
应用程序发送参数: 客户端发送执行指令,同时单独发送用户输入的数据(如 ' OR '1'='1' -- 和 any_password)作为参数,并指明这些参数对应模板中的哪个占位符。
-
服务器接收参数: MySQL 服务器接收到执行指令和参数列表。
-
执行器 (Executor) - Execute 阶段:
- 跳过分析器: 参数不再经过分析器的词法和语法分析阶段来构建 AST。数据库已经知道 SQL 的结构了。
- 调用优化好的计划: 执行器调出在“准备”阶段生成并缓存的那个安全的、参数化的执行计划。
- 绑定参数: 将接收到的参数值(' OR '1'='1' -- 和 any_password)仅仅作为数据,填充到执行计划中对应的位置。
- 执行: 执行器按照安全的计划调用存储引擎接口。它会明确地告诉存储引擎:“请查找 username 列的值严格等于字符串数据 ' OR '1'='1' -- ' 并且 password 列的值严格等于字符串数据 any_password 的记录”。
-
存储引擎 (Storage Engine): 接收到执行器的指令,将传入的参数仅仅当作需要比较的数据字面量进行查找。由于找不到 username 完全匹配 ' OR '1'='1' -- ' 的记录,查询失败。
核心区别与防止注入的原因总结:
关键在于分离处理:
- 预编译阶段: SQL 的结构(代码逻辑)在没有用户数据干扰的情况下,通过分析器被一次性确定、解析、优化并固化为安全的执行计划。
- 执行阶段: 用户输入的数据作为独立的参数被发送,它们绕过了分析器对 SQL 结构的解析,直接被执行器当作纯粹的数据值代入到预先定义好的安全执行计划中。
因为用户输入的数据没有机会在分析器阶段影响或改变 SQL 的语法结构 (AST),所以它无法注入恶意的 SQL 逻辑,从而有效防止了 SQL 注入攻击。