术语表

什么是 SQL injection(SQLi):能用输入改写数据库命令的漏洞

SQL injection(SQLi)是让用户输入改写数据库命令(SQL)含义的漏洞,会导致数据被读取、篡改或删除。图解原理,并讲解占位符(prepared statement)、ORM 与最小权限数据库等防御。

发布于 2026-06-08 更新于 2026-10-06 4 分钟阅读
本页目录

SQL injection 是指在搜索框里输入的文字,成了数据库「命令的一部分」。本文讲解它的原理与可靠的防御方法(不公开攻击手法)。

为什么会发生(原理)

危险的根源在于用字符串拼接来组装 SQL 语句。一旦「把输入直接混进语句里」,输入就会越过「值」的边界,作为「命令」生效。

✗ 字符串拼接(危险)

把输入直接拼进语句 → 输入可能被解释为命令的一部分

✓ 占位符(安全)

值作为「数据」传入 ? / $1 等占位框 → 始终只是值

用字符串拼接混入输入,就会越过「值的边界」、改变命令的含义。而占位符能让值始终只是值。

受害范围是「那个数据库连接能做的一切」。一旦能触及内部数据库,它就和 RCE、SSRF 并列,成为大规模泄露的典型原因。

防御

1

使用占位符(prepared statement)(最重要)

组装 SQL 时不使用字符串拼接。值通过 ? / 具名参数等作为「数据」传入。仅此一项,就能让这一类漏洞几乎消失。

2

交给 ORM/查询构建器处理

多数 ORM 默认使用占位符。越少自己写裸 SQL 就越安全。即便必须写裸 SQL,也务必参数化。

3

把数据库用户设为最小权限

不要给应用用的数据库用户多余的权限(DROP、其他表、管理操作)。即使万一被攻破,也能缩小受害范围。

4

输入校验作为「辅助」

对类型、长度、格式做校验是有效的,但不要把它单独当作防御的主力。它是对占位符的补充。

本站观点:从根本上不再「手工拼裸 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 状态,更新到已修复的版本。减少可从互联网访问的管理画面
两者共同给应用的数据库用户最小权限,只限于它需要的表和列

检查自己环境的方法

不需要输入攻击字符串来测试。代码和设置就能告诉你需要知道的事。

1

找出用字符串组装的 SQL

在代码中搜索 SQL 关键字(SELECT、WHERE 等)与字符串拼接或模板字符串同时出现的行。同时确认没有把输入拼接进裸 SQL 函数($queryRawUnsafe、whereRaw、DB::raw、raw() 等)。

2

写下数据库用户能做什么

确认应用的数据库用户能访问哪些表、能在其中做什么(MySQL 用 SHOW GRANTS,PostgreSQL 用 information_schema.role_table_grants)。去掉它不需要的删除权限,以及对其他应用的表的访问权限。

3

把所用产品与漏洞数据比对

用 CVE / KEV 查询查看所运营产品的已知漏洞,以及它们是否正在被利用。依赖包可以用 osv-scanner 一次性检查。

4

确认 SQL 错误不会显示在画面上

在生产环境中,确认数据库错误信息不会原样显示给用户。详细的错误只保存在日志中。

5

在日志中寻找迹象

检查数据库语法错误是否突然增加,以及 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(根本解决与缓解措施的区分)

接下来阅读

分类编号

CWE 对导致漏洞的弱点类型进行分类,MITRE ATT&CK 对攻击者的技术进行分类。两者均由 MITRE 维护,广泛用于标注漏洞信息和检测规则。

CWE™: Copyright © 2006–2026, The MITRE Corporation. CWE is a trademark of The MITRE Corporation. The MITRE Corporation hereby grants you a non-exclusive, royalty-free license to use CWE for research, development, and commercial purposes. Any copy you make for such purposes is authorized on the condition that you reproduce MITRE’s copyright designation and this license in any such copy. CWE Terms of Use

© 2026 The MITRE Corporation. This work is reproduced and distributed with the permission of The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation. The MITRE Corporation (MITRE) hereby grants you a non-exclusive, royalty-free license to use ATT&CK® for research, development, and commercial purposes. Any copy you make for such purposes is authorized provided that you reproduce MITRE's copyright designation and this license in any such copy. ATT&CK Terms of Use

FAQ

QSQL injection 会造成什么后果?
A

攻击者可以改变数据库查询的含义,读取本不该看到的数据、篡改数据,最糟时甚至能全部删除或绕过身份验证。它是个人信息大规模泄露的典型原因之一。

Q最可靠的防御是什么?
A

就是「用占位符(prepared statement)传值」。组装 SQL 语句时不使用字符串拼接,值始终作为「数据」通过另一条路径传入。这样输入就没有被当作命令解释的余地了。多数 ORM 默认就这么做。

Q对输入做转义就够了吗?
A

手动转义容易遗漏,不推荐。真正有效的还是占位符。此外要把数据库用户设为最小权限,万一被攻破时也能限制损害。

Q用了 ORM 就安全吗?
A

在常规用法下是安全的,因为 ORM 默认用占位符传值。但如果把输入拼接进 ORM 提供的裸 SQL 函数(Prisma 的 $queryRawUnsafe、Laravel 的 whereRaw 和 DB::raw、Django 的 raw 和 extra 等),即使用了 ORM,同样的漏洞也会回来。排序列名这类占位符无法承载的部分也需要注意。

QWAF 能防住 SQL injection 吗?
A

它有助于减轻危害,但不是根本解决办法。日本 IPA 的指南《安全なウェブサイトの作り方》也把根本解决(占位符)和只能减轻危害的缓解措施区分开来。WAF(Web 应用防火墙)属于缓解措施,有些输入会绕过检测。请把它当作代码修好之前的临时手段。

Q怎样知道自己使用的产品有没有 SQL injection?
A

把产品名称和版本与已公开的漏洞(CVE)以及 CISA 的 KEV 清单(已确认被利用的漏洞)进行比对。本站的 CVE / KEV 查询工具可以做到这一点。正如 MOVEit Transfer 和 Metabase 所显示的,所用产品中的 SQL injection 已不止一次导致大规模数据泄露。

Q什么是 prepared statement(预处理语句)?
A

一种先把 SQL 语句的结构(值放在哪里)发送给数据库,之后再单独发送值的机制。放值的位置用 ? 或 $1 等符号标记(占位符)。由于结构先被固定,之后发送的值无论包含什么字符,都不会被当作命令解释。

Q什么是盲注(blind SQL injection)?
A

一种 SQL injection:数据库内容和错误都不显示在画面上,但仍可以根据响应内容或响应时间的差异一点点读出信息。画面上什么都不显示,并不代表网站安全。原因和修复方法与普通 SQL injection 相同:使用占位符,它就不起作用。

Q隐藏错误信息能防止它吗?
A

不能。隐藏错误会让漏洞更难被发现,但正如盲注所示,即使不显示错误也能读出信息。不应把错误显示在画面上,但除此之外,仍需修复所有用字符串拼接组装 SQL 的地方。

Q存储过程安全吗?
A

写法正确的话,效果与占位符相同。但 OWASP 提醒,如果存储过程在内部把输入拼接成 SQL(动态 SQL),同样的问题就会回来。要检查存储过程内部做了什么。