String.format或+拼接SQL是高危操作,因数据库驱动将拼接结果视为合法SQL执行,无法防御注入;必须用PreparedStatement等参数化查询,使用户输入仅作为数据而非语法结构处理。

为什么 String.format 或 + 拼接 SQL 就算高危操作
因为数据库驱动不会识别你拼出来的 WHERE name = 'admin' OR '1'='1' 是“用户输入”,它只当这是合法 SQL 字符串执行。参数化查询的防护机制(如 PreparedStatement)在 SQL 解析阶段就固定了语句结构,变量值永远作为数据传入,不参与语法解析。
- 常见错误现象:
java.sql.SQLException: ORA-00917: missing comma或看似正常但查出不该查的数据 - 使用场景:任何含用户可控输入的地方——登录、搜索、导出条件、分页 offset
- 哪怕输入做了
replaceAll("'", "''")或trim(),也拦不住1; DROP TABLE users--这类绕过
Java 里必须用 PreparedStatement 替代 Statement
不是“建议用”,是只要涉及变量就得走 setString()、setLong() 这套 API。JDBC 驱动会把参数值转义后安全注入预编译语句占位符位置。
- 错误写法:
statement.executeQuery("SELECT * FROM user WHERE id = " + userId) - 正确写法:
ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?"); ps.setLong(1, userId); - 注意:
?不能用于表名、列名、排序字段(这些属于 SQL 结构),只能用于值;动态表名需靠白名单校验 + 显式枚举 - MyBatis 用户注意:
${}是字符串替换,等同于拼接;必须改用#{}才触发预编译
PHP 中 mysqli::prepare() 和 PDO::prepare() 的关键区别
两者都支持预编译,但默认行为不同:PDO 默认关闭模拟预处理(PDO::ATTR_EMULATE_PREPARES = false),而 mysqli 在低版本或配置不当可能 fallback 到客户端模拟,失去防护能力。
- PHP 7.4+ 推荐用 PDO,并显式设置:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false) - mysqli 要确认
mysqli_options($conn, MYSQLI_OPT_PREPEND_DRIVER_NAME, true)且服务端协议支持 - 别信
mysql_real_escape_string()—— 它依赖当前字符集,UTF-8mb4 下仍可能被绕过 - 示例:
$stmt = $pdo->prepare("SELECT * FROM log WHERE level = ? AND time > ?"); $stmt->execute(['ERROR', $start]);
Node.js 用 pg 或 mysql2 时,占位符写法不是可选功能
这两个库默认开启参数化,但开发者常误以为 text: "SELECT * FROM t WHERE id = ${id}"(模板字符串)是安全的——其实不是,ES6 模板在 JS 层就完成了拼接,传给驱动的已是完整字符串。
-
pg正确用法:client.query('SELECT * FROM t WHERE id = $1', [id]) -
mysql2正确用法:connection.execute('SELECT * FROM t WHERE id = ?', [id]) - 绝对不要:
client.query(`SELECT * FROM t WHERE id = ${id}`)或query("SELECT * FROM t WHERE id = " + id) - ORM 如 TypeORM、Prisma 默认安全,但
raw()、query()方法仍需人工核对是否带参数化
事情说清了就结束。最常被忽略的是:存储过程里用 EXEC(@sql) 或 EXECUTE IMMEDIATE 同样危险,参数化在 PL/SQL/PL/pgSQL 里要靠 USING 子句,不是加个引号就行。










