必须强制使用预处理语句并禁用字符串拼接,pdo::attr_emulate_prepares设为false,绑定类型须严格匹配,like通配符在php层拼接,表名/字段名等标识符须白名单校验,输入验证仅作初步过滤。

必须强制使用预处理语句,且禁止任何 SQL 字符串拼接——这是唯一能从根源杜绝 SQL 注入的硬性红线,其他所有措施(过滤、转义、正则校验)都只是辅助,不能替代。
所有数据库操作必须走 prepare() + execute() 流程
不是“推荐用”,而是代码提交前的静态检查项。CI 流水线中应加入规则:一旦检测到 "SELECT * FROM $table"、'WHERE id = ' . $_GET['id']、f"INSERT INTO {table}" 这类字符串拼接模式,直接阻断合并。
-
PDO::ATTR_EMULATE_PREPARES必须设为false,否则prepare()仅在 PHP 层模拟,不经过数据库真实预编译,等于没防 - MySQLi 中
bind_param()的类型字符串(如"is")必须与变量实际类型一致;传intval($id)后绑定"i",不能图省事全用"s" - LIKE 查询中的通配符(
%)必须在 PHP 层拼好再传入占位符,例如$search = "%{$_POST['q']}%"; $stmt->execute([$search]);,绝不能写成WHERE name LIKE ?%
动态表名/字段名必须走白名单硬编码
预处理语句无法参数化标识符(如 ORDER BY ? 或 FROM ? 是语法错误),所以任何涉及表名、列名、排序方向、GROUP BY 字段的动态拼接,都必须查白名单。
- 排序方向只允许
ASC/DESC:$order = in_array($_GET['order'], ['ASC', 'DESC']) ? $_GET['order'] : 'ASC'; - 字段名必须锚定首尾匹配:
preg_match('/^id|name|email|status$/', $_GET['field']),漏掉^或$可被id\0anything绕过 - 禁止从 URL 或表单直接取值拼
INSERT INTO `{$table}`;多租户场景下,表名应由用户身份映射为固定键值,而非自由输入
输入验证只做守门员,不做救火员
验证的作用是快速拒绝明显非法输入(比如邮箱里塞 admin'--),但它不负责“消毒”或“兜底”。一旦攻击者构造出格式合法的恶意数据(如科学计数法 123e4 被 filter_var(..., FILTER_VALIDATE_INT) 放行),验证就彻底失效。
- 数字字段优先用强制类型转换:
(int)$_GET['page']比intval()更可靠,且自动截断非数字后缀 - 字符串长度限制要在数据库字段定义(
VARCHAR(32))和应用层(mb_strlen($s) )双重校验 - 禁用
addslashes()和mysql_escape_string():它们不依赖连接上下文,宽字节、多字节编码场景下极易绕过;仅在无法改预处理的遗留模块中,才用mysqli_real_escape_string($conn, $str)
最易被忽略的是:开发规范里必须明确定义“谁来负责校验、在哪一层校验、校验失败怎么处理”。比如 $_GET['id'] 是 controller 层转成 int 后传给 service,还是 service 层自己做?不写清楚,就会有人在 DAO 层补 is_numeric(),有人在前端加 type="number" 就以为安全了——而这两者都拦不住抓包改请求。











