参数化查询是防止SQL注入的唯一可靠防线,因其将SQL模板与参数彻底分离,数据库仅把绑定值视为数据而非代码;其他过滤、转义或黑名单均为无效补丁。

直接过滤 SQL 关键字不能防注入
别用 std::string::find 或 replace 去“删掉” SELECT、UNION、OR 1=1 这类词——这既不安全也不可靠。攻击者可以大小写混写(SeLeCt)、加注释绕过(SEL/**/ECT)、用编码(%20UNION%20)或 Unicode 变体(全角空格、零宽字符)逃逸。字符串替换类方案在 SQL 注入防护中属于典型误用。
该用参数化查询,不是字符串清洗
C++ 没有内置的“安全 SQL 字符串拼接”机制。真正有效的做法是:把用户输入作为参数传给预编译语句,由数据库驱动负责转义和类型隔离。例如使用 libpqxx(PostgreSQL)时:
pqxx::prepare::declaration prep{"get_user", "SELECT * FROM users WHERE name = $1 AND age > $2"};
// 后续执行时:
pqxx::result r = txn.exec_prepared("get_user", user_input_name, user_age);
或者用 SQLite3 的 sqlite3_bind_text / sqlite3_bind_int 系列函数绑定参数,而非用 sqlite3_mprintf 拼接。
- 所有用户可控输入(表名、字段名、ORDER BY 值等动态结构)都不能靠“过滤关键词”解决,必须白名单校验或间接映射
- 若必须拼接 SQL(如动态列名),只允许从预定义枚举或
std::map<std::string, std::string>中查出合法值,禁止任何原始输入直入 SQL -
mysql_real_escape_string类函数仅适用于 MySQL C API 且需严格配对使用,现代 C++ 应优先选mysqlx或 ORM 封装
如果真要临时清理(仅限日志/显示等非执行场景)
仅当字符串用于前端展示、审计日志、调试输出等**绝不参与 SQL 执行**的上下文,才可做简单脱敏。此时目标不是“防注入”,而是避免日志污染或信息泄露:
立即学习“C++免费学习笔记(深入)”;
- 用
std::regex_replace替换连续空白为单空格,再移除常见 SQL 注释符号(--、/*...*/)——注意正则需开启std::regex_constants::icase - 对非 ASCII 字符做
std::isprint校验,或统一转成XXXX;HTML 实体(若输出到网页) - 绝对不要把清洗后的字符串喂给
exec()、system()、sqlite3_exec()等执行接口
警惕 ORM 和 Query Builder 的“假安全”
像 sqlpp11、LiteSQL 或手写的 Query Builder,若内部仍用 operator+ 拼接字符串并插入用户输入,就等于没防住。检查其参数绑定是否最终落到 sqlite3_bind_* 或 PQexecParams 等底层安全 API 上。一个典型反例是:
query += "WHERE name = '" + user_input + "'"; // 危险!即使用了 ORM 封装类
只要出现这种拼接,无论前面套了几层模板或宏,都已失效。关键看最终生成的 SQL 字符串里有没有未转义的用户输入。
真正的防护边界在“参数绑定发生的位置”——它必须紧贴数据库驱动调用,中间不能有字符串拼接环节。这点容易被抽象层掩盖,得翻源码确认。


















