z% K2 i4 ^( M) _1 R x
( P( P6 a* F7 f2 {
, b8 n& o8 @/ z4 J+ t( I" u; M1 Z1 | t2 d- t# s) b
前言
" i8 w) |5 e- M
% A/ @ u" Z/ z6 q6 i3 l4 W- P1 z$ y7 F- ]* n
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
C: i+ F( _/ t$ T/ q5 s
" s2 t% n4 B3 P9 j3 U4 |* h/ x# i; ]* y' M) m9 \
h: i1 Q" r0 j4 q R1 X1 `# l : ]( L) j r I T: _
$ j) ?4 ~) N7 c1 k+ w, C! ]$ @8 _
漏洞分析* x' G) e ^) G, F5 E
- y! t; ~: P( e( l- A$ o. a# z& N1 A! T8 r: G
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:. X/ S6 D6 h2 R
3 s! V. [% K. ~1 v8 c/ I
) C; c' T) K, v! U' ^ - y+ }" r# N# r) _: y
7 q. G+ q0 v5 \! D& X0 `
- ]6 J- W: K% o; U7 T 对应着avatar.inc.php代码如下:
o5 A# T8 O" s* s1 N2 `+ ~ ' _' f# G: J( Q2 Z: u
/ F& Z$ p2 X9 y" t <?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) {8 ` I3 I, U7 M6 u, b; Q+ ^7 u1 C% P
% H5 u0 x9 Z/ N7 O; @
# T! X9 J1 [' K! N case 'upload':
- ]$ s! }$ ?4 _3 Z! j ; o! Y0 g0 r% x! N7 J
+ c4 |* m$ \5 s5 M. }# ]
if(!$_FILES['file']['size']) {6 K* _8 l+ }2 x/ I# v0 f
! w6 W2 |& f- L) {; c j
9 h) M( ~7 E1 G0 t, f if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
! B0 \0 `- R6 s : a' D0 d+ E- n' D+ z& O$ U+ S" V
- R: E! V5 t+ \6 z# { exit('{"error":1,"message":"Error FILE"}');
/ ]; J. S7 y. h" s8 C: t* `2 Y: C
4 U) h) E' p; e! g
. E8 f+ O( x* N; g, @4 t( ^' q }
; ^3 ^1 _% @7 C
3 X2 K0 T0 h8 c& c/ ^' U8 ~ ?8 ^2 O4 F+ o4 [# K$ G
require DT_ROOT.'/include/upload.class.php';
1 ~/ U+ ^& w. C# n4 w
- B) R6 J5 k7 F# K
5 u0 d# Z+ q, Z8 C$ J 6 @4 u5 H! l* N* k: p4 a% ^
! ?2 d, g8 t' S1 R4 H/ ?! O1 ]6 _$ ]& N% k$ \
$ext = file_ext($_FILES['file']['name']);
; Y$ @# `# u! b6 z7 E0 n
6 G- B+ V( G! C7 {& @: v
- h- Z; [" `+ X9 c2 p3 { $name = 'avatar'.$_userid.'.'.$ext;
" f. r( E$ V3 @% N% H. L
9 P8 A7 A6 u1 L" C1 ]
' n1 g0 o# e6 x; h4 d8 N $file = DT_ROOT.'/file/temp/'.$name;) h# a; `6 j1 K4 a( v e# o
1 |/ ~- g) q6 f- X! u8 T, R
6 Q$ z7 Q5 r, x% w) j
' p* D! K7 I- {) p8 M+ N 7 y% i6 }7 u( ]) }" a5 P( G
: Q# ?! I$ j% L
if(is_file($file)) file_del($file);; W5 ~; X" @2 R* Y# L
! p/ ~+ I% s# h$ @: b" C2 Y3 z: E8 f" k
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
! M. V% u D4 I7 D & W" y, c$ ~5 J5 t" S
9 e0 E) N, E2 O. z# z$ G
/ x, t* {: M3 D ; c: P* C Z& B I f( h
: j8 z% F3 R( L% w T! y: A- E h
$upload->adduserid = false;: j: Z/ S+ s5 U6 {9 H
/ K' M" I" \4 f$ a; Q
# V$ l* B: Y& I 9 ?2 L% |0 b0 e2 q& I* }& F
4 r& d4 U0 @0 r' T% f* n" P. ~2 j
$ t+ t$ e7 ?$ L
if($upload->save()) {
/ k2 _3 w, N& w( J% H } | D
/ b9 A0 S* e8 d! l2 V! t
+ `" D' s, ?7 T) m( f0 A, \& N ...
8 T& E& v6 l h" g$ c" `6 e; X8 k # a+ e3 i8 I" H" z8 Q' X/ h0 |1 K+ ? |
' g; O, Y* E9 I# F
} else {1 Z! o6 G& q- e- q
5 J, _- ~$ X$ U9 S3 N9 t3 _) j
% `4 |% \! Y* g# n: e5 i5 Z ...+ V, E5 X3 t% E/ x( z
+ |1 m; B% N# l* T0 y$ V4 f7 H, U$ E- @2 B0 j, V
}
5 o, d; Z. L1 ^' g2 \+ Z& W & n8 ]9 L9 w7 ]% @2 r9 h% R6 h. I7 l
4 d( M* H5 N/ p
break;% C3 l* x' `' s) E
2 u' g& _/ }* y' v! S$ P& J9 {! k5 A) L6 F6 j
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。, d% \: Q3 c( A& K3 E9 J% r
" M! i, R- }; d0 Y) s6 F7 \
8 Z: W- j, t2 w: ]: N% B upload对象构造函数如下,include/upload.class.php:25:
! m& o# x; L/ T; n, {4 K& L
, b% @1 b |7 ^( n7 [% L
W+ s$ N' F7 @- {, n1 h4 _+ y <?phpclass upload {
; Z2 |5 J% l8 s4 o4 _' W : @9 J: s7 d2 x* M
7 x; w5 K$ i+ w7 { function __construct($_file, $savepath, $savename = '', $fileformat = '') {
, {8 N- \# |& i8 F 8 J0 m: d6 P" y1 g
' K |- |- u) b1 C# ^ global $DT, $_userid;
g# K: I1 [3 I( p6 u$ ]
: n( `3 D' F w* R4 f C x3 h$ }) N$ b( i6 g% z# g) o
foreach($_file as $file) {
3 g- }8 d* U3 q5 ?% t
% I& e# R% B3 C: ~$ N2 j; E5 S! J7 D
$this->file = $file['tmp_name'];
5 z' H) A J/ b5 K 9 _" w9 A" f0 G, i/ t2 X" c, T
5 D9 ~8 a% Q: l
$this->file_name = $file['name'];
& z+ o& U% ~7 c1 o
) W! R- d1 I( ]/ _+ t1 c! [' q% w# C* s; Y. q, V6 i3 k7 a4 M7 j
$this->file_size = $file['size'];
/ Y1 X4 e2 f; B* x & ^9 F# ?/ a; G( w5 f
" q* e% R C, h; Q $this->file_type = $file['type'];
) r2 _: U2 F) R% {" i, u5 P
) \0 t5 F# R7 ~' I% |# v# t
8 Q) [$ {; a* p3 A. m8 P $this->file_error = $file['error'];
W+ p, U8 d Y 2 Y G" z0 g* T" x
) [; [2 L- j T* y
8 t- L0 r8 {! Q! \/ p
8 J$ V) h# J$ P/ P8 a7 c6 |# [- R* E. e7 G- [2 x
}
4 N! U3 X8 j3 R& D/ U6 h
- f5 |2 J" s8 b- D6 V7 i5 T2 ~, L0 X. y% _
$this->userid = $_userid;
& j& L4 e4 Y) K K5 _# j* ^2 o) x
?! A& j5 ~& b& I
- O, x, m+ j) y. _, X+ R2 H8 ~ $this->ext = file_ext($this->file_name);" i- j: o/ j4 \) h& w
& r& F# i) a' W5 U( [% A9 b" h. k
; w% G% L7 r% O4 z4 z $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ K9 P6 y4 b' a" M7 X( b9 ?
9 _! l; l9 M( q7 B0 G
4 @& I* @! _- o, V $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;* ]* A F+ R, }5 L1 m- j
" V$ w) `) f7 G8 Q/ Z$ x5 R) {: _0 V/ E" A, s8 P" \
$this->savepath = $savepath;
: A Q/ {& o/ P0 h& J
4 Y9 Y- m$ k- i7 s }; n5 g/ P! h5 Y3 T4 K/ o: N4 @
$this->savename = $savename;; P! L# F" `' p( Z( P6 H& ~
' c9 \2 k' R7 ~! x* Q/ m/ B& K0 l5 |& A
}}0 L6 l# n. r* p0 }2 J/ y# U
% D" `$ Q! H$ k- B( T8 M% o3 r' @: H6 L+ m" p5 H
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
, w/ p& u0 x6 ?( \6 Q4 j ! C, S$ }3 B$ [
; R( c0 }) ^: a. H0 w
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 2 [, j0 G) H( k$ o6 d2 w) c
# y- ]; o& ^& I# n
! |+ |9 D! D. V' A$ R$ p$ X $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( R$ S5 M# C3 e. z+ n
' I# ^7 }$ j5 r7 v- X
2 s2 _: M6 D w+ l* p/ z! d 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:6 w* n9 b ?8 m& D% f* R, @
, r* B( k; R C- ]& d, y8 S. n5 Y* ^# b( Z
3 c* D: i" H3 d1 g4 e3 E0 R2 K) u 7 D0 T6 a/ X T) B7 I
3 K: J; k0 E* D7 b" s
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
! J1 |3 @: A6 W# Q2 @3 U5 b ; ], F+ R0 j* X, f- G
" ]' c6 u7 I7 ]( o% d f
<?phpclass upload {
/ ]& F: @" |" l$ `9 U/ r! a0 A
8 L) ]" M: C1 z. c1 j. Y, ?! E$ ?: O& U% N8 D/ @6 s" I* b/ J0 e4 o
function save() {
" X9 Y( h; c) n+ O3 s( J
9 A7 l5 {- N! F9 k( r* a6 j9 L: r9 h! S7 @8 ^' K0 K1 K
include load('include.lang');
( b* L% u, \; K( }4 Z4 O m
! ~1 g- J6 ^( ^* {0 K$ @8 P6 h
: L+ R6 p5 x; {% v e D% x6 @0 S. ^ if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');- J9 F7 e; z2 R# \# [2 _
' v, R* u9 j7 v$ G- p0 J
7 |/ m5 o1 A5 s, ^6 A" x+ Y# [ 2 w; B7 h% O- P+ x j4 U
/ |- `: j5 y4 m( k& y- Q( `9 ^, {# _+ U
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');3 L7 ~; A# p5 A5 D& u w
! P& v) ^& x$ n
4 D9 o' i% o% l+ M, V+ ^ + \4 D0 H' v$ {
+ H. _! z' w/ e& p
. r V1 R# [9 | g
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ N" C4 b# V# `% o4 f+ H% T- W2 W
S; S+ ~ A1 M: c
, R+ ]* z1 e, _" f6 y* e5 P! t e3 J% P
: Q0 E/ h2 j% U
8 }8 M9 j7 q' I8 ~7 Z4 `8 l
0 C, L; B8 k% o
$this->set_savepath($this->savepath);
2 J- ~+ |# d- X
/ g: b: b; B. a$ T$ S0 l0 Z. X
6 l. U2 a V8 K5 G $this->set_savename($this->savename);
" ]+ j* ]" z+ }* U3 w; B8 ] 4 {9 g, B. q6 O! E; e/ \
. X5 F7 |! k( [% @
+ l! i( x* e T/ z! O: ~. ^
4 [; s" q0 S, j' G2 J( k, _; W4 W! a* `# T+ [
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
5 N# T5 e& D. R( Z' r4 Y! C + {8 B I; I+ V9 Z6 K
) \/ L* ?+ Y* l- E: ]5 q5 L) {! o
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);' W9 u7 b6 r, D
4 L$ ?, s" R8 `% O- z- B9 q
* L' g0 [( M' F! ] if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);* V% s$ E3 {$ @- f1 h
: O# A% l9 X1 U& z
^; Y% O% a9 } F
& F* B0 m9 N* l8 [) M1 w
. M- q5 {& x% |4 Z' g
+ [/ u8 p$ w' j3 i: l- W; `9 D; e6 a $this->image = $this->is_image();
; w: ^2 q0 q5 J6 }+ R
( R2 I: a6 V8 {# @6 A p' o; F: V% z. b4 y& k. n _5 T
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
, ^6 [! j+ n& [. U; I0 r $ s4 ]% }) Z* Q
1 g8 m) e3 q. t. s' \
return true;
9 I, V# x: F; | Y Z 5 n# k- C Q* n% _& F. {8 u
8 Z/ ]9 g6 _( a8 ?! j/ W
}}
) O' E" Y6 q6 c & f) f/ p5 C7 M) n r, ?
( H2 D( B8 W% K 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:0 m% y k. H! o C3 ~
: U: z/ E* I" b. @: b" N
& ^: W0 P6 U- \1 I) x$ z/ S2 v <?php
1 N, z2 G1 o% C4 i ! t% U+ C" M8 I2 Q( d
& ^& u7 Z# L6 S1 M( i+ \ function is_allow() {
1 `, b& J, ]& W8 e* i' U+ q8 b$ k - i. k! N# @* A% ~7 Q6 K- R' K/ q
, ~1 `/ K2 \5 I3 D5 w, x if(!$this->fileformat) return false;
$ v$ N/ {; x" z7 \6 N; R & t5 X5 P: y5 C1 ^
! y$ w1 F8 u) j
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;: H+ }7 b! `% G2 U2 h
; u& u8 [. H2 D. h! l
+ |( L( i/ ?: x& u% ~ 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;
0 N, d, H( x8 ^6 R 5 D, L) Y2 W6 e: d% _$ k6 r
! N3 H+ w s. L
return true;& m1 A$ C& N$ Z7 r, v
) `8 d2 U, \& b; k' h2 H; ~
3 e" T+ t; s; l/ P5 `+ l/ n
}& j: ]! ~0 g# @( q: J; h% Y* m7 k* z
' m9 M' f9 ~" K1 B
3 D- @ L z$ a$ h2 p( E% Q7 h' E
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。( b5 q" L/ s2 C+ f3 C$ A% M% P
% @$ M( [5 z! p! B! [
1 U2 ]. y; W3 t0 R% F. a4 s 接着会进行真正的保存。通过$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文件。
4 t Y4 c8 C/ C2 O# |6 r4 Y5 F9 k : ^$ `0 E- v$ x3 ?! s/ V1 R& ?
- g. \ A$ B/ k0 k A 漏洞利用4 _. f5 C" q& c7 T; {
\6 }& u: d; B; P# j' [% v& h" Z, [; l% Q& r' P7 E
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
: N. g5 W* c" I7 G/ Q
1 S0 t0 B" Q( [2 T" s P2 t! h: X( \9 _! j( w6 q
( w9 y$ J0 _8 R- [
* z" v' n8 d- ]' z5 w) ~ c# w% d& \7 `
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
( y) M* N0 W) L5 L" _3 S; K
" [1 [* u+ [* I1 Y0 g J* W
& y( N+ c0 o: _: M9 a2 ` 不过实际利用上会有一定的限制。
, N/ x8 C7 V( N' g2 v
" F: U" H! \1 c" o, M+ I" j5 f8 ^) N+ U
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。. V$ a6 K8 m7 n$ Q; l
+ i+ M; K# s) f, A( E# B. M
' T$ u! M z& m6 W # Y3 C( B0 v3 W- i9 p! s* d
2 X! m+ d6 s- t; I3 X S# e
4 y' }9 N2 K3 `; O) |) S- N3 }' T! D
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
h. o3 A g* K5 m; S
. [4 v$ ]: b3 ^' x i4 e
/ X) b, ~" z3 m9 j* e7 J! T. X 省略...$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]);省略...
% ~9 h7 K/ I3 r t6 z8 N; x3 Z
! x0 Q* |" _& q3 ~" C% I: G8 C( F, [% n4 U* C l
因此要利用成功就需要条件竞争了。
7 V! i' f& f8 E: I. H 5 o) W5 l) n8 W
, _5 [2 j0 x0 g% f9 E 补丁分析, w) v2 G# N6 t9 r3 x& c
) C4 K$ f3 j9 }/ Z4 G5 |
4 _4 M0 L! K, h9 G , |# _) e( U3 \. K6 a! }1 d$ @# l
. l3 L5 z' {$ \3 G
3 r5 L4 Y. N$ a+ m1 g6 h7 H% {) ?/ s
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:, k9 J; w1 O. w; s$ U
1 ` S8 B9 b, X% ^, G
4 z2 {0 y6 z7 n1 ?: P9 G( l function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}) _* Y/ e: p: H! O( U4 x
# g [2 L( O; Q& l9 |& T6 X5 ^5 O# t0 @7 o( E; v
( v% u: k" R, |2 _4 C6 o 8 ^/ o ~+ O7 V+ C2 Q" e/ z
% W% F! L* P, {' q
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
. a6 g- l" q, D: a4 c. s7 s9 _. ~/ [. P 1 n R; K. U) U
$ S3 ]4 `; a6 @ n 在is_allow()中增加对$this->savename的二次检查。
$ W0 d4 g) J$ j3 b
& H M& P/ e1 }0 Y5 [) e3 `# Y8 @+ W4 u7 F& S
最后
% Q1 `* r9 O' R' u U+ d5 ]: q* M, z( ~3 r
# Z) X; b9 t5 }& ?7 b4 h
嘛,祝各位大师傅中秋快乐!: t$ d& u+ s+ x' l/ c/ J
- o9 \! v2 V* J
z9 d; r) m: s6 `8 S1 j - f6 L1 I, N8 E) A2 n% @2 S
: v: z; i" c1 u# z% K% o
|