直接拼接sql字符串会触发sql注入,因用户输入未经处理即嵌入sql,如1 or 1=1或'; drop table...;$wpdb不自动过滤,需用prepare()配合%d(整数)、%s(字符串)、%f(浮点)严格匹配类型,且必须包裹完整sql模板与全部变量,表名列名不可用占位符,须白名单校验,同时prepare()不能替代权限控制等其他安全措施。

为什么直接拼接SQL字符串会触发SQL注入
因为用户输入的数据(比如 $_GET['id'])未经处理就塞进SQL里,攻击者可以输 1 OR 1=1 或 1'; DROP TABLE wp_users; -- 这类内容,让数据库执行非预期语句。WordPress的 $wpdb 不自动过滤变量,它只负责执行你给它的SQL——不管那条SQL是不是被污染过的。
prepare() 的占位符怎么选:%s、%d、%f 到底用哪个
占位符不是随便换的,类型错会导致数据截断、查询失败甚至绕过防护:
-
%d:仅用于整数,会调用(int)强转,123abc变成123,abc123变成0 -
%s:用于字符串,做esc_sql()转义,但不会改变内容长度或结构 -
%f:用于浮点数,经floatval()处理,不常用,多数场景用%d或%s更稳妥
常见错误是把ID字段用 %s ——虽然能跑通,但失去整型校验,给 1' UNION SELECT ... 留了缝隙。
prepare() 必须包裹整个SQL,不能只包变量部分
下面这些写法都是错的:
-
$wpdb->query("SELECT * FROM {$wpdb->prefix}users WHERE ID = " . $wpdb->prepare("%d", $_GET['id']));—— 拼接破坏了预处理上下文 -
$wpdb->prepare("SELECT * FROM {$wpdb->prefix}users WHERE ID = " . $_GET['id']);—— 占位符缺失,等同于没用prepare() -
$wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}users") . " WHERE ID = %d", $_GET['id']);—— 占位符在字符串外,prepare()根本看不到
正确姿势是:SQL模板 + 所有变量,一次性交给 $wpdb->prepare(),例如:$wpdb->prepare("SELECT * FROM {$wpdb->prefix}users WHERE ID = %d AND status = %s", $id, $status)。
复杂查询中表名和字段名不能用占位符
%s 只能代入值,不能代入标识符(如表名、列名、ORDER BY 字段)。否则会被加引号,变成字符串字面量,导致语法错误:
- ❌ 错误:
$wpdb->prepare("SELECT * FROM %s WHERE %s = %s", $table, $column, $value)→ 实际执行SELECT * FROM 'wp_users' WHERE 'ID' = '123' - ✅ 正确:表名/字段名必须白名单校验后硬编码,或用
esc_sql()手动转义(仅限可信来源);值部分仍走%d/%s
比如动态排序字段,得先限定可选项:$order = in_array($_GET['order'], ['ID', 'user_login', 'user_email']) ? $_GET['order'] : 'ID';,再拼进SQL:"ORDER BY $order DESC"。
最易被忽略的一点:prepare() 不解决逻辑漏洞。就算SQL安全,如果权限没校验(比如未登录用户也能查任意用户ID),照样出事。安全链条上,输入过滤、prepare()、权限判断、输出转义,缺一不可。











