mysql解析器不识别sql注入,仅做语法校验;真正防御靠preparedstatement的参数隔离机制,将sql模板与参数分离传输,使用户输入永不参与解析。

MySQL解析器不会主动识别SQL注入风险
解析器只负责语法校验和生成执行计划,它不判断你的 SELECT * FROM users WHERE id = ? 是合法查询还是攻击载荷。所谓“识别注入”,其实是应用层的事——比如你拼接了用户输入:"SELECT * FROM users WHERE name = '" + userInput + "'",解析器照单全收,只要语法对就继续往下走。真正能拦住注入的,是预处理语句(PreparedStatement)在客户端和服务端协作完成的参数隔离机制。
PreparedStatement 执行时发生了什么
不是“先拼SQL再执行”,而是分两步走:第一步发带占位符的模板(如 INSERT INTO log(msg) VALUES(?))给 MySQL,服务端编译并缓存执行计划;第二步只传二进制参数值(比如字符串 "' OR 1=1 -- "),这些值永远被当数据处理,不会进入解析阶段。
- 服务端收到
COM_STMT_PREPARE包后,调用parse_sql()解析模板,但此时没有参数值,无法构成完整语句,也就不存在“注入上下文” - 后续
COM_STMT_EXECUTE包里只含参数类型、长度和原始字节,MySQL 直接绑定到已编译的执行计划中,跳过词法分析和语法树重构 - 即使参数里含
;、--、/*等字符,也不会触发语句截断或注释解析——因为它们根本不在 SQL 字符串里
为什么 prepare + execute 组合才安全,单独用 prepare 不行
prepare 本身只是个 MySQL 命令,如果在客户端用字符串拼接构造出带恶意内容的模板(比如 "SELECT * FROM t WHERE id = " + userInput),再交给 PREPARE stmt FROM @sql,那注入已经发生在模板生成阶段了。真正的安全边界在编程语言的 PreparedStatement API(如 Java 的 Connection.prepareStatement()),它强制把 SQL 模板和参数分离,并通过协议级字段传输参数。
- Java 中
ps.setString(1, userInput)发送的是独立的MYSQL_TYPE_STRING类型参数,不是字符串插值 - PHP 的
$pdo->prepare("SELECT ... ?")同理,execute([$userInput])走的是 binary protocol 参数通道 - 若用 MySQL 原生命令
SET @s = CONCAT('SELECT * FROM t WHERE x=', @inj); PREPARE stmt FROM @s;,等于自己绕开所有防护
容易被忽略的“伪安全”场景
有些写法看着像预处理,实际没生效。比如动态表名、列名、排序字段——这些不能用 ? 占位,只能拼接,一旦来源不可信,立刻破防。
-
SELECT * FROM ?是语法错误,MySQL 不允许?出现在表名位置 -
ORDER BY ?虽然能通过 prepare,但参数会被当成字符串字面量(如ORDER BY 'name'),而非列标识符,结果可能不符合预期 - 用
CONCAT()或FORMAT()在 SQL 内部拼接字段名,等于把注入点从应用层挪到了数据库层,依然危险
参数化只保得住值(value),保不住结构(identifier)。结构部分必须白名单校验或硬编码。











