从零开始SQL注入之四
0x00 废话
虽然上一节说了我们要从Less-1开始逐个分析,但是很多相近的原理完全相同的我就跳过了。怎么可能真的一个一个的来,我没讲的大家就当做练习吧,不然我全讲了也没意思了。
0x01 先来体验一下使用工具的快感
鉴于这个SQLi-labs有日志的功能,我决定先给大家介绍一个杀器,再配合日志功能,这样就可以自己学习了。这款工具就是SQLMAP,官网地址:http://sqlmap.org。根据官方的介绍,这是Automatic SQL injection and database takeover tool
,并不是扫描器,这一点很重要!
SQLMAP 不会扫描 注入点,它只是一个自动化的注入工具。也就是说,你需要给他一个确实存在注入点的URL才可以正常工作。当然某些时候也可以将你不确定是否有注入点的URL丢给他,让SQLMAP帮助你判断。这是一个相当牛逼也相当复杂的工具,各种参数选项十分的多,详细的用法 十分建议 阅读官方的文档,我曾经读过一遍,对整个工具的熟练度提升了不少,很多以前认为SQLMAP无法做到的事情其实都可以做到的。
这个工具是基于PYTHON的,所以运行之前请大家安装好python并设置好环境变量。在SQLMAP的根目录下你会看到一个sqlmap.py
,我们执行python sqlmap.py
就可以运行他了。
我们先通过SQLMAP来查看当前使用的是什么数据库,我们输入下面的命令:
python sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" -p id --current-db
解释一下参数:
- -u 参数指定了注入的URL
- -p 参数指定了注入的参数,这里指定了id,也就是通过URL中的id参数注入,遇到多参数的URL时候这个-p就十分有用了。
- --current-db,让SQLMAP列举出当前的数据库名称
- -v 这个参数这里并没有使用到,这个参数会改变输出log的详细程度,一般默认即可,如果是学习或是想查看sqlmap使用的payload,可以使用
-v 3
参数。 - --level 这个很有用咯,我个人理解是提高检测的等级,也就是说level 3会比level 1使用更多的payload进行测试。
之后我们根据SQLMAP的提示一路操作即可,应该比较容易看懂。等待一会儿后我们可以看到如下的界面:
我们需要注意其中的三个部分,第一部分SQLMAP列出了可用的payload,中间第二部分白色灰色字的部分列出了SERVER的部分信息,最后的一部分是我们需要的内容,也就是--current-db
得到的结果,可以看到当前使用的数据库是security
。
现在我们打开Less-1文件夹下的result.txt
文件,看到了什么?
对,那就是刚刚sqlmap尝试过的各种PAYLOAD,我们可以仔细研究这些payload,然后总结出攻击方法。
关于SQLMAP更多用法我这里就不讲了,网上又很多关于SQLMAP的用法,大家可以自行百度。
0x02 显错注入是什么鬼?
我们回顾一下,如果在后面多加一个单引号或者其他什么的符号,让其括号不匹配,从而显示出数据库错误。我们通常将这种显示数据库错误的注入类型称为 显错注入。如果你想装逼,还有个逼格更高的名字更适合你: Error-Based SQL Injection。
对于注入,我们最感兴趣的就是从数据库里提取各种各样的数据了,显错注入的核心原理就是在SQL语句中造成错误,然后将我们感兴趣的内容通过错误显示出来。举个不是很恰当的例子,用户询问数据库,“嘿!把你的当前用户名转成数字类型告诉我!”,数据库突然觉得很蛋疼,因为用户名是字符串啊,这根本就没办法转换。于是笨笨的数据库就进行回复:“嘿!我不能把用户名root转换成数字型!”
看看我们的到了啥?那就是数据库的用户名,显错注入也正是这个原理,通过构造错误的SQL语句,从而将我们想要的信息显示出来。
0x03 从Less-1开始吧
首先我们先来搞出来当前的数据库用户名吧。
来访问这个东西试试:http://127.0.0.1/sqli-labs/Less-1/?id=1' AND (SELECT 5648 FROM(SELECT COUNT(*),CONCAT(0x7171,(MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,50)),0x7171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a) AND '1'='1
这显示的都是什么东西啊?我们来分析吧,不要被这长长的一串payload吓到了,把他分解开来分析还是很简单的。首先1'
是为了闭合前面的一个单引号,最后的AND
是为了闭合后面的单引号,如果不理解的话想想第三节讲的内容。
下面把两个AND之间的内容抽出来:
SELECT 5648 FROM(SELECT COUNT(*),CONCAT(0x7171,(MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,50)),0x7171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a
头和尾的括号就不要了,分析的时候看起来反而不爽,你觉得爽的话就留下吧。
分析的方法有两种,一种是从外向里的分析,另一种是从里向外的分析,我们先从外向里的分析吧。纵观全局,这是个SELECT * FROM xxx
的语句。就是SELECT 5648 FROM(XXXX)a
,a就是个别名吧,可以理解为将XXXX的结果作为一个表a来使用。现在我们提取出里面的东西。
SELECT COUNT(*),CONCAT(0x7171,(MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,50)),0x7171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x
很容易看出来还是个SELECT FROM
语句,从INFORMATION_SCHEMA.CHARACTER_SETS
这个表里去SELECT
,最后还要GROUP BY x
,这个x就是另一个子语句的别名,稍后会讲到。我们可以看到这条语句总共SELECT
了两个个东西,分别是COUNT(*)
,CONCAT(XXX)
,其中CONCAT
出来的东西有个别名叫做x,就是最后GROUP BY
用到的。
下面就是要分析最核心的CONCAT语句了。
CONCAT(0x7171,(MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,50)),0x7171,FLOOR(RAND(0)*2))
CONCAT的作用是连接这几个参数,这几个参数分别是:0x7171
,MID(xxx)
,0x7171
,FLOOR(xxx)
,至于为啥要有0x7171呢,这个参数其实可有可无的,加上的话主要是为了容易分辨,关键的地方在FLOOR,我们稍后分析。核心的部分在MID语句。继续抽出来。
MID((IFNULL(CAST(DATABASE() AS CHAR),0x20)),1,50)
其中MID
的函数作用是为了截取字符串,这里的是从第1位开始,并截取50位,注意这里的字符串是从1开始的。
IFNULL
的作用其实是判断所给的东西是否为NULL
,如果是NULL
的话就返回第二个参数,否则返回第一个参数。这里就是判断CAST(DATABASE() AS CHAR)
是否为NULL
,如果为NULL
就返回第二个参数0x20
。
至于CAST
嘛,把他当做类型转换就好了,现在该来说刚才没讲的FLOOR
部分了。FLOOR(RAND(0)*2)
,这个RAND就是随机函数,没什么好说的。但是为何要在这里莫名的加个RAND呢?还记得这CONCAT
出来东西是x吗?是的,最后的GROUP BY
还用到了。在MYSQL手册里有这样的一段描述:
RAND() in a WHERE clause is re-evaluated every time the WHERE is executed.
You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times.
没错,我们利用的就是RAND
和GROUP BY
的冲突会报错这个特点。根据文档中描述,RAND()
是不可以用在ORDER BY
中的,其实同理,也不可以用在GROUP BY
中的。
现在我们从头来理解一下这个payload,整个payload的核心是利用RAND
与GROUP BY
的冲突报错来实现的,而具体想要读取什么内容则在CONCAT
的MID
中,此部分语句的灵活性很强,对于读取一个数据有很多种不同的实现方法,大家可以自己研究,不一定是按照我这里的payload,这个payload是从SQLMAP的截取出来的,比较经典。
可能有人会问这个FLOOR
函数的问题,FLOOR
函数是为了取整,具体作用是为了取到不确定的可重复数。大家可以试一下,FLOOR(RAND(0)*2)
的结果,我本机测试的时候结果永远是0,但是因为RAND()
又增加了随即性与GROUP BY
冲突,所以会出现Duplicate entry
的错误,当然你*3,*4,*999
都是可以的。
另外,count(*)
其实也是必须的,没有了COUNT(*)
也不会报错。
0x04 讲了好多我特么都记不住了
没错,讲了这么多确实记不住了,所以我建议平时可以收集各种payload记到笔记里,用的时候拿出来稍加修改即可。当你遇到越来越多的情况以后,就会理解的越深入,自然会理解这些复杂的payload。遇到WAF的时候也需要在这个payload的核心基础上进行各种修改达到绕过的目的,在这个过程中就会增加你对SQL注入的理解。
Crawler
Crawler
Crawler
Crawler
Crawler
Crawler