SQL injection 是指在搜索框里输入的文字,成了数据库「命令的一部分」。本文讲解它的原理与可靠的防御方法(不公开攻击手法)。
为什么会发生(原理)
危险的根源在于用字符串拼接来组装 SQL 语句。一旦「把输入直接混进语句里」,输入就会越过「值」的边界,作为「命令」生效。
✗ 字符串拼接(危险)
把输入直接拼进语句 → 输入可能被解释为命令的一部分
✓ 占位符(安全)
值作为「数据」传入 ? / $1 等占位框 → 始终只是值
受害范围是「那个数据库连接能做的一切」。一旦能触及内部数据库,它就和 RCE、SSRF 并列,成为大规模泄露的典型原因。
防御
使用占位符(prepared statement)(最重要)
组装 SQL 时不使用字符串拼接。值通过 ? / 具名参数等作为「数据」传入。仅此一项,就能让这一类漏洞几乎消失。
交给 ORM/查询构建器处理
多数 ORM 默认使用占位符。越少自己写裸 SQL 就越安全。即便必须写裸 SQL,也务必参数化。
把数据库用户设为最小权限
不要给应用用的数据库用户多余的权限(DROP、其他表、管理操作)。即使万一被攻破,也能缩小受害范围。
输入校验作为「辅助」
对类型、长度、格式做校验是有效的,但不要把它单独当作防御的主力。它是对占位符的补充。
本站观点:从根本上不再「手工拼裸 SQL」
SQLi 早已为人所知却至今仍在发生,原因在于「图方便,就把输入混进语句里」。本站的立场很明确:不制造把值用字符串拼接放进 SQL 的场景。只要把占位符或 ORM 设为默认,这个漏洞在设计上就能被防住。请不要依赖手动转义。
注意:占位符只能传值
占位符并不能覆盖一切。它能传的只有值,而对**表名、列名、ORDER BY 的方向(升序/降序)**这类「查询的结构」无能为力。
当你想把这部分做成动态的(例如:让用户选择「排序键」「筛选列」),不要把输入直接放进 SQL,而应从事先确定的许可清单里挑出对应的正规名称。「排序列」「过滤列」正是那种容易因「以为占位符已经守住了」而被忽视的典型之处。
输入不只是表单字段
如果以为只有搜索框和登录表单需要注意,就会漏掉一些情况。可能进入 SQL 的值,会从很多途径到来。
- HTTP 请求头(
User-Agent、X-Forwarded-For等)和 Cookie - API 收到的 JSON 中的值,或从文件中读取的值
- 先保存到数据库、之后被另一个处理过程组装进 SQL 的值
最后一种称为二阶 SQL injection(second-order SQL injection)。即使保存时用了占位符,如果之后另一个处理过程用字符串拼接使用这个已存储的值,它依然会生效。不要以为「来自自己的数据库,所以安全」:凡是组装 SQL 的地方,都要使用占位符。
本站关于 X-Forwarded-For 伪造 的解说中介绍了一个案例:探测字符串通过请求头送达,但由于该值是用占位符传递的,SQL injection 没有生效。
与相似术语的区别
名称中带有「injection(注入)」的漏洞有一个共同点:输入被当作另一种语言的命令来解释。区别在于输入最终进入了哪里。
| 术语 | 输入最终进入哪里 | 主要危害 | 根本修复 |
|---|---|---|---|
| SQL injection | 发给数据库的命令(SQL) | 数据被读取、篡改或删除 | 占位符 |
| XSS | 在用户浏览器中运行的 HTML 或 JavaScript | 滥用用户的会话、在页面上显示虚假内容 | 输出时转义和 CSP |
| OS 命令注入 | 由服务器的 shell 执行的命令 | 在服务器上执行任意命令(RCE) | 使用不经过 shell、单独传递参数的 API |
SQL injection 和 XSS 经常被一起提及,但危害落在不同的地方:SQL injection 打击的是服务器上的数据库,XSS 打击的是用户的浏览器。
主要语言中占位符的叫法
每种主要语言都提供了单独传值的方法。可以把下表当作在自己的技术栈中该用哪个功能的参考。
| 语言 / 库 | 要使用的功能 |
|---|---|
| Java(JDBC) | PreparedStatement 以及 setString 等设值方法 |
| PHP(PDO) | 用 prepare 预处理语句,把值传给 execute |
| Python(DB-API) | 把值作为 execute 的第二个参数传入(不要嵌入 SQL 字符串) |
| Node.js(pg 等) | 使用 $1 这样的编号符号,把值作为单独的数组传入 |
| C#(.NET) | 把值添加到命令的 Parameters 中 |
无论哪种语言,一旦用模板字符串或 + 把值嵌入 SQL,这种保护就会失效。
实例:始于产品中 SQL injection 的大规模泄露
SQL injection 早已为人所知,却至今仍在引发重大泄露。近年的案例往往不是始于自己写的代码,而是始于自己使用的产品。
| 案例 | 发生了什么 |
|---|---|
| MOVEit Transfer(2023) | 文件传输产品中的 SQL injection(CVE-2023-34362)在修复发布前就被利用,2,700 多个组织的数据被窃取,其中许多是经由供应商受害(→ MOVEit 大规模数据泄露) |
| Metabase(2026) | 数据分析工具中无需认证的 SQL injection(CVE-2026-72898)在修复前被利用,数家企业公布了客户数据泄露(→ Metabase 漏洞利用与泄露(英文)) |
| WordPress 核心(2026) | 与另一个漏洞组合后,无需认证的 SQL injection 可能导致 RCE(→ CVE-2026-63030 解读(英文)) |
MOVEit 事件之后,美国 CISA 与 FBI 于 2024 年 3 月发布警报,敦促软件厂商消除 SQL injection,并把参数化查询(占位符)列为解决办法。在 MITRE 于 2025 年 12 月发布的「CWE Top 25」中,SQL injection(CWE-89)位列第二。
不同角色该做的事
| 你的角色 | 首先要做的事 |
|---|---|
| 编写 SQL 的人(开发者) | 停止字符串拼接,改用占位符或 ORM。检查裸 SQL 函数的用法 |
| 运营产品或开源软件的人(运维) | 确认所运营产品的 CVE 和 KEV 状态,更新到已修复的版本。减少可从互联网访问的管理画面 |
| 两者共同 | 给应用的数据库用户最小权限,只限于它需要的表和列 |
检查自己环境的方法
不需要输入攻击字符串来测试。代码和设置就能告诉你需要知道的事。
找出用字符串组装的 SQL
在代码中搜索 SQL 关键字(SELECT、WHERE 等)与字符串拼接或模板字符串同时出现的行。同时确认没有把输入拼接进裸 SQL 函数($queryRawUnsafe、whereRaw、DB::raw、raw() 等)。
写下数据库用户能做什么
确认应用的数据库用户能访问哪些表、能在其中做什么(MySQL 用 SHOW GRANTS,PostgreSQL 用 information_schema.role_table_grants)。去掉它不需要的删除权限,以及对其他应用的表的访问权限。
把所用产品与漏洞数据比对
用 CVE / KEV 查询查看所运营产品的已知漏洞,以及它们是否正在被利用。依赖包可以用 osv-scanner 一次性检查。
确认 SQL 错误不会显示在画面上
在生产环境中,确认数据库错误信息不会原样显示给用户。详细的错误只保存在日志中。
在日志中寻找迹象
检查数据库语法错误是否突然增加,以及 WAF 或访问日志中是否有来自同一来源、包含类似 SQL 字符串的一连串请求。如果有,先检查该路径背后的代码。
参考资料(一手来源)
- OWASP, "SQL Injection Prevention Cheat Sheet" — cheatsheetseries.owasp.org(占位符、关于存储过程的注意事项、基于许可清单的输入校验、最小权限、不依赖转义)
- CISA and FBI, "Secure by Design Alert: Eliminating SQL Injection Vulnerabilities in Software"(2024 年 3 月 25 日)— cisa.gov
- MITRE, "2025 CWE Top 25 Most Dangerous Software Weaknesses"(2025 年 12 月)— cwe.mitre.org(CWE-89 位列第二)
- IPA「安全なウェブサイトの作り方」(日语)— ipa.go.jp(根本解决与缓解措施的区分)
接下来阅读
- 术语:什么是 RCE / 什么是 XSS / 什么是 OWASP Top 10
- 解说:X-Forwarded-For 伪造与可信代理设置
- 入门:安全超入门