跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

如何防御二次SQL注入_对所有读取的数据库字段重新校验

二次SQL注入本质是数据重入执行,即从数据库读出的数据未经校验直接拼入新SQL语句;校验必须在SQL构造前、紧贴执行逻辑处进行,且须用白名单或强类型约束,参数化无法覆盖动态表名等场景。 二次SQL注入的本质是“数据重入执行” 不是所有从数据库读出来的数据都安全,关键看它后续会不会被拼进新的SQL语句。比如用户注册时存了
admin' OR '1'='1
,当时没出问题;但某天后台用这个字段拼了
SELECT * FROM logs WHERE user = '{$row['name']}'
,就炸了。校验不是为了“消毒”,而是为了阻断“信任链”——数据库字段 ≠ 可信输入。 校验必须在拼接SQL前做,不能靠入库时过滤 入库时转义或参数化只能防一次注入,对二次注入无效。真正要拦住的,是读出来之后、进
mysqli_query
或
PDO::exec
之前那一步。常见错误是只在表单提交时校验,却放任
$user_name
从DB查出来后直接进查询。 校验位置:紧贴SQL构造逻辑,比如在调用
prepare()
前检查
$name
是否含
'
、
;
、
UNION
等危险字符 不要用黑名单(如简单
str_replace(['"', "'"], '', $s)
),容易绕过;优先用白名单(如只允许字母数字下划线)或类型强约束(如ID必须是
is_int()
或
ctype_digit()
) 如果字段本该是邮箱,就用
filter_var($email, FILTER_VALIDATE_EMAIL)
;如果是用户名,用
preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]{2,20}$/u', $name)
参数化无法自动覆盖二次注入场景 很多人以为用了PDO预处理就万事大吉,其实不然。预处理只保护“你写的SQL模板里的占位符”,不保护“你动态拼出来的表名、字段名、ORDER BY 子句”。比如:
SELECT * FROM {$table} WHERE id = ?
这里
$table
是从DB读出来的,哪怕id用
?
占位,
$table
依然能触发注入。这种地方必须单独校验
$table
是否在允许列表里(如
in_array($table, ['users', 'orders'])
)。 危险函数组合:任何把DB字段直接塞进
mysql_query("...{$var}...")
、
mysqli_real_escape_string()
(已废弃)、或
eval()
/
create_function()
的地方都要拉响警报
mysqli_real_escape_string()
对二次注入基本无效——它依赖当前连接的字符集,而读取字段时连接可能已切换,逃逸失效 ORM如Eloquent默认不保护动态表名/列名,
whereRaw()
、
orderByRaw()
同样需要手动校验传入的变量 别把校验逻辑写死在业务代码里 每次读DB后都手写
if (!ctype_alnum($name)) die('hacker')
很容易漏。更可靠的方式是定义“可信上下文”: 封装读取函数,如
getTrustedUserName($id)
,内部强制走白名单校验,业务层只许调它,不许直查
users.name
对高危字段(如
redirect_url
、
callback
)加注释标记:
// DB field: must be validated before use in header() or SQL
用PHP的
filter_var()
配合自定义过滤器,比如为SQL标识符写一个
FILTER_SANITIZE_STRING
+ 正则限制的组合 最麻烦的其实是那些“看起来无害”的字段:昵称、签名、来源URL——它们往往被当成普通字符串显示,结果某天被拿去拼
INSERT INTO audit_log (op_by) VALUES (?)
,而?前面那个
audit_log
表名又恰好是从配置表读的……这种链路一环没卡住,整条就崩。

相关文章