拼接SQL字符串是第一道雷,因用户输入被当作代码执行而非数据处理,导致数据库无法区分结构与值;必须用参数化查询强制隔离二者,禁用字符串拼接。

为什么拼接SQL字符串是第一道雷
几乎所有新员工写的第一个登录查询,都会写成类似 SELECT * FROM users WHERE username = '$user' AND password = '$pass'。问题不在于语法错,而在于:只要用户输入里带单引号或 SQL 操作符(比如 ' OR '1'='1),整个查询逻辑就被接管了。
这不是“输入没过滤”的问题,而是设计层面的错误——把数据当代码用。数据库根本分不清哪部分是值、哪部分是结构。
- 哪怕加了
mysqli_real_escape_string(),也挡不住 Unicode 编码绕过、多字节截断等老套路 - 前端 JS 校验纯属心理安慰,后端必须独立验证
- 正则匹配“非法字符”本质是黑名单思维,永远漏掉新操作符(比如 MongoDB 的
$ne、$regex)
参数化查询不是可选项,是唯一合法入口
所有主流语言都支持参数化查询,它强制把“结构”和“数据”物理隔离。数据库驱动在发送语句前就编译好执行计划,用户输入只作为纯数据传入,连解析阶段都进不去。
例如 PHP + MySQLi:
$stmt = $mysqli->prepare("SELECT id, name FROM users WHERE status = ? AND created_at > ?");
$stmt->bind_param("si", $status, $ts);
$stmt->execute();
-
?占位符类型必须明确:s(字符串)、i(整数)、d(浮点)、b(BLOB) - 不要用
bind_param传数组,每个参数单独绑定;批量操作改用INSERT ... VALUES (?, ?), (?, ?)形式 - MyBatis 的
<select>里用#{param}而非${param}——后者照样拼接,等于白防
权限最小化不是运维的事,是开发要填的表
即使某条查询被绕过参数化(比如动态表名、字段名这种真没法参数化的场景),攻击者能干的事也得被卡死。这靠的是数据库账号权限。
- Web 应用连接数据库用的账号,禁止
GRANT ALL PRIVILEGES,只开SELECT, INSERT, UPDATE(且仅限业务表) - 禁用
LOAD_FILE、INTO OUTFILE、EXECUTE等高危权限,MySQL 8.0+ 还要关掉GLOBAL VARIABLES查看权限 - 别让应用账号有
mysql.user表读取权——否则攻击者能直接拖出其他账号密码哈希
错误信息回显=给攻击者发地图
本地开发时打开 display_errors = On 很方便,但上线必须关。一旦报错里出现 MySQL syntax error near '...' 或 MongoDB: unknown operator $where,攻击者立刻知道你用的什么库、字段名怎么拼、甚至索引结构。
- PHP 中统一用
error_reporting(0)+ 自定义错误日志,绝不输出到页面 - Node.js 的 Express 用
app.use((err, req, res, next) => { ... })捕获并返回通用提示 - WAF 或 Nginx 层可配置过滤含
SQL syntax、ORA-、errmsg的响应体,但这是兜底,不能替代代码层处理
真正的难点从来不在“会不会写 prepare”,而在于:所有动态构建查询的地方(包括日志埋点、管理后台的搜索框、导出功能的字段选择)是否都被纳入审查清单。一个漏网的 mysqli_query("SELECT * FROM $table") 就能让前面所有防护失效。

















