防止SQL注入时: 输入验证应放在客户端还是服务端?

作者:袖梨 2026-07-21
客户端验证拦不住SQL注入,因为JavaScript校验可被禁用脚本、curl直发、篡改前端或抓包重放等方式绕过,攻击者无需访问网页即可发送恶意payload;服务端才是唯一可信的入口守门人,必须对所有输入源进行白名单校验、危险字符过滤或转义,并配合参数化查询兜底防护。

必须放在服务端,客户端验证只能当辅助,不能当防线。

为什么客户端验证拦不住SQL注入

浏览器里的 JavaScript 验证可以被轻易绕过:禁用脚本、用 curl 直接发请求、改前端代码、抓包重放——所有这些操作都不经过你的 JS 校验逻辑。攻击者根本不需要打开网页,就能把 ' OR 1=1 -- 这类 payload 发到后端接口。

  • 用户提交的任何数据,只要没经过服务端处理,就等同于“未验证”
  • HTML5type="email"pattern 属性、Vue 的表单规则、ReactonChange 校验,全属于客户端行为,不具安全约束力
  • 哪怕你用 WebAssembly 或加密校验,只要验证逻辑跑在浏览器里,就存在逆向和跳过的可能

服务端验证该怎么做才有效

服务端验证不是“再检查一遍”,而是“唯一可信的入口守门人”。它要覆盖所有输入源:GET 查询参数、POST 表单、JSON 请求体、CookieHTTP 头(比如 X-Forwarded-For)、甚至 gRPCWebSocket 消息。

  • 优先用白名单:比如用户名只允许 [a-zA-Z0-9_]{3,20},ID 强制转成 int 类型
  • 对不可预知格式的字段(如搜索关键词),至少过滤或转义危险字符:'";--/*UNIONSELECT 等(注意:过滤不能替代参数化查询)
  • 验证失败必须立即中断请求,返回通用错误(如 400 Bad Request),绝不能泄露字段名、校验规则或数据库结构

常见错误:把验证逻辑混进 ORM 或框架默认行为里

很多开发者误以为用了 ORM(如 Django ORM、Sequelize、MyBatis)就自动防注入,其实不然。ORM 的安全前提是:你没手动拼接 SQL 字符串。

  • MyBatis 中写 ${username} 是拼接,危险;用 #{username} 才是预编译,安全
  • Djangofilter(name__icontains=request.GET.get('q')) 是安全的,但 raw()extra() 里拼字符串就直接破防
  • SQLAlchemytext() 如果插了用户输入,照样中招;必须用 bindparamexecute(..., {'val': user_input})

真正容易被忽略的点是:验证和参数化查询不是二选一,而是必须同时存在。验证负责提前拦截明显非法输入(比如负数 ID、超长邮箱),参数化查询兜底处理所有漏网之鱼。少一个,防护链就断了。

相关文章

精彩推荐