
本文详解如何在pdo中通过参数化查询安全传递值(而非列名或表名),避免sql注入,并纠正将变量直接拼入sql结构的常见错误。
本文详解如何在pdo中通过参数化查询安全传递值(而非列名或表名),避免sql注入,并纠正将变量直接拼入sql结构的常见错误。
在使用PDO执行SQL操作时,一个关键原则是:只有数据值可以参数化,而SQL结构(如列名、表名、关键字)必须在预编译前确定,不可动态拼接。你提供的代码中存在两个严重问题,需立即修正:
❌ 错误示例分析
$sql = "UPDATE village SET `$res` = ? WHERE `$villageID` = ? AND `$fabrik` = ?"; $stmt->execute([$res, $villageID, $fabrik]);
-
'$res'被当作列名插入(如SETwood= ?),但$res是传入的值(如"wood"),此处却错误地被当作了标识符; - 同样,
$villageID和$fabrik也被错误地用反引号包裹并拼入WHERE条件——它们是值,不是列名; - 更危险的是:若
$res来自用户输入(如表单、URL),直接拼入SQL会导致严重SQL注入漏洞(例如传入`wood`, status='active'; DROP TABLE village; --)。
✅ 正确做法:严格区分「结构」与「数据」
- ✅ 列名(如
wood,stone,population)必须硬编码或通过白名单校验后静态指定; - ✅ 所有用户输入的值(如
$res_value,$villageID,$fabrik_value)必须通过?占位符参数化传递; - ✅ 使用白名单限制可更新的字段,确保安全性与可维护性。
✅ 推荐重构方案(安全 + 可扩展)
public function setVillageRes(int $villageID, int $ownerID, string $resource, int $value, string $fabrikValue): string
{
// 1. 先验证村庄归属(防越权)
$stmt = $this->pdo->prepare("SELECT villageID FROM village WHERE villageID = ? AND ownerID = ?");
$stmt->execute([$villageID, $ownerID]);
if (!$stmt->fetch()) {
return "RE-3001"; // 村庄不存在或非该玩家所有
}
// 2. 白名单校验资源字段(杜绝非法列名)
$allowedResources = ['wood', 'stone', 'iron', 'crop', 'population'];
if (!in_array($resource, $allowedResources)) {
throw new InvalidArgumentException("Invalid resource field: {$resource}");
}
// 3. 安全执行UPDATE:列名硬编码,值全部参数化
$sql = "UPDATE village SET `{$resource}` = ? WHERE villageID = ? AND fabrik = ?";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([$value, $villageID, $fabrikValue]);
return $stmt->rowCount() > 0 ? "SUCCESS" : "UPDATE_FAILED";
}⚠️ 注意事项
-
永远不要用
?占位符替代列名、表名、ORDER BY 子句或任何SQL语法结构; - 若需动态列名,请使用严格白名单 + 字符串拼接(如上例),并禁止任何外部输入绕过校验;
- PDO 的
prepare()不会解析反引号内的内容为变量——它只处理?或:named占位符; - 建议为每个资源字段添加数据库约束(如 CHECK 约束或触发器),进一步保障数据一致性。
通过这种方式,你只需维护一个函数,即可支持任意合法资源字段的更新,既满足“不写多个函数”的需求,又彻底规避SQL注入风险,符合现代Web应用的安全开发规范。
















