正确做法是明确参数用途后分类过滤:数字型强制转整型或用filter_var校验;字符串型一律用预处理;枚举型白名单校验;预处理需配合存在性、类型校验及显式绑定类型;htmlspecialchars仅防xss,不防sql注入。

直接用 mysqli_real_escape_string() 是错的
这个函数只对字符串做转义,但无法处理数字型参数、布尔值或 NULL;更关键的是,它依赖当前连接的字符集,如果连接未设置好 SET NAMES utf8mb4,可能被绕过。实际项目中,只要参数类型不明确、没绑定上下文,用它就等于留后门。
正确做法是:先明确参数用途(ID?用户名?状态码?),再选对应过滤方式:
- 数字类(如
id、page):强制转为整型或用filter_var($val, FILTER_VALIDATE_INT) - 字符串类(如
username、search):不用手动拼 SQL,一律走预处理 - 枚举类(如
status=active):白名单校验,in_array($val, ['active', 'draft'], true)
预处理语句不是“加个问号”就完事
很多人写 $stmt = $pdo->prepare("SELECT * FROM user WHERE id = ?"); 就以为安全了,但漏掉关键一步:没检查 $_GET['id'] 是否存在、是否为空、是否为合法数值。预处理只防注入,不防逻辑漏洞。
完整链路要闭环:
立即学习“PHP免费学习笔记(深入)”;
- 用
isset($_GET['id']) && is_numeric($_GET['id'])初筛(或更严的filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT)) - 若校验失败,直接
http_response_code(400); exit;,别返回空结果或默认值 - 绑定时用
$stmt->bindValue(':id', $id, PDO::PARAM_INT),显式声明类型,避免隐式转换出错
htmlspecialchars() 和 SQL 安全无关
这是最常被混淆的一点。该函数只用于输出到 HTML 页面时防止 XSS,对数据库查询毫无防护作用。把 htmlspecialchars($_GET['q']) 塞进 SQL 字符串里,反而可能因编码不一致导致查不到数据,还让人误以为“已过滤”。
记住分工:
- 进数据库前 → 预处理 + 类型校验
- 出数据库后 →
htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8')再输出 - 中间不做任何“混合过滤”,比如一边
addslashes()一边又预处理,纯属自扰
别信“通用过滤函数”
网上流传的所谓 safe_input() 或自动遍历 $_GET 调用 mysql_real_escape_string() 的代码,本质是反模式。它无法区分 id(应转整型)和 content(应进预处理),更没法处理嵌套数组、JSON 参数等现代用法。
真正可持续的做法是:每个接口单独声明参数契约。例如用注释或配置标明:
// GET /api/user?id=int&name=str&sort=enum:asc,desc
然后统一由路由层解析并校验,而不是在每个控制器里重复写 if (!is_int())。
复杂点在于参数来源不止 $_GET,还有 $_POST、$_REQUEST、路径参数、JWT payload —— 安全过滤必须绑定上下文,脱离场景谈“过滤”就是纸上谈兵。











