flawopen.com/SQL 注入
SQL 注入
当应用把用户输入直接拼进 SQL 文本来构造查询时,数据库会把其中一部分输入当作命令解析,这就是 SQL 注入。修复方法在所有语言中都一样:用占位符发送查询,并单独传递值。在下方选择你的语言,查看确切的 API 调用、漏洞代码与修复代码,以及能在 CI 中发现问题的规则。
通俗易懂的原理解析 (ELI5)
你递给图书管理员一张条子,上面写着“请调出这位读者的档案:___”,你在空白处填上:“张三。另外把档案室打开,所有卷宗都拿来。”如果管理员把整张条子当成一条指令来读,就会两件事都照做。把用户输入直接拼进 SQL 字符串,拼出来的就是这样一张条子。参数化查询则是一张印好的表格,“姓名”一栏是封好的格子:无论里面写了什么,都只会被当作姓名去查。
核心概念与专有名词
- SQL injection (CWE-89)
- 不可信输入被拼接或格式化进 SQL 文本,其中的引号、关键字和运算符改变了查询结构,而不是被当作一个单独的值。
- Prepared statement and bind parameters
- 查询带着占位符(
?、$1、:name)发送,并在任何值到达之前完成解析;值单独传输,始终被当作数据处理。这是所有驱动和 ORM 中的首要防线。 - Blind SQL injection
- 当页面既不显示查询结果也不显示错误时,攻击者会根据响应内容的差异(布尔盲注)或响应时间(时间盲注)逐位推断数据。看不到输出并不代表没有漏洞。
- Second-order SQL injection
- 某个值先被安全地存储,之后又从你自己的数据库读出并拼进另一条查询。每条查询都必须参数化,包括那些输入来自你自己数据表的查询。
- Identifiers and allowlists
- 占位符只能承载值,不能承载表名、列名或排序方向。这些必须从代码中写死的白名单里选取,绝不能直接取自请求。
攻击执行流程分解
输入到达查询
表单字段、URL 参数、请求头或 API 字段被传给构造 SQL 语句的代码。
输入被拼进 SQL 文本
字符串拼接、插值或格式化直接插入该值。无害的测试值 ' OR '1'='1 会把 WHERE name = '...' 变成一个恒为真的条件。
数据库把它当作语法解析
数据库收到的是一整条字符串,无法分辨哪部分来自用户,于是以应用的权限执行被篡改的语句。
数据被读取、篡改或删除
取决于查询和账户权限,攻击者可以绕过登录、用 UNION 查询导出整张表,或以盲注方式提取数据。2023 年 MOVEit Transfer 数据泄露事件(CVE-2023-34362)正是始于这类漏洞。
各编程语言的 SQL 注入指南
工程与系统安全加固清单
- 所有包含值的查询都必须参数化,包括 ORM 中的原始 SQL 出口,内部输入或数值输入也不例外。
- 表名、列名和排序方向从代码中写死的白名单里选取。
- 在 CI 中运行针对字符串拼接 SQL 的 SAST 规则(Bandit、gosec、Semgrep、CodeQL、Find Security Bugs、Brakeman 或 Psalm),出现新问题即让构建失败。
- 使用最小权限的数据库角色连接:不授予 DDL,不使用超级用户,只允许访问服务需要的表。
- 转义函数和 WAF 规则只能作为额外的防护层;它们会漏掉无引号的上下文和编码技巧。
- 向客户端返回通用错误信息,把数据库错误记录在服务端日志中,确保不泄露查询结构。