末尾校验完全无效,因为注释符在SQL注入中起作用的关键是“截断后续SQL”,攻击者可将其置于中间或开头(如admin' -- abc),且存在多种编码与Unicode变体(如%0a--、–),仅检查末尾+字面量必然失效。

单纯校验字符串末尾是否为 -- 或 # 无法防御注释符绕过,因为攻击者根本不需要把注释符放在末尾。
为什么末尾校验完全无效
注释符在 SQL 注入中真正起作用的位置,是它能“截断”后续原有 SQL 片段。攻击者往往把 -- 或 # 插在中间甚至开头,而非末尾:
-
admin' -- abc→ 后续AND password = 'xxx'被注释掉(典型登录绕过) -
123 OR id > 0 #→ 后续闭合引号或括号被跳过 -
test'/**/UNION/**/SELECT/**/1,2,3#→#在长 payload 末尾,但前面的/**/已完成空格替代
更关键的是:很多 WAF 或自定义校验只查 -- 字面量,却忽略 %0a--%0d(换行+回车)、%09--(Tab 开头)、甚至 –(Unicode 破折号,U+2013)等变体。末尾位置 + 字面匹配,双重失效。
哪些输入场景容易误信“末尾安全”
开发者常在以下地方错误依赖末尾检查:
- 对
username字段只扫结尾是否有--,却放行admin'--x(单引号未闭合,但已触发截断) - 对搜索关键词做
trim()后检查末尾,但攻击者用%09--%09(Tab 包裹)绕过 trim 和末尾判断 - 前端 JS 校验只检测
.endsWith('--'),后端又没重做,导致服务端直接拼接执行 - 日志告警规则只匹配
.*--$,漏掉.*--\s+.*或带编码的变体
如果必须做字符层过滤,该拦什么、怎么拦
过滤不是首选方案,但如果处于遗留系统无法立刻改用预编译,至少应覆盖这些点:
- 统一解码后再检查:先做
urldecode()、utf8_decode()(视语言而定),再处理,否则%2D%2D直接逃逸 - 不只盯
--和#,还要识别常见注释等效形式:/*、/*!、%0a--、%0d#、%09/* - 禁止连续空白符后紧跟注释符:如
\s+--、\s+#(正则需用/[\s\u2000-\u200F]+(--|#)/u类模式) - 对数字型参数,强制转为整型或浮点型,
is_numeric()+filter_var($input, FILTER_VALIDATE_INT)比字符串扫描可靠得多
注意:mysql_real_escape_string() 已废弃;addslashes() 对 -- 完全无感;htmlspecialchars() 只防 XSS,不防 SQLi。
真正可靠的防线只有一条:预编译语句
所有语言的标准驱动都支持,且行为一致:
- PHP PDO:
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?"); $stmt->execute([$name]); - Java JDBC:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?"); ps.setInt(1, userId); - Python psycopg2:
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
关键点在于:数据库服务端先解析 SELECT * FROM users WHERE name = ? 这个模板并缓存执行计划,? 占位符传入的值以二进制协议单独传输,绝不会参与语法分析——哪怕你传入 admin'--,它也只是一个 7 字节的字符串值,不是 SQL 代码的一部分。这才是从根源上消灭注释符滥用的唯一方式。
任何试图靠字符串位置、正则、黑名单去“堵”注释符的行为,本质都是在对抗数据库解析器的灵活性,注定失败。

















