30小时教你玩转CTF

web

SQL注入的起因

**SQL注入(SQL Injection,简称 SQLi)**的根本原因可以总结为八个字:数据与代码边界模糊

当应用程序将用户输入的数据,直接拼接到了用于执行的 SQL 命令中时,数据库就无法分辨哪些是开发者原本写好的“控制指令”,哪些是用户输入的“纯数据”,从而导致用户输入的数据被当作 SQL 代码执行。

以下是导致 SQL 注入的具体核心原因和常见场景:

1. 动态字符串拼接(最直接的元凶)

这是最常见的错误编写方式。开发者直接使用字符串相加或格式化字符串的方式,把用户提交的参数拼接到 SQL 语句中。

  • 开发者原本预想的正常代码:
    1
    
    SELECT * FROM users WHERE username = 'admin' AND password = '123';
    
  • 黑客输入的恶意数据: 在用户名框输入 admin' OR '1'='1
  • 实际执行的 SQL 语句:
    1
    
    SELECT * 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-AgentRefererCookie 等(这些常被用来进行日志记录或数据统计,也可能触发注入)。
  • 隐藏表单域或下拉菜单: 攻击者可以通过浏览器开发者工具直接修改前端的 HTML 值再提交。

4. 数据库权限过高(推波助澜)

虽然数据库权限过高不是 SQL 注入的触发起因,但它是导致灾难扩大的关键原因

  • 如果 Web 应用程序连接数据库时,使用的是 rootsa 等超级管理员账号,一旦发生 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 的高级特性:

  1. 未校验输入数据的结构: 开发者没有限制用户对 SQL 查询逻辑的改变。
  2. 数据库信息过于透明: 数据库中存在 information_schema 等系统字典表,这使得攻击者可以通过 UNION 轻松遍历出整个数据库的结构(表名、列名),降低了攻击门槛。
  3. 程序反馈过于详细: 页面直接输出了查询结果,甚至在发生错误时返回了详细的 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 注入就失去了执行的根基。