sql注入修复后仍被绕过,大概率因多层解码(如重复urldecode、嵌套base64_decode)或字符集错配导致过滤/参数化逻辑失效,防御关键是在所有解码完成后、且仅一次地执行参数化查询。

修复SQL注入后仍被绕过,大概率不是漏写了某个WHERE条件,而是请求在抵达SQL执行点前,经历了多次编码/解码,导致过滤或参数化逻辑被跳过——比如urldecode()调用两次、base64_decode()嵌套、或前端URL编码 + 后端自动解码 + 手动再解一次。
检查是否有多层URL解码(尤其是urldecode()重复调用)
PHP中$_GET和$_POST默认已由SAPI层完成一次URL解码,若代码里又手动调用urldecode(),就会触发二次解码。攻击者用%2527(即%27的URL编码)就能绕过单引号过滤:
-
%2527→ SAPI第一次解码 →%27→ 手动urldecode()第二次解码 →' - 但你的过滤逻辑(如
str_replace("'", '', $input))可能只跑在第一次解码后的字符串上,对%27完全无感 - 常见误写:
$id = urldecode($_GET['id']); $id = addslashes($id);—— 这里addslashes()作用对象已是%27,不是'
确认Base64解码是否发生在参数化之前
如果用户传data=Ym9yIDE9MQ==,而你写的是$raw = base64_decode($_POST['data']); $stmt->bind_param('s', $raw);,看似安全——但问题在于:这个$raw是否在绑定前被用于其他上下文?比如日志记录、缓存键生成、或拼进另一条非参数化语句?
- Base64本身不改变语义,
Ym9yIDE9MQ==解码后就是or 1=1,和明文输入无异 - 更危险的是递归解码:若前端JS用
btoa(btoa("or 1=1")),后端却写base64_decode(base64_decode($input)),就直接触发注入 - 检查所有
base64_decode()调用点,确认它只出现在绑定参数的路径上,且仅执行一次
排查字符集转换引发的“隐形解码”
MySQL连接层的SET NAMES、PDO的PDO::MYSQL_ATTR_INIT_COMMAND、或mysqli_set_charset()若与实际传输内容编码不一致,会导致数据库把字节流错误解释为字符——比如UTF-8下%C0%BC(无效UTF-8序列)被MySQL按latin1解析成¼,而某些WAF规则只查UTF-8的',就漏掉了。
- 统一全链路编码:HTML声明
<meta charset="utf-8">、HTTP头Content-Type: text/html; charset=utf-8、PHP连接时显式设置set_charset('utf8mb4')、MySQL服务端配置character-set-server=utf8mb4 - 避免依赖
mysql_real_escape_string()(已废弃),它内部会读取当前连接字符集,一旦错配,转义就失效 - 用
bin2hex()临时打印接收到的原始字节,对比你认为的“解码后字符串”,看是否有意外的字节重组
真正棘手的绕过,往往藏在“多一步解码”或“少一步约束”里——比如你以为json_decode()后的内容是干净的,但它里面某个字段值其实是Base64编码的SQL片段,而你忘了对那个字段再做参数化;或者WAF放行了%2527,因为它的规则只扫描第一层解码结果。防御的关键不是堆砌编码,而是明确每一步解码发生的时机、位置和目的,并确保参数化查询的bind_param()或execute()调用,永远在所有解码动作完成之后、且仅此一次。











