必须用参数化查询重写所有数据库操作,addslashes()等无效;快速定位高危点需grep搜索mysql_query(、mysqli_query(、->query(、ORDER BY . $、GROUP BY . $、where.*exp、'exp'等模式,重点检查登录、搜索、详情页、导出接口等用户可控入口。

必须用参数化查询重写所有数据库操作,addslashes()、mysql_real_escape_string()、正则过滤等全是无效方案。
怎么快速定位所有高危SQL拼接点
别依赖扫描工具——它会漏掉二次注入、动态ORDER BY、ThinkPHP的exp模式这类隐蔽点。直接 grep 搜索以下模式:
-
mysql_query(、mysqli_query((未配合mysqli_prepare()) -
->query((尤其ThinkPHP 5.1.x中的Db::query()或$model->query()) -
ORDER BY . $_、LIMIT . $_、GROUP BY . $_(字符串拼接在SQL结构位置) -
where.*exp、'exp'(ThinkPHP中明确放行原生SQL的写法)
重点检查登录、搜索、详情页、导出接口等用户可控参数入口。发现一处就停,先验证是否真能触发报错或布尔盲注(比如传id=1' AND SLEEP(1)--看响应延迟)。
数字型参数不能只靠预处理,必须提前校验类型
预处理本身防不住WHERE id = ?里传入字符串"1 OR 1=1"——mysqli可能隐式转成整数再执行,PDO在PDO::ATTR_EMULATE_PREPARES = true时也会失效。
立即学习“PHP免费学习笔记(深入)”;
- 对
$_GET['id']这类参数,强制用filter_var($_GET['id'], FILTER_VALIDATE_INT)或(int)$_GET['id'],失败则设为默认值(如0) - 避免
intval()——它对"1abc"返回1,而filter_var会返回false - 枚举类参数(如
status=active)必须白名单:in_array($_GET['status'], ['active', 'inactive'], true)
ORDER BY / LIMIT / 表名字段名没法参数化,只能白名单+解析器
MySQL不支持ORDER BY ?,PostgreSQL虽支持ORDER BY $1但旧驱动常报错;LIMIT ?在部分环境也受限。硬拼是唯一路径,但必须加锁。
- 排序字段白名单:
$allowed = ['title', 'created_at', 'score']; $sort = in_array($_GET['sort'], $allowed) ? $_GET['sort'] : 'created_at'; - 字段名拼接前必须过
parseKey()(ThinkPHP)或mysqli_real_escape_string()(仅限ASCII字母数字,且需确认连接字符集已设为utf8mb4) -
LIMIT值用min(max((int)$_GET['limit'], 10), 100)硬限范围,防止传-1或9999999拖垮数据库
修复后最容易被忽略的三个执行环节
写了prepare不代表安全了。漏洞常死在绑定之后:
-
execute()返回false却没检查,错误被吞掉,参数根本没绑上 - 用
mysqli_query()执行预处理语句(该函数不接受mysqli_stmt对象),实际走的是旧拼接逻辑 - ThinkPHP中
update(['field' => $_POST])若$_POST是数组,会触发parseData()漏洞,必须先is_string()校验每个值
上线前用SELECT * FROM user WHERE username = ?配"admin' --"和"1 OR 1=1"各试一遍,看是否报错或返回异常数据——这才是真实防线。



















