PHP中绕过字符串转义与预处理语句的SQL注入技术问询
针对mysqli_real_escape_string的绕过场景
mysqli_real_escape_string并非绝对安全,以下是实际中可能存在的绕过情况:
宽字节注入(字符集不匹配)
当数据库连接字符集为GBK等宽字节编码,而Web应用使用UTF-8处理输入时,可构造宽字节字符“吃掉”转义符。例如输入%bf%27(URL编码后的¿'),mysqli_real_escape_string会将单引号转义为%bf%5c%27,但GBK编码中%bf%5c是一个合法汉字“縗”,最终数据库解析后会剩余单引号',从而触发注入。
测试时可在sqlmap中指定字符集:sqlmap -u "http://target.com/?id=1" --charset GBK错误的编码/转义逻辑
如果开发者存在二次转义、未正确设置连接字符集,或在拼接SQL时未用引号包裹转义后的参数,都会导致防护失效:- 示例1(未加引号的数字型参数):
此时输入$id = mysqli_real_escape_string($conn, $_GET['id']); $sql = "SELECT * FROM users WHERE id = $id"; // 无引号包裹,转义无效1 OR 1=1即可直接注入,sqlmap可直接检测到这类数字型注入。 - 示例2(二次转义导致转义符失效):
若应用先转义一次,再对输入做URL解码,输入%5c%27(即\')会被第一次转义为%5c%5c%27,URL解码后变回\',最终数据库解析为单引号。
- 示例1(未加引号的数字型参数):
特殊字符绕过
部分场景下可利用不需要单引号的注入语法,比如基于布尔的盲注用1 OR 1=1,或使用十六进制编码代替字符串(如SELECT * FROM users WHERE username = 0x61646D696E代替SELECT * FROM users WHERE username = 'admin'),避免触发转义逻辑。
针对预处理语句(参数化查询)的“绕过”情况
正确使用的预处理语句(参数与SQL结构完全分离)是无法被常规SQL注入绕过的,因为数据库会将参数作为纯数据解析,不会当作SQL指令执行。但以下情况可能被误认为是“绕过”:
错误使用预处理语句
很多开发者误以为只要用了prepare就安全,实则将用户输入直接拼接到SQL模板中:$username = $_GET['username']; $sql = "SELECT * FROM users WHERE username = '$username'"; // 直接拼接 $stmt = $conn->prepare($sql); // 预处理无效这种情况下和普通拼接SQL无区别,sqlmap可正常检测注入。
存储过程内部的动态SQL
如果预处理语句调用的存储过程内部存在动态SQL拼接,即使外部用了参数化,内部仍有风险:CREATE PROCEDURE getUser(IN p_id INT) BEGIN SET @sql = CONCAT('SELECT * FROM users WHERE id = ', p_id); PREPARE stmt FROM @sql; EXECUTE stmt; END;此时传入
1; DROP TABLE users;可触发注入,sqlmap可通过测试存储过程参数来检测。数据库底层漏洞
极少数情况下,数据库的预处理实现存在安全漏洞(如早年MySQL的某些版本),但这类情况属于数据库自身问题,并非通用的注入技术,且目前主流数据库版本已修复此类问题。
关于sqlmap检测失败的说明
- 若目标正确使用了参数化预处理语句,sqlmap确实无法注入,因为参数化本身就是有效的防护手段。
- 针对mysqli_real_escape_string的场景,可尝试使用sqlmap的tamper脚本绕过转义,比如:
--tamper=unmagicquotes:处理转义符相关的绕过--tamper=charencode:对字符进行URL编码,尝试避开转义逻辑--tamper=gbk2utf8:针对宽字节场景的编码转换
内容的提问来源于stack exchange,提问作者Marek Sabol

