+ ]. @2 |" l6 r" p Y; E
) h- \. ?/ d: ~0 d% _: \. H* o. d1 G* J Y% O
" B5 U' Z: _% \7 A8 p8 y+ W
前言
3 Z9 Z8 o$ c$ w( q1 a; E4 w3 g9 n& V4 V; |8 b3 W% _% Y* L9 |
( u& E, S/ }% a 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
1 v* O, s. t% T/ a* a+ | 9 U' u+ A0 S" S
. H8 E4 F! X7 k. m
& q7 L" V y; |2 h$ O + V, i" I- c0 i6 C" M, f, o
6 J0 n! c D5 k, S/ ]5 e! d. W. W
漏洞分析) L( J) r* w" B& q" c' l) W
4 o u- ~ U' e9 c
. ~. P0 T0 x/ }, j 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:" G6 L+ g" y: m$ P
, S6 j, O! e3 f
4 C, w9 M; m& s& p7 e
$ j+ O0 v" a& U2 T, M
5 h( @3 O. l6 D& Z. h% a& T, T: f5 p6 m2 u _, U
对应着avatar.inc.php代码如下:3 x2 @6 D: Q4 c% J2 ]
, ]6 `2 C8 K% R( q
1 {6 U$ g( y/ Y4 u0 d7 ~) y <?php defined('IN_DESTOON') or exit('Access Denied');login();require DT_ROOT.'/module/'.$module.'/common.inc.php';require DT_ROOT.'/include/post.func.php';$avatar = useravatar($_userid, 'large', 0, 2);switch($action) {( j d: e& B0 O2 W3 a/ A1 X
! k# x8 ?; r7 u
+ A# U- V/ n! @: M7 M' d( @ case 'upload':' T% ]( x) ~1 l; j: x8 z, q% t( D
2 f6 X, _! W/ n
# T3 w: U/ y+ ?0 y
if(!$_FILES['file']['size']) {
* ]. \7 W8 i8 f) [
6 a/ \3 |: ~' l
" s) ~6 E7 ~8 l if($DT_PC) dheader('?action=html&reload='.$DT_TIME);" Q% p3 o# p0 v9 K) P7 s/ _( ^4 `2 [
, x, ^+ g3 l: D! u/ [6 h) y) C- ^* |0 ?6 d
exit('{"error":1,"message":"Error FILE"}');
/ F: k+ J3 v; _7 x. J$ o5 J* [" Q5 d
- t, C4 P! Z, l$ ^( S6 h( p! y* E) T! F+ V8 s2 W
}0 r; O4 k1 ?; {0 J) s
7 K7 L; S, [# N1 Q
+ H* p: s6 x* G. f. X: w$ D require DT_ROOT.'/include/upload.class.php';
( r1 a* [3 q; V- X/ R 5 A9 d N" w+ u
+ _( W- T" c" a 2 a' b. I7 C4 U. I- v1 {) h
9 g9 x1 m2 P0 _. V
% x$ }$ s! u% k7 P& }& C% x; H $ext = file_ext($_FILES['file']['name']);
, S4 |) S, L2 p, R* N+ q " A! Q4 H) d- }! H( _
2 N5 B# e6 P, m: m- [0 T- @% T $name = 'avatar'.$_userid.'.'.$ext;4 U, m8 ]7 d/ M. o2 h
5 K" R' [# K4 W# y" R
1 _; y# L6 f. @ $file = DT_ROOT.'/file/temp/'.$name;
! h# C9 X& m. e# @( r: C0 ]
& k. @. R0 i: }/ T+ B. K7 n' s2 w- s8 k3 a# l i. U2 U
9 @; o0 D4 |. K$ g' f4 V7 m/ o- u 7 L6 l( ]( ^, g& x
# f* P% G$ h6 }! X( O( y7 X
if(is_file($file)) file_del($file);
4 x% ?4 J' V3 M" }! ^ 2 U6 o2 b4 \2 T4 k7 `& k
8 z" @1 y9 K" }8 |* U$ s $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');9 n/ U6 e( ?/ b2 j) }( n
' d6 B% b7 N5 S+ @$ ]0 W8 |& M6 p u* B1 D2 I$ A* [' y
4 x" z$ f# Q A$ V' ` ' l5 x' Z9 C2 y' @
3 N" [: R9 d! y9 Q
$upload->adduserid = false;4 X: g0 t3 U' {* W9 _: ]- Y: g
, S* r( ?% x3 w. K# e
2 @5 S O$ T: U; N+ B9 B' p
3 t4 {. z, U+ D# P* h
& ]5 b5 x0 i D- y' B
& j- o5 ]" o" T2 u if($upload->save()) {
& c0 z" q$ B" ~
. e( N0 P, l0 P0 x H) ?3 u# J3 {% h
...
# d) R" b# \" a; N
* O5 q$ `+ d, N
8 K2 B. Y+ C- `4 J3 k* K9 R! a0 { } else {
7 z4 y0 v% R, p W6 q! d2 { * z" Z0 K; M+ m0 _
- S a" r: E( p1 r
...
- U3 r" z p7 V) L; v' K p 7 ~9 w: K1 }: y9 ^
7 p7 e6 S" Z) G- F: Y. E: w) K0 x" T }' b" ?- O+ R8 S% d5 @, z
" t6 D/ k( N$ ^8 @& e! s
' W& I% L% Z* f- b/ [6 r break;& e7 I- x0 }) A" u- Y. s. Z! w+ C
! E2 ]$ i! q# m2 U: P: i/ \2 [3 r! E, v( c8 S4 A' |
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
4 }7 T6 G' P& T- t3 X' a ( m6 }6 p3 L" ?
3 h6 r; m$ G; o! f4 c) k
upload对象构造函数如下,include/upload.class.php:25:
+ ^: w$ M1 O2 F! u7 o3 j7 E + H( c; B$ b$ s: Y
6 F C8 l' `& ~, R: `# F/ O! Z <?phpclass upload {9 A" ?! b7 L W7 ~
" X5 f- @/ D- Z
5 i* k* r8 Y1 R: o, @5 J$ Y function __construct($_file, $savepath, $savename = '', $fileformat = '') {
# V! y- b1 r \! Q0 q# S & N2 y' a) _- ~* F! } H) [
! {2 i3 v# [' G. A4 |- j9 [ global $DT, $_userid;
& Z% Q2 h* F' O
4 s& j9 `; x3 V1 E% ^" g8 D; t J/ \5 B/ ]. X1 b8 R
foreach($_file as $file) {7 z# @! k N4 Q7 W4 L% A Y1 P; T+ l
; K( C$ E" J ^7 @: w h5 e
" i! K, p& {( e5 l, Y ?5 H $this->file = $file['tmp_name'];3 ?2 w6 y. v7 x3 i
+ `( s) j, K# H: @- J( j
+ @$ S' D, v& U; O6 F3 M( O $this->file_name = $file['name'];1 z/ ]3 G; F5 U a2 ]$ w7 Y0 y6 K
+ I4 J$ F+ D6 y+ ~
0 }6 f9 }$ c/ A2 h
$this->file_size = $file['size'];. E' [# B) ~7 B2 x. x. z
1 s- F7 o4 n5 b8 v; m, O' @ x
& N& S* d: [' L
$this->file_type = $file['type'];2 x$ P& ~/ s+ N! g L& y
1 X# \, J% R- j; s( `
S+ J) I! Y' l, m6 n $this->file_error = $file['error'];
- ?( N2 B" Q; Y
! V! \3 B" M/ h5 c! S' m# v2 y3 A' [) u( h# C1 L) E. N
: w/ l/ W2 p6 f$ z9 o' ^
$ }% f" x$ }( y3 `& i' l: B* }
3 B# u2 K5 i& K
}
+ ?; W! [& Z; g( X/ N6 ]" n
+ N5 j2 s+ i# M7 F( Y* b3 V* P
}8 x I: a# `+ k/ n $this->userid = $_userid;
! T) L, P& ?" o$ d6 c: T! T; q ; W/ b2 N3 `; Y% V* @
0 x8 u+ j0 f( {( ~ $this->ext = file_ext($this->file_name);9 |8 L9 s+ W. v. H/ _
) w; E. o% Q; r2 {- {
! g& V/ C8 c' l: }; }+ A
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];1 A. H4 D* H$ p! N2 S+ F7 |
9 \0 h3 [! j. E, ]; U
% x. E; k( K4 H/ Y% v6 F7 e $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
' N3 N/ ~. x _) }
8 b0 E# H$ |+ d: o. u& d1 W1 v. {" e1 x3 k5 n, [ P: d0 x$ I, _
$this->savepath = $savepath;$ ]! o9 {+ N4 k7 A1 k; f5 Y5 s
, f0 r. V/ U6 s2 I* a( x
) @* j, E* A6 T r/ T $this->savename = $savename;* Q7 x8 F0 W' m* ~
. L V# a& w9 K+ U
. T2 t! l, x: X$ G
}}/ ^/ e) m+ W2 a9 X
( `: \/ _# ~7 A: [
* l. l8 e% Z& N: @+ p 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。( M+ _4 }! V4 l7 B% E
" r9 ~" A' p% r/ b7 L
( z h, D: Z. `1 l4 y 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * z/ n ]- e" x* p. g# f/ \ B+ B# ?
$ [) \' ^0 ]+ y. s5 e8 l
- @% I) S5 l2 `: K: Z6 I' I1 i $ext = file_ext($_FILES['file']['name']); // `$ext`即为`php` $name = 'avatar'.$_userid.'.'.$ext; // $name 为 'avatar'.$_userid.'.'php'$file = DT_ROOT.'/file/temp/'.$name; // $file 即为 xx/xx/xx/xx.php; U! M7 N7 P5 i4 X
5 n$ @% n2 a4 R" m
: |& R. @" m' g s* @ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
0 u0 O: X0 f8 f" \9 ^8 S4 J' A
( H Q2 I, D' h; X$ Q4 i
( H$ M2 o5 v, a/ Y
0 L6 d% f, p2 v: L, o; K
+ j5 r! S5 ]0 g1 }5 s: s
( r U/ W* m3 Z/ O$ o 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:9 Q, A/ D3 m" Q7 S
$ D& o9 ^" t/ O* G
7 b8 D) |4 Y4 o* n <?phpclass upload {5 O; s5 J) @- p5 a5 \6 h. H
$ h' Y5 l" V0 }, [
3 G9 }0 Z4 p" F' U( b6 j7 C) x$ w function save() {
. m0 G( I8 L | ) Q- g6 I, {8 g% f( y6 [# v
. J5 {$ u- I8 _
include load('include.lang');; p E# }% T9 y
: ~: a% G0 o$ ^$ R
4 T7 k# T) G4 n' a" c8 w: v5 D0 o if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');) \- M( z0 N X+ {4 U& d* [0 q* Z1 s
2 C: _3 @8 e% z3 N9 \3 g$ ?7 _! B7 I. I9 j% {
* c. h. r- t J! m+ u 5 W. F+ o; ^+ x/ y9 G1 B( {
# p2 @( O3 p% \' M. F# r! M
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
# W+ d( q! l) x- l7 a
) y( v* k2 K4 D7 j1 ~8 X: J" I6 [6 g8 @# z
7 J! L+ U9 d& z5 ]( q" C2 J3 w
' e F. W3 G5 }7 f; t
; @4 \$ i! A, b1 @3 l( r l if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
, i0 W- H6 A9 l+ F' ~2 Q 6 n; g E8 |) M$ \! f) @
1 A( T+ q# I2 @ [
7 j2 C8 Z3 k/ j: `& @" p. { - H6 \1 q& v0 e$ j4 m7 Y
6 M$ Z- u" @! f# Y* g3 { $this->set_savepath($this->savepath);
1 y' j q0 \) F( b! w! |6 y- P ' Y+ }: f6 Z9 @8 G/ F
7 P" ?$ Z7 d6 ~5 y9 Y
$this->set_savename($this->savename);
7 V1 C! a5 i1 f" S9 y, T$ R ' d7 k9 j9 o1 S. _
9 |! x: C7 C! g! d ; J- d0 i0 ]: U6 S$ i" n# p8 ]
1 ]( E. y4 z/ M( X$ W
) r5 E1 s" M6 e# c* e if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
7 k2 x: O( m- R0 c4 `
% z' S6 V$ o# _4 j" ^
" S1 M W& o; e3 [9 m* C7 m if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);( S1 L1 q! G1 ^$ c9 r$ e. Q
5 M6 {% Y/ s% X9 m) J' D3 G( u" k5 x7 A% s3 W0 @# K. {3 y' r) Y) N: i
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
/ U2 a" L3 O% s6 o 7 v) ]3 s! Z* u# v! n) V' O! ?
% }" v0 ?% g1 x6 k @, u4 y
. V- O( }% V; d% U( D: ? - T5 W% G- [ B
* p6 ~4 H/ ~# U$ a. | $this->image = $this->is_image();
; Q2 W% @* j, s4 P5 {$ G
" ?; k B" R" p- u: r* _
J4 T# e1 t0 T0 n J) Z if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
( q! l, n& M/ c . R8 W4 H' T2 w
8 U+ @+ W$ s7 V0 p% \; E; t/ c return true;
$ ]* W7 U+ Q5 i8 ?- P 0 t1 X% H, }. Z5 j
2 P3 T1 M1 i/ O# i }}
) ^8 [7 H4 Y. H6 I% H* j9 U ; B; q5 n8 S$ e4 O
+ I, \8 }, U" e; D
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:$ ]' B1 y) V7 X: |8 c
( f2 X1 \' j- \- Q
( R8 c7 [7 W" I! h; Q
<?php8 l6 P9 a. C, T$ F+ k0 ~+ Z( G$ F* r
, G$ M6 T. ]" a/ |4 X7 i( F& r
# ?$ C8 ^* C/ N" t! J2 N
function is_allow() {
8 L8 l+ B; i( g2 } 0 c1 Y. o7 S2 x( `5 W' N
2 F( U) c) k9 z q7 j0 V2 O
if(!$this->fileformat) return false;6 |8 F6 ?! @+ @3 r( B3 p
) }- q7 y$ n" r, u6 c p3 U0 e0 i! G5 }, k5 U5 @
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
1 E" s) q/ {$ l- ]4 n, k
: r1 ~0 S) S3 a% _1 [) N+ k" R9 B' O4 d" W6 r" M- `
if(preg_match("/^(php|phtml|php3|php4|jsp|exe|dll|cer|shtml|shtm|asp|asa|aspx|asax|ashx|cgi|fcgi|pl)$/i", $this->ext)) return false;, v! v" n2 i0 a2 ^
; E2 _- a1 m: c2 T& {9 ^. @6 y: Z3 o6 x7 X- x5 s
return true;& Z) ?$ d2 K4 I5 l/ f& h
" \& x& O, q/ U! o) l7 I; `
7 X" |: h' Q# S- Q9 X: b# m
}
- k# R. J' W5 l. s8 u' @' I
( j* G, o4 [' ~, ~" H F* o" e/ ^, t4 R2 Z' p
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。/ o( R4 t* U& }9 U5 o
- M6 b) ]( _) Z9 a6 O( k9 V8 ]9 r* S' v/ \. T6 f8 {* n. }* Y. C! F2 z
接着会进行真正的保存。通过$this->set_savepath($this->savepath); $this->set_savename($this->savename);设置了$this->saveto,然后通过move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)将file保存到$this->saveto ,注意此时的savepath、savename、saveto均以php为后缀,而$this->file实际指的是第二个jpg文件。' S2 m" W: m6 \! y. K6 ]! v1 m2 `
8 B* M, Z e$ x
. O4 s) [, E, [" S& ~- A* M- }* C$ B 漏洞利用
) G! [6 T) k# V1 I' u2 {4 P0 z& K) F {9 G" E' C7 a: f# T
& F$ j3 B! t( ~. ~! y } 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。5 M2 [+ D' }: r0 m& t; T
9 g U7 Q( F1 q% R% ~: D l
6 b6 e# ~. X/ h % ?, U: Y8 Y) \9 `) i1 b! l
2 _, c0 o: x7 r1 P& K- I' d! G6 i$ Q2 G2 ~" b
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid. R& E( h4 |5 I. k( Y/ L
# m% k' e8 O$ W* w+ `0 T0 W- c7 D: s
2 r4 ]* V/ d# k1 c3 K& Y [ 不过实际利用上会有一定的限制。
7 x9 |# X6 Z% [7 f. M " A7 z" O: v f$ r
3 x; n! B3 J, i: y
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。6 H* M8 q4 t/ y( |
; a. k# @: i" Z. m; ^9 D
1 Y$ L. d6 S( Y 1 i- Y6 O' i. f5 \' v B6 ?
( C7 n1 k% ]% d, g/ S; x3 m! |, Z# k
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:, F @6 i$ e9 J/ o6 N0 w/ g
X* B( X1 x; i) ?/ \# m
9 ^: M) [& F( G; \' y" F3 U 省略...$img = array();$img[1] = $dir.'.jpg';$img[2] = $dir.'x48.jpg';$img[3] = $dir.'x20.jpg';$md5 = md5($_username);$dir = DT_ROOT.'/file/avatar/'.substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/_'.$_username;$img[4] = $dir.'.jpg';$img[5] = $dir.'x48.jpg';$img[6] = $dir.'x20.jpg';file_copy($file, $img[1]);file_copy($file, $img[4]);省略...+ T F* n* W% K) M" a8 F
1 E! D+ P! u& Y6 S- n- S/ |! L5 n
4 G3 Z" u5 P% R9 W8 p+ p3 F
因此要利用成功就需要条件竞争了。8 d/ A6 ]" A2 ]$ F" V, y) c" z: t1 s
$ }* g. \8 B8 B5 W/ f
; \" V; j7 @: }7 A5 k
补丁分析
8 ^, |9 v: q3 f/ ]
: t7 [( A2 ], f4 u7 t9 ~9 G0 f( K. u' U% h
- S1 i$ J# P, Z: z# h/ ] " c0 _4 M' m! r2 }3 p' T
6 z, d' h" B, V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
+ _& |. B x0 e. |: \0 `! a
/ R: {, N0 H, ~3 Z$ p& [2 F
) h& ]8 r% m* A% r function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
. @: E* _; @& o 4 N* K# v" I6 q- r& f* {
$ Q& \# q' {' m6 M% z7 f
6 ^9 c \" \* r4 a
6 @% ~ {. _' O$ m! Q* S/ x z) K1 D2 w# i5 p* k5 \" L2 Q% {
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。+ A% {/ D6 q5 d
% g- T) i7 Y! N0 f0 M
7 \; R% ?& }& X 在is_allow()中增加对$this->savename的二次检查。
% ^2 k4 k4 C: M+ L0 t % h- k2 L! b# k2 W$ W
- o6 |/ O/ A$ r& {8 B! G+ X ~. v. |
最后4 [) ~& I" ^8 S8 i8 d
! a, K. G( ?" ]) h- \) X& q
3 w9 _0 y+ \7 m: r 嘛,祝各位大师傅中秋快乐!
2 C6 v3 }" `+ i+ G; e
7 n9 v y3 Q) W( o" N+ y- R% m0 h8 m, c2 y; |( l/ N, _, \
3 y: k, f% B4 E3 }; [2 s
# ^) Y8 N, T. K2 j4 z, A
|