mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
2524 字
7 分钟
SQL注入学习笔记
2026-11-10

SQL注入学习笔记#

我好菜啊,卧槽。

我们以 MySQL、PostgreSQL、Microsoft SQL Server、Oracle 为主要参考。它们的语法有共性,但也存在差异。

一、sql基本语法#

SELECT#

SELECT column1, column2
FROM table_name
WHERE condition;

含义:

  • SELECT:查询哪些字段。
  • FROM:从哪张表查询。
  • WHERE:查询条件。
  • ;:语句结束符。

假设有一张 users 表:

idusernamepassword
1admin123456
2aliceabc123
3bobqwerty

执行:

SELECT * FROM users WHERE id = 1;

只会返回 admin。

如果条件变成:

SELECT * FROM users WHERE id = 1 OR 1=1;

where后为真,会返回所有。

WHERE#

需要熟悉以下运算符:

运算符含义示例
=等于id=1
!=不等于id!=1
>大于id>1
<小于id<10
AND逻辑与id=1 AND active=1
OR逻辑或id=1 OR active=1
NOT逻辑非NOT active=1
LIKE模糊匹配name LIKE 'a%'
IN集合匹配id IN (1,2,3)
IS NULL判断空值name IS NULL

特别注意 AND 和 OR 的优先级。

例如:

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

逻辑上相当于:

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

因为 AND 的优先级高于 OR。

这意味着 OR 1=1 可能让整个条件恒真。

字符串与数字#

构造payload时要先清楚类型。

数字条件:

SELECT * FROM users WHERE id = 1;

字符串条件:

SELECT * FROM users WHERE username = 'admin';

两者区别在于:

  • 数字通常不需要引号。
  • 字符串通常需要引号。
  • 字符串中的引号需要正确闭合。

假设服务端采用字符串拼接:

String sql = "SELECT * FROM users WHERE username = '" + input + "'";

用户输入:

admin

最终 SQL:

SELECT * FROM users WHERE username = 'admin';

如果输入中包含用于结束字符串的字符,就可能改变后续 SQL 的结构。

这就是所谓的字符串上下文注入。

注释符#

注释符的作用通常很大,因为注入sql后可能后面还有别的条件,引号啥的。

数据库注释方式
MySQL#
MySQL--
PostgreSQL--
SQL Server--
Oracle--

order by排序#

SELECT * FROM users
ORDER BY id ASC;
  • ASC:升序。
  • DESC:降序。

SQLi 中它有一个很重要的用途:判断查询结果有多少列。

例如:

SELECT username, email, age
FROM users
ORDER BY 1;

这里 ORDER BY 1 表示按照查询结果中的第一列排序。

如果改成:

ORDER BY 3;

则按照第三列排序。

在适当的查询上下文中,可以逐渐增加序号:

ORDER BY 1
ORDER BY 2
ORDER BY 3
ORDER BY 4

当序号超过实际查询列数时,数据库可能报错。

UNION联合查询#

这是 SQL 注入必须掌握的核心语法之一。

正常 SQL:

SELECT username FROM users;

联合查询:

SELECT username FROM users
UNION
SELECT name FROM administrators;

UNION 可以把两条查询的结果合并。

但是有两个重要限制:

  1. 两条 SELECT 查询必须具有相同数量的列。
  2. 对应列的数据类型必须兼容。

例如:

SELECT username, age FROM users
UNION
SELECT name, id FROM administrators;

这里两边都是两列。

如果第一条查询是三列,第二条查询只有两列,就无法直接 UNION。

当应用程序把查询结果展示在页面上时,我们有机会让数据库返回其他表的数据。

这是 PortSwigger 中 UNION-based SQLi 的核心原理。

GROUP BY 和 HAVING#

SELECT department, COUNT(*)
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;
  • GROUP BY:分组。
  • COUNT(*):统计记录数量。
  • HAVING:筛选分组后的结果。

注意:

WHERE 在分组前筛选记录,HAVING 在分组后筛选结果。

子查询#

子查询就是 SQL 中嵌套 SQL。

例如:

SELECT username
FROM users
WHERE id = (
SELECT user_id
FROM orders
WHERE order_id = 100
);

内部的 SELECT 先提供一个结果,外部 SELECT 再使用这个结果。

SQLi 中子查询非常重要,因为有些盲注场景无法直接回显数据,只能通过条件判断提取数据。

例如:

SELECT *
FROM users
WHERE username = 'admin'
AND (
SELECT SUBSTRING(password, 1, 1)
FROM users
WHERE username = 'administrator'
) = 'a';

这个例子是在判断 administrator 密码的第一个字符是不是 a。

如果页面出现某种可观察的差异,就可以判断条件是否成立。

LIMIT 和 OFFSET#

MySQL、PostgreSQL 等数据库常见:

SELECT * FROM users
LIMIT 10 OFFSET 20;

意思是跳过前 20 条,返回后面的 10 条。

SQL Server 和 Oracle 在分页语法上有所不同。

它们在 SQLi 中可能涉及结果集控制、数据提取等场景。

二、数据库信息#

SQL 语法是一方面,数据库自身的信息结构也很重要。

例如,你可能已经成功实现 UNION 注入,但不知道:

  • 数据库叫什么?
  • 当前使用哪张表?
  • 有哪些字段?
  • administrator 的密码存在哪里?

这就需要掌握数据库元数据。

information_schema#

MySQL 和 PostgreSQL 等数据库提供了数据库元数据查询机制。

以 MySQL 为例:

查询数据库:

SELECT schema_name
FROM information_schema.schemata;

查询表:

SELECT table_name
FROM information_schema.tables
WHERE table_schema = '目标数据库';

查询字段:

SELECT column_name
FROM information_schema.columns
WHERE table_name = 'users';

三、常用数据库函数#

这些函数在盲注、信息收集和数据提取中非常重要。

函数作用
VERSION()获取数据库版本(MySQL 等)
DATABASE()获取当前数据库(MySQL)
USER()获取当前数据库用户(MySQL)
LENGTH()获取字符串长度
SUBSTRING()截取字符串
ASCII()获取字符编码值
CONCAT()拼接字符串
COUNT()统计记录
SLEEP()MySQL 时间延迟函数

四、注入技巧#

1.经典注入(In-band SQLi)#

特点:数据库查询结果能够通过正常 HTTP 响应观察到。

主要分成两类。

A. 条件注入(Boolean Logic)#

核心思想:改变 WHERE 条件,让查询结果发生变化。

假设原始 SQL:

SELECT * FROM products
WHERE category = 'Gifts'
AND released = 1;

可以通过比较不同逻辑条件的响应,判断输入是否影响 SQL 执行。

例如,测试:

' AND 1=1--

和:

' AND 1=2--

两者的差异:

条件结果
1=1恒真
1=2恒假

如果两个请求的响应明显不同,就说明你可能成功影响了 SQL 查询逻辑。

B. UNION-based SQLi#

核心思想:通过 UNION 将其他 SELECT 查询的结果合并到原查询结果中。

典型流程:

确认存在 SQL 注入
↓
判断原始查询列数
↓
判断哪些列可以显示文本
↓
确定数据库类型
↓
查询数据库元数据
↓
获取目标表数据

重点掌握:

  • UNION SELECT
  • ORDER BY
  • NULL
  • 数据类型兼容
  • information_schema

例如,假设原始查询有三列:

SELECT id, name, price FROM products;

那么联合查询也需要三列:

SELECT id, name, price FROM products
UNION
SELECT 1, 'test', 2;

这里第二条查询的三列分别是数字、字符串、数字。

为什么经常使用 NULL?

UNION SELECT NULL, NULL, NULL

因为 NULL 在很多数据库中可以与不同类型兼容,有助于测试列数。

2. Error-based SQLi(报错注入)#

核心思想:

让数据库产生可观察的错误,并利用错误信息推断数据。

例如:

  • 数据类型转换错误。
  • 数学运算错误。
  • XML 函数错误。
  • 数据库函数错误。

