web
SQL注入的起因
**SQL注入(SQL Injection,简称 SQLi)**的根本原因可以总结为八个字:数据与代码边界模糊。
当应用程序将用户输入的数据,直接拼接到了用于执行的 SQL 命令中时,数据库就无法分辨哪些是开发者原本写好的“控制指令”,哪些是用户输入的“纯数据”,从而导致用户输入的数据被当作 SQL 代码执行。
以下是导致 SQL 注入的具体核心原因和常见场景:
1. 动态字符串拼接(最直接的元凶)
这是最常见的错误编写方式。开发者直接使用字符串相加或格式化字符串的方式,把用户提交的参数拼接到 SQL 语句中。
- 开发者原本预想的正常代码:
1SELECT * FROM users WHERE username = 'admin' AND password = '123'; - 黑客输入的恶意数据: 在用户名框输入
admin' OR '1'='1 - 实际执行的 SQL 语句:
1SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = '123';原理: 由于
'1'='1'永远成立,数据库会直接跳过密码验证,将用户数据全部泄露。
2. 缺乏严格的输入验证与过滤
系统对用户输入的内容过于“信任”,没有在数据进入数据库之前进行合规性检查。
- 没有转义特殊字符: 未对单引号(
')、双引号(")、分号(;)、注释符(--或#)等 SQL 关键符号进行转义或过滤。 - 未限制数据类型: 例如,程序本需要一个数字类型的
id(如SELECT * FROM articles WHERE id = 1),但因为没有强制检查输入是否为整数,导致攻击者可以输入1 UNION SELECT...拼接恶意查询。
3. 盲目信任“隐藏”的输入源
很多开发者只对表单中的文本输入框(如用户名、密码)做防护,却忽略了其他攻击者可以篡改的输入来源:
- URL 参数(GET 请求): 如
?id=5 - HTTP 请求头: 如
User-Agent、Referer、Cookie等(这些常被用来进行日志记录或数据统计,也可能触发注入)。 - 隐藏表单域或下拉菜单: 攻击者可以通过浏览器开发者工具直接修改前端的 HTML 值再提交。
4. 数据库权限过高(推波助澜)
虽然数据库权限过高不是 SQL 注入的触发起因,但它是导致灾难扩大的关键原因。
- 如果 Web 应用程序连接数据库时,使用的是
root或sa等超级管理员账号,一旦发生 SQL 注入,攻击者不仅能读取当前网站的数据,甚至能跨库查询、读写服务器本地文件(如使用LOAD_FILE()),或者直接执行系统命令控制整台服务器。
核心防御:参数化查询
要彻底根治 SQL 注入,核心方法是参数化查询(Parameterized Queries / Prepared Statements)。
在使用预编译语句时,数据库会先将 SQL 语句的结构编译好(例如:SELECT * FROM users WHERE username = ?)。之后无论用户输入什么内容(哪怕输入了一整段恶意代码),数据库也只会把它当成一个纯字符串字面量填入问号处,而绝不会将其当作指令去执行。
数据库的基本结构
库 A学校、B学校、…
表 1班、2班、…
列 学号、姓名、成绩
数据 101、张三、60分
联合注入
联合注入(UNION-based SQL Injection) 是 SQL 注入中一种非常经典且高效的攻击方式。它的核心在于利用 SQL 中的 UNION 操作符,将攻击者自定义的查询结果“拼接到”原始查询结果中,从而通过原本正常的页面将数据库中的敏感信息直接“回显”出来。
1. 联合注入的前提条件
并非所有的注入点都能使用联合注入,它通常需要满足以下两个硬性条件:
- 页面有回显: 网页上必须有能够显示查询结果的地方(例如:搜索结果页面、个人资料页、文章详情页等)。如果后端执行了查询但页面什么都不显示(盲注),则无法使用此方法。
- 联合查询的列数必须一致:
UNION操作符要求前后两个查询语句具有相同的列数。
2. 联合注入的工作流程
攻击者通常遵循以下三个步骤进行:
第一步:判断列数(Order By)
利用 ORDER BY 来推测原 SQL 语句查询的字段数量。
- 注入:
1' ORDER BY 1#(正常) - 注入:
1' ORDER BY 2#(正常) - 注入:
1' ORDER BY 3#(如果报错,说明查询的列数只有 2 列)
第二步:寻找回显位
确认列数后,通过 UNION SELECT 尝试在页面上定位哪个字段的内容会被展示出来。
- 注入:
1' UNION SELECT 1, 2# - 分析: 如果页面上原本显示“标题”的地方变成了数字“1”,显示“内容”的地方变成了数字“2”,说明第 1 和 第 2 个字段都是可回显的。
第三步:获取敏感信息
确定了回显位后,就可以替换为真实的数据库查询语句,直接“拖取”数据。
- 查询数据库名/版本:
1' UNION SELECT database(), version()# - 查询数据库中的表名:
1' UNION SELECT 1, group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()# - 查询特定表中的数据(如用户名和密码):
1' UNION SELECT username, password FROM users#
3. 原理解析示意
核心逻辑: 数据库将
SELECT column1, column2 FROM original_table WHERE id = 1的结果与SELECT 'hack_user', 'hack_pass' FROM users的结果通过UNION纵向合并,最终将拼接后的完整结果返回给了前端页面。
4. 为什么会有联合注入?(起因总结)
联合注入的本质依然是 “代码与数据边界模糊”,但它进一步利用了 SQL 的高级特性:
- 未校验输入数据的结构: 开发者没有限制用户对 SQL 查询逻辑的改变。
- 数据库信息过于透明: 数据库中存在
information_schema等系统字典表,这使得攻击者可以通过UNION轻松遍历出整个数据库的结构(表名、列名),降低了攻击门槛。 - 程序反馈过于详细: 页面直接输出了查询结果,甚至在发生错误时返回了详细的 SQL 错误信息,为攻击者提供了精准的“路标”。
5. 防御建议
- 依然是参数化查询: 这是防御联合注入最有效的手段。一旦使用了预编译,
UNION关键字将直接被作为普通字符串处理,无法改变 SQL 逻辑结构。 - 权限最小化: 确保 Web 用户仅拥有其必需的权限(例如:只读权限,或仅限特定表的增删改查),禁止其访问系统架构信息。
- 屏蔽错误回显: 在生产环境中关闭数据库报错提示,避免通过错误信息泄露表名、列名或数据库类型。
SQL注入过程
SQL 注入的过程通常遵循一个标准化的逻辑链条。对于初学者或安全研究人员来说,可以将其拆解为五个核心阶段。
SQL 注入的五个攻击阶段
1. 发现注入点 (Detection)
攻击者首先会扫描 Web 应用中的交互入口,尝试通过输入特殊字符触发异常。
- 输入测试: 在 URL 参数、搜索框、登录表单等位置输入
',",),#,--等字符。 - 判断依据: 如果页面出现数据库错误提示、页面显示内容异常、或者响应时间与正常情况不同,则说明该参数可能存在注入点。
2. 判断注入类型 (Fingerprinting)
确认存在注入点后,需要判断注入的类型,以便选择对应的策略。
- 数字型 vs 字符型: 判断后端代码是否使用了引号包裹参数。
- 注入方式:
- 联合注入 (UNION-based): 页面有直接回显。
- 报错注入 (Error-based): 页面不回显数据,但会显示数据库的报错信息。
- 布尔盲注 (Boolean-based): 页面根据查询真假显示不同内容(如“欢迎登录” vs “账号错误”)。
- 时间盲注 (Time-based): 通过注入
SLEEP()函数,观察页面响应延迟来判断逻辑真假。
3. 探测数据库结构 (Enumeration)
利用数据库自带的元数据表(如 MySQL 中的 information_schema)获取权限和结构。
- 获取数据库名:
... UNION SELECT 1, database(), 3-- - 获取表名:
... UNION SELECT 1, group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()-- - 获取列名:
... UNION SELECT 1, group_concat(column_name) FROM information_schema.columns WHERE table_name='users'--
4. 获取敏感数据 (Exfiltration)
当已知表名(如 users)和列名(如 username, password)后,通过拼接查询语句获取具体内容。
- 攻击载荷:
SELECT username, password FROM users - 结果: 页面直接显示出数据库中存放的所有账号和密码。
5. 后渗透与提权 (Post-Exploitation)
根据数据库权限,攻击者可能进一步扩大影响。
- 写文件: 如果拥有
FILE权限且知道服务器物理路径,可使用INTO OUTFILE写入一句话木马。 - 执行系统命令: 如果是 SQL Server 或高权限的 MySQL,可能尝试调用系统存储过程进行提权。
给安全学习者的建议
- 观察响应差异: 在处理盲注(Blind SQLi)时,重点观察 HTTP 响应包的状态码、内容长度 (Content-Length) 或响应时间,这是判断注入是否成功的核心。
- 使用标准工具链: 在练习(如 CTF)或合法测试中,
sqlmap是最常用的自动化工具。它能自动处理上述所有阶段。- 命令示例:
sqlmap -u "http://target.com/page.php?id=1" --dbs
- 命令示例:
- 防御意识: 无论攻击过程多么复杂,最终的防御逻辑永远是 “预编译” (Prepared Statements)。只要将数据和指令彻底隔离开,SQL 注入就失去了执行的根基。