HTML表单是SQL注入入口,但防御关键在于后端切断“输入→拼接→执行”链路;前端校验完全可绕过,唯一有效防线是参数化查询,确保SQL结构与用户数据严格分离。

HTML表单本身不执行SQL,但它是绝大多数SQL注入攻击的入口。防范的关键不是改表单,而是彻底切断「用户输入 → 拼接SQL → 执行」这条链路。
为什么前端校验拦不住SQL注入
很多人在 <input> 上加 pattern、用 JavaScript 做长度或字符限制,甚至弹窗提示“不能输单引号”——这些全无效。攻击者绕过浏览器只需一条 curl 命令或直接用 Postman 发请求,前端逻辑根本不会运行。
常见错误现象:
- 页面显示“用户名格式错误”,但后端日志里仍收到
' OR 1=1 -- - 禁用了表单提交按钮,攻击者仍能用 Burp Suite 重放修改后的请求
后端必须用参数化查询,而不是拼字符串
这是唯一真正有效的防线。无论语言和框架,核心原则是:SQL结构与用户数据必须分离,数据库引擎必须明确知道哪部分是语句、哪部分是值。
立即学习“前端免费学习笔记(深入)”;
错误写法(所有语言都危险):
const query = `SELECT * FROM users WHERE username = '${req.body.username}'`正确写法示例:
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- PHP + PDO:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$username]); - Node.js + mysql2:
connection.execute("SELECT * FROM users WHERE id = ?", [id]); - Python + sqlite3:
cursor.execute("SELECT * FROM logs WHERE level = ?", (level,))
注意:? 或 :name 占位符由驱动层处理,不是简单字符串替换;即使传入 "admin'--",它也只会被当作文本值,不会改变SQL语法结构。
MyBatis 中 #{} 和 ${} 的区别必须分清
很多Java项目用 MyBatis,但开发者常混淆两种占位符,导致半途而废的“伪防护”。
#{} 是安全的参数化方式,MyBatis 会将其转为 JDBC 的 PreparedStatement 参数;${} 是纯字符串替换,等同于手拼SQL,完全不可信。
典型踩坑场景:
- 动态表名/字段名必须用
${}?错。应白名单校验后硬编码,或用枚举映射,绝不能直接插用户输入 -
ORDER BY ${sortField}—— 若sortField来自 URL 参数,攻击者可填id; DROP TABLE users-- - 正确做法:限定可选值为
["id", "name", "created_at"],匹配失败就拒绝请求
额外但容易被忽略的点
参数化查询解决的是“数据作为值”的问题,但它不解决“数据作为结构”的问题。比如排序字段、表名、LIMIT 数量,这些属于SQL语法组成部分,不能靠 ? 占位。
这类情况必须:
- 用白名单严格约束可选值,而非正则过滤
- 对数字类参数(如
limit、offset)强制转为整型并设上限,避免传入10000000000导致慢查询 - 数据库连接不用 root,给应用分配最小权限账号(如只读账号禁止
DROP、UNION)
最危险的不是不会写预处理语句,而是写了 #{} 却在另一个地方偷偷用了 ${} ,还自以为已防护到位。


