它的价值在于:即使页面不正常展示查询结果,数据库错误也可能泄露信息。

你需要理解:

  1. SQL 语法错误与运行时错误的区别。
  2. 错误信息是否被应用程序返回。
  3. 数据库错误是否包含查询内容或数据。
  4. 不同数据库错误行为的区别。

注意,现代应用程序经常会隐藏数据库错误,所以报错注入不一定可用。

3. Blind SQLi(盲注)#

数据库可能已经执行了注入,但页面不会直接显示查询结果。

我们只能通过其他信号判断。

主要有三种。

A. Boolean-based Blind#

通过页面响应差异判断条件是否成立。

例如:

SELECT * FROM users
WHERE username = 'admin';

假设我们想判断密码第一个字符是否为 a:

SUBSTRING(password, 1, 1) = 'a'

如果页面出现正常响应,可能说明条件成立。

如果页面出现另一种响应,可能说明条件不成立。

实际利用流程:

判断字符串长度
↓
判断第一个字符
↓
判断第二个字符
↓
逐字符提取
↓
还原目标字符串

B. Time-based Blind#

如果页面响应没有明显区别,可以利用数据库执行时间作为侧信道。

以 MySQL 为例:

SELECT IF(1=1, SLEEP(5), 0);

如果条件成立,就会触发延迟。

反过来:

SELECT IF(1=2, SLEEP(5), 0);

不会触发这次延迟。

注意:网络波动、服务器负载和应用程序超时都会影响判断,所以不能只凭一次请求认定成功。

C. OAST-based Blind#

OAST 是 Out-of-band Application Security Testing。

它的思路是:

当页面没有结果、没有错误、也没有明显时间变化时,尝试观察数据库是否能通过外部 DNS 或 HTTP 请求产生可控的带外信号。

需要了解:

  • 带外通道的概念。
  • DNS 查询。
  • HTTP 外部请求。
  • 数据库是否具备相关网络能力。
  • 实验环境的网络限制。

4. Stacked Queries(堆叠查询)#

正常情况下:

SELECT * FROM users;

堆叠查询允许在某些数据库和驱动配置下执行多个 SQL 语句:

SELECT * FROM users;
SELECT * FROM products;

这里需要特别注意:

不是所有数据库、驱动和查询接口都支持堆叠查询。

例如,不同语言的数据库驱动对多语句执行的支持就可能不同。

5. Second-order SQLi(二次注入)#

这个概念非常值得理解。

假设一个网站:

  1. 用户注册时保存了用户名。
  2. 之后管理员进入后台查看用户信息。
  3. 后台把保存的用户名拼接到 SQL 中执行。

此时,用户输入并没有在注册时立即造成注入,而是在后续使用时才触发。

这就是二次注入。

它说明:

输入数据被保存下来,并不意味着它已经安全。

五、payload构造技巧#

技巧解决的问题
字符串闭合结束原有字符串
括号闭合结束原有表达式
逻辑运算改变查询条件
注释截断处理后续 SQL
UNION 查询合并其他查询结果
编码与语法适配处理传输和数据库差异

1. 字符串闭合#

原始 SQL:

WHERE username = 'INPUT'

思考:

  • 输入前面是否有单引号?
  • 后面是否还有单引号?
  • 输入能否结束当前字符串?
  • 是否还有其他语法需要闭合?

2. 括号闭合#

原始 SQL:

WHERE (username = 'INPUT')

这里除了字符串之外,还有一个括号。

因此,你需要考虑 SQL 最终是否仍然满足括号匹配。

3. 注释截断#

原始 SQL:

SELECT * FROM users
WHERE username = 'INPUT'
AND active = 1;

即使你成功闭合字符串,后面仍然存在:

AND active = 1

因此需要考虑后续语句如何处理。通常直接注释掉即可。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

SQL注入学习笔记
https://fatdog.20060113.xyz/posts/sql-injection/
作者
神秘大胖狗
发布于
2026-11-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00