8 [9 s( q. t6 [0 G8 n6 z
5 p1 @8 ?& U2 S6 U v" Z7 s& F( \9 A, }/ A g @! k ~ q
) H% R2 ]) P2 h$ M
前言/ z$ m; O+ Q t
. ^9 H- a( x% ]6 y; d+ I/ \& O5 _, o G) p0 E* N% g
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
: I: C$ b& c9 b , ~7 h8 ~. U4 Y' Z
9 r. r1 w$ Z U( T
/ W' P- C- _' J0 G) h
X5 {, V7 O8 I" E. u8 A. ]% j7 c2 Y: Z4 W6 ^
漏洞分析
2 R; B3 z0 _) H" @1 e
3 p; C! n5 c- E' W; d9 R9 J2 K# g0 _7 g6 F
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
* U& w, @& L- C- v
! u+ N% m; U- }$ J3 N0 ^. R* `) ~3 r/ d* [; |5 s( {, [& p
- p% u+ h" [: h8 `+ W5 }$ g ! N3 u0 C+ Z& B
* |' f+ j: r* f6 [# v" i9 [& t 对应着avatar.inc.php代码如下:, ]2 N+ x# O8 Z2 V
: T& i+ t2 s. h9 u$ f( {
8 ^. h, I& j( c; F" A( {3 Y e
<?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) {
. f4 v& R2 f) d& z* K5 O
; U1 P0 A6 E- M: G! m
* q# ]6 F* Q. b9 C case 'upload':
0 e5 U. C8 m/ t, T3 r5 C/ J/ Y: Z
/ h+ ?% _! c$ O9 M" `3 K1 ]0 W% l3 z7 [* x6 m2 j
if(!$_FILES['file']['size']) {6 ~# U2 Q5 B& x% S, ]4 i# P0 S
9 P4 z7 @& S4 N: T; J9 A; k3 _6 u8 E
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);- I9 M) M, M3 i9 w
: |5 O) `8 B3 `# n% `
, T9 p' N9 |6 O: g j! k: c; O7 B& n
exit('{"error":1,"message":"Error FILE"}');' m4 z" Q4 T/ w9 a Q5 Y7 C
9 c) L0 X" U4 m9 i# w6 s
+ @' f/ {/ [6 I/ x7 N% ] }
3 U4 w0 G& o2 g+ ^
8 M" I# F& W" y0 @
- C2 [- H9 P4 v require DT_ROOT.'/include/upload.class.php';
; F) T& ~; j& V5 X! v2 t8 \
8 W4 Y% `; C3 S) q# u: Q" y9 W7 K2 V2 D+ T
$ ~7 D; b8 L# d
& u1 Q5 F6 J% g+ o8 m8 \/ Y" p v$ ^9 {1 j2 J" w% ^% u! V& h
$ext = file_ext($_FILES['file']['name']);
# x; M1 R7 X# v % i8 u% E* z7 _3 a7 [
) W, h3 M4 l/ o8 h& f. h) }2 j0 Z
$name = 'avatar'.$_userid.'.'.$ext;1 D: ^1 C3 h3 Y4 W" e/ ^# F
$ Z' |; o. C' n6 [4 ^) n6 p! ~% q. J
$file = DT_ROOT.'/file/temp/'.$name;
& z/ T6 @5 t6 H ( Y: T) C$ g6 M S' `' t: e
' P$ G4 @ l# B8 G! S0 y
0 ~/ o( I9 h: ~( L$ F' P & D" U1 @- }) U! v: b
8 ^3 ]6 |) I( \1 a% V! ~+ U
if(is_file($file)) file_del($file);9 ]/ d. S G7 ?* t: ?
; x, z6 Z9 y1 B2 a; b: t
' ~' @# `% {# e7 \6 Z: I; h $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
, d0 g: S1 w6 {! y" y* S
7 G' q& P# a" X$ E/ [# Q4 {& C5 t/ _1 u- }
& q' ^, N6 E, o }+ F+ E+ S' ^: h
" w1 j) H- W; P
, [8 T& j4 s/ \, U1 X- ~0 L7 J2 Z
$upload->adduserid = false;
# z! n- }- F+ Z6 S/ O* R, s* q2 Z
0 J( t+ x! t) \7 ^# K
0 l7 E9 D. s. O' s . P3 Q. U, X0 R& v$ p
* R5 a/ t4 H6 V+ K0 n, K5 d
5 e. r- e* i1 I9 y if($upload->save()) {
+ ]( T9 Q) c0 [1 P1 p) Q& X6 K/ ~) I
" S/ m& E9 r3 p) b/ ]- j8 n8 \6 A# Y2 L) s K
...
6 l8 L. @, w, @, F5 i- ~
" J* Z3 q# d1 p& w4 T0 y
4 J; |5 Z1 a+ }" L% l& r' L: I } else {0 z2 g7 N# i( N
/ ~' x5 ]4 |$ Y9 K" s9 K6 m
1 c$ Z* k$ `. n: {3 j0 T
...: k1 ?$ m8 b7 ~6 g
7 t& O. t+ [; ^5 \# Q( u5 C! B
- o2 F, N, M3 C
}# r! E M, D/ D2 q- [
& s$ e* j6 N. K2 ?9 A6 c1 |7 N E5 b7 n9 _3 @3 u+ z
break;6 V8 T& X8 t3 k2 {
& G+ `5 U8 E L, ^
/ \( }# `& `7 e" ] 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。4 P7 s9 Z6 E4 J9 Y6 g
b a: Y4 p+ I; l* e
: t7 u, h# t* p( P upload对象构造函数如下,include/upload.class.php:25:
' ~$ \! W' c j6 n ! j7 L" M9 M9 v: ?
: E7 h) J' p: t% ~, E
<?phpclass upload {! |8 ? o: G+ x; q- W
# i; J. P; L4 v
% w+ D" A) k( c' ]* n
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
* p9 t7 L ?! l8 X0 ]6 B5 M ) t# h3 s" T8 b y6 E" A/ p$ A" e
7 g# o! O' R9 Q
global $DT, $_userid;
3 a7 c1 D4 ^- B8 }% f2 Z 8 M+ M" d4 f* M, {% d- d. ~
& c; S( c' v2 ]# K# `- p4 s foreach($_file as $file) {
6 t% s" o) l% k5 l0 b) l) t5 Y
. N% y ^2 h3 s2 E, u+ ^
% N* w* {* n9 B$ W $this->file = $file['tmp_name'];$ m% T/ e- w( M! Q
/ G- N# b- |: m% b& S
* m5 u, I5 j2 X: p3 c0 T9 o w ` $this->file_name = $file['name'];
: W: H5 E* H6 E4 c( A 2 Y# Z. b0 a' G8 o. t1 H5 k
# p: ~6 m' p$ w- y4 ~9 t M/ ~3 D" N
$this->file_size = $file['size'];
+ y* v6 ? @" h' L
8 u8 j8 A0 B+ o! \8 ?/ @# c! W! N7 f! U& B1 ]
$this->file_type = $file['type'];3 Y" Q8 l) w0 H* X" W& [ H- |" g
8 [ y2 M% s6 t& C7 u8 A# q+ o9 o
1 z: W; @0 r3 g- X6 t# M $this->file_error = $file['error']; d- K G: H3 C; p: @
1 \) R$ a3 a' `; Z% ^9 |1 L0 D
( H/ p K; T, H, L8 X ! f. U* W, k: V' ^3 n0 M% f
0 [- x) u; C. m/ L: q' n4 q
5 f+ ^: c4 V; B3 u+ a3 V9 R
}
: \. c. S1 \6 N5 \
: j$ d/ p+ a) u! a, d2 `( G3 y& O2 E# Z/ }
$this->userid = $_userid;
! n0 q5 }( R% I$ h. x6 _6 p ! r( s% \+ k8 b% N& o( Q
9 V& F' l0 J" D $this->ext = file_ext($this->file_name);" W; U: Z9 Y1 d* C9 d
7 }% ]* P4 W; F0 M. e
, ^; {: O; l: ~, t( Y8 F
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
: L% h. W# h- A9 n9 x) q
( z( O! Z5 @3 |
" L, I/ i: L! ~ $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
' T2 ] d( a, A9 x- s ! k4 `6 ]" C3 [# A0 t
; B& C7 v$ x8 w6 w( C6 B $this->savepath = $savepath;
3 Y) `3 d, X0 n$ f7 C% d
g$ d; {) B/ |2 ]
9 N6 y! S% P0 U3 e7 j $this->savename = $savename;$ J, q: f6 x- V& [5 T6 Y* j) U3 X
# b5 K' x4 d, i& a5 G- r: q7 D7 D; n! k* ?8 b9 W
}}
" h# J; ]) L. R# Y1 k2 G- Z8 i $ c. T' o- q, D; W7 B+ z! z, D( |4 G
+ S' b# G8 \+ G1 t$ B 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。; h* B7 ]! u7 W
f3 a0 q4 s1 c1 L2 Z# X$ v4 i- l5 n, z
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 + [, U7 u: L9 h- a( P
$ D! \5 I% Q9 ]: n' f R# @5 z$ v% y' D1 U" _2 u" H7 n
$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.php7 r7 E0 k) Z3 E T, d/ U& Z4 f
3 _& ^# f* {! Y2 y( X7 g
% \2 }( a8 T: A1 a7 i4 ]" L! N8 ?0 L 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:" }: d( f" A% `7 @ E) P6 P
' i# S: v* n. T; M( ?" T
7 d/ N; V) U, A O Q
3 U& T7 A* e0 G" p& d/ o' {! n
9 i% ?# I6 t k# ^0 I, r4 E% Q9 N+ p& B
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
6 [1 V$ U9 w0 @" M k
9 c9 Y2 T5 L9 e: r% F9 O# r9 O! c7 `! {. y k
<?phpclass upload {( G, F/ b: q1 Y5 ]& B1 @
$ T1 |: A( c9 c9 ?( G" l. |" v1 ~; E! \* Y, [3 U Q
function save() {
0 _) D' H1 a: z* y6 G1 K3 W0 S- {
$ e" I* z; ^3 R) K4 [
3 ?! \: H& I+ U include load('include.lang');
, P0 \) p) W- M$ {" A* H' k# s
* Y) l; O e& k: `( ?3 z
- o1 h7 n; S$ k' J if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');% o' _; G# U: p! C
" _0 c5 h$ t& w
% C+ P2 r, U# N/ k. f; ^2 j" k
( D+ y1 h. M! d: z" h5 V
, ~" A) z2 J5 p/ h: M s( _" |! e- f8 `$ |; b
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
/ P6 A# e% r! d
: z& L; Z' @$ M, a" L4 K6 h: ^% E- q7 ^ [
0 A8 \% t0 L* j; t/ Z$ R; ]6 N. x. s
5 W3 B5 S' O4 _7 Z: T8 [* ?- t& L
' M y! ?/ g. t3 ]5 g4 Z if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);" w* ^9 v$ n. W7 x9 z# d
: P/ p. ]0 Q% k& h6 `+ t/ g$ u
2 O) h, |, o9 p
. x% M2 @5 l1 {/ M, B" m
0 c# G. F$ @1 D
8 E& | W/ G) K/ T $this->set_savepath($this->savepath);
1 G4 K4 a( E+ |; v4 u- M) _ 0 _0 W' b1 I9 y! b: Z
+ L4 c' k/ U O* G7 o% ?
$this->set_savename($this->savename);% I& b4 P* y( v
% |- a- o/ R3 k6 a0 j& W
4 v ^# F' q+ k! [
9 l7 }, J0 J6 q" f% ?# w5 o1 Q
f0 G! S8 [2 J( q7 J
( X I2 }& ~# E# u- b# J: Q/ N if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);* ?/ n9 c: z9 O0 W% S5 K$ e
& O- F$ ?3 q; X" P. O) A
9 p4 k# Y8 ]- a3 c' P2 R9 X if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) _ I, }+ v: M- c$ S5 I
$ n5 X! Z$ e, K) ^ K0 H
+ k `8 u* M4 h/ w- j. u! ^ if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
( {2 y9 C0 \# w0 ? D8 X
( k$ {8 c6 r3 ~6 U& K9 }/ f1 K" l! r4 j: |5 N1 v
0 C8 x u/ _& y6 @4 w
9 J* \; s) F2 T% t/ F+ m; t
1 M6 d0 c) j" O: y3 \& i$ V
$this->image = $this->is_image();7 i. ?6 g2 A8 L% C5 s1 O! i
" K# Q4 X9 `9 u6 d5 G8 j
+ c% P% n& f, ~) f; v) X4 i' j
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
8 C. c8 h' R Y! k) m % i+ @+ H: ?; B; C4 n
& D( C& P% D7 J. X2 a
return true;
0 [) h( o9 L+ b6 S& @7 d" w
8 g9 I/ j; h$ M. Z
, o0 S0 b9 t$ Q$ o6 C }}. s& |- C7 i( W! N- Q b
+ o( }( d% q) `8 A) P5 Q
! e0 T/ ?$ _( V: E 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
7 q! b1 ]5 c# J% x! E
L9 h% ^( N# N, t7 r; x( R: H$ E- u% f3 p) c3 u9 \
<?php6 L! J2 c4 Y% K+ f5 o4 g8 z1 k! R3 P( m
" {4 c; d1 c6 r4 X7 W
1 ?8 R' j* {. Z2 \' ?- q function is_allow() {
L) p& z% t* d4 L+ Z* n! [0 F9 W/ r) Y
/ O- N& b% Q' ]% i9 b
1 J/ Z3 a5 J& E. _5 l2 C' i if(!$this->fileformat) return false;
# [; b5 C( l3 y! F
$ j. B7 l& U* b6 X% d! N) B6 b3 w6 O- X1 a5 k# f. ]
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
, z8 H0 ?8 f3 t w; j- u- M' x }# Q
, g0 ]' q# M2 K4 t
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;
! h, P1 y( K! A( G& u
$ m) j1 }9 u+ O! m6 K& A/ u6 ]
return true;
1 [8 [0 w+ r) x( s ) K9 d! A2 ~5 N/ t8 i+ k
( L" \% Q" I3 c* y } K$ O3 K# F. Q9 T
" }2 [% n( k2 J" `8 G! {8 A
. O9 q8 ]: V8 z8 Y! y& U 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
4 @6 l1 u" N+ U9 h& b 9 R* t" w; Y, V% X/ d2 @
) |& [3 d+ w8 {+ b! f
接着会进行真正的保存。通过$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文件。
5 s- B- D* V& A; b$ f3 l, J
- o9 ?) m4 I. L* \: G# J5 R( x' x- I1 w! p' O; G# U
漏洞利用$ |# @2 L; {% O% P' I
3 J: S4 h( J- N- C9 v0 i' B$ @! e! I f+ f) X/ `
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
/ y1 @% r0 T: C% v7 ]
& p6 @, T7 { \8 q
& q# W! z' n! K- ]" e
1 Z7 { y- v# f% T + ?, e/ b# d5 T/ P( `8 E" t
. G6 m: o: M: ?9 [& G 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid# [( `2 y8 c3 g; O, M C
1 j Q5 X- S" Q4 y3 l8 r. o% k2 I$ @# m' H. J
不过实际利用上会有一定的限制。
5 j6 n; i3 Y4 _! u4 Y * r# z, C( s- [; A7 g$ k7 s+ C$ t
9 A4 G! K. u6 y" f' C8 G7 z6 ~. f 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。; A% N7 F! ^! o2 B8 A( D
3 P3 j1 j1 o6 F: Q% k9 s
4 m3 [; e0 V; N. Y6 z; G
9 ?$ H/ }( U) d( {% V( N
* _$ b4 j* H7 F( T! D
& U( g* j1 `# \: |) A$ _& {# l 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
* e* q# D6 X$ t0 w
; B9 p$ C. P9 d7 A3 h# q- N& T/ G4 A1 E7 Q- k! W) h( f
省略...$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]);省略...
5 M# y2 Q5 q; O; B1 I' N) {
& W9 n8 Q& R' g1 @5 [6 Z. z& p
4 j: C. I# J. F9 |1 w$ o 因此要利用成功就需要条件竞争了。
0 @, \' e) M" l2 i, { 4 r4 ?( _/ Y: M2 m
' b4 X, b5 ], }$ @+ Q; N
补丁分析
( X) y' ^; u2 K0 `. G. j o6 Q& L1 a a1 E1 [
1 ]; V5 t. `. C& U 8 t% J) ~& n3 W
" n) p% L" [/ Q+ J; I0 t0 g
2 I) J6 R. Z2 |9 j 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
0 u" ]: X9 L# W# m0 s 8 J$ d7 m0 w4 }3 @1 E
$ j& f2 K( x* o" U/ Y# e' ~ function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
2 K, Z* U# X; n+ o! b n* b
8 }/ O. }! S" t; N x' c2 y3 R4 C* ^1 `9 u: r% A
0 O, H+ @2 V/ @% { 1 K( x6 G5 S0 u$ G4 |' G4 _% z! x/ [
: l2 u: C8 R& X$ V2 Q2 J: r6 ` 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
4 y' n+ w9 @8 W/ r
; B( A: ~+ V; z& m! K% s' k6 W$ V+ l3 x& I8 Z E" ]% k
在is_allow()中增加对$this->savename的二次检查。
1 T! r4 A+ A1 B1 ?3 S 0 m/ @% g7 \2 r3 P
* V) g# S5 L4 |2 }% ] 最后
4 D* X K0 J' o0 {
9 M; a8 D _+ W+ A9 E/ W- l. ?
( n5 u6 ?9 y; T w& h; G/ m 嘛,祝各位大师傅中秋快乐!
; f* T8 O3 ^( {; n5 T a# b w; E' E; Q# R: ?- E: }
% ?7 I5 W6 p3 q! ` h' ]+ r7 C# T
* l c6 E- p( ?- l* S Y4 D0 \
3 N" s/ E' h9 E+ h. v D3 n
|