0 F; Z3 i/ h( S8 f5 z, N4 u6 m9 c
, t+ t) j, s, T' E! M8 Q
$ Z- P( T m( F& J/ y# |. j
2 l( C7 W8 x3 d/ y0 c' I 前言/ {: t# @+ M! I/ Q+ Y
# G2 H* m6 b2 K4 o5 q5 A
7 ^& G7 M! l; G 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
0 P% d' g+ i) z p$ H+ E
# m9 }1 q; T0 Q1 h1 A9 N2 h$ a" e$ R# A+ F3 U7 }- x3 o1 |* l/ A
) W: r8 ~, h7 `: [' Q
& e2 X+ l8 Y0 V3 g- G8 |5 P" O6 u1 L' |" j& c
漏洞分析$ r- s Q) s s/ N: I) d
' @% q* q6 f7 l6 m+ E- b
5 J1 L0 g* I2 F- j8 n
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:- J! b7 {1 ~) s
7 ?; j# f& w8 p
4 v4 j0 A: |3 P. e
: T8 |! G" `$ I: }) q- p1 {) Q ) ~( `& F; M) g z! a4 g6 G
3 ^2 l' u* \2 [3 t 对应着avatar.inc.php代码如下:; _+ n6 n( `6 C, _ J0 o
; U4 s. V7 A/ H* L# N
7 M& W }; v* Z/ v <?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) {
5 r$ d' ?' W0 ]" w
' q: }. a6 R7 ]) O
+ T7 i6 J& W- r+ @" B. U case 'upload':
$ Z6 c$ r1 @* G5 c5 I2 j
/ {: i+ n4 e, D- n* M, n- p7 ]! y# R7 ?2 U
if(!$_FILES['file']['size']) {
" z2 Y. c) `5 _; ]' ]% s \9 c) F, _% ]' D
8 l4 v* D6 D- L/ j! N if($DT_PC) dheader('?action=html&reload='.$DT_TIME);' ~! K- m( g& \5 _
! L6 }7 Z/ ^: ]# v/ t4 F' z# z
# x. i% N" E. z/ ]. d exit('{"error":1,"message":"Error FILE"}');
' {# Q- |2 j4 K- k+ E! r+ H
% c) {# i6 D! ?) e( V
0 e) E& O y/ s0 b" f$ @* t) J }
% g+ B7 D. ], B2 g+ A / w, C4 n/ m8 Y: w) l9 h1 Q( b
- v! p# V' l, j9 U2 I
require DT_ROOT.'/include/upload.class.php';
6 m8 q- g4 k9 R0 h3 g: L
7 L: d. K- {! m6 d2 e0 |* l
- q) d; a7 f( R/ ~ {0 _% a 6 A, Y, J+ u# x. f, r
A/ o# i) a7 {0 w4 x/ v
8 S+ B, z2 G8 }! U6 n
$ext = file_ext($_FILES['file']['name']);" b3 S' Q( l0 ] ?+ J% V
. D6 N' ~' A, K0 t( f P% d% m5 a [7 j
$name = 'avatar'.$_userid.'.'.$ext;$ f: \. X$ M, H' D6 Z3 O
# e# Z/ X+ E& z) `; `- n1 e4 O! W3 K! W& {" R. E6 W! D/ v/ _9 c
$file = DT_ROOT.'/file/temp/'.$name;
8 g; v- Q9 V5 E' `5 j% _6 F1 l% D3 F4 g3 t% i 8 C, y) o; l3 i9 N
6 h. ^3 R5 r5 l9 x
; e7 T; k- v( P8 N1 w! s$ E; X' P% \! M- \
) ^$ c4 _- o2 s7 V; Y# H$ S* F8 G
+ B! X6 b' D5 x, S, \' | if(is_file($file)) file_del($file);
' b8 B4 A; R0 S - p- A* Z. I6 U+ @- d
/ l6 Y# ~# J- {7 B' e. p
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
7 F- M7 J3 J! L& P' b3 c6 l2 ^0 n1 a- \
. s$ y) M( @3 n ?0 O' j& H8 W9 d6 U( Z8 ^" b
( ?1 l4 W* K2 M5 k7 g; n
" o; ~% r% S, A- Z% r; [6 M P3 x5 N! @6 C% F
$upload->adduserid = false;
, s4 w5 P7 e6 m- `9 d$ L 8 @* c* ]: {5 X7 J
0 Q# E, }1 f9 C# L7 Z& N
: K0 ~" Z8 `) v7 `
6 a7 g8 E$ w' y. L' ]3 R2 e% e$ M1 j- h$ u% K/ W
if($upload->save()) {! l# J3 Y* X, e1 o. H* _
7 g' f5 l( T. J/ Y7 V, u( U
3 u* J. Y. ` C7 E5 D- j0 T; D! B
...
) m& Y& o4 f$ Y6 Y& s- v9 ?* q6 P 6 x& \7 {7 d. S: R/ z
" F; h4 Q$ n1 e; l6 _; A+ k$ P0 h
} else {3 g! f4 ~* s' S) C) a- z' H
3 ^( e( \9 S1 D- G! P3 d8 ^9 m7 Z
m% \/ p% G+ H% D" m4 b A ...
& ~6 F6 {# ~) ^- g' t% g- ~ : ^ ~: W2 ~1 k' S
P( U; }$ m4 d/ X% Y$ [: j2 d }
4 ~. ^# G- y: ]) M% I. l, Z
* N1 z3 ]: T; i/ @5 s* l1 C0 f( f) z
break;
$ f: V) L# r7 l& r$ C
# T! u; T! ~9 s- N) T5 F' m$ ?9 N p
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
5 r' N: c, L" z; c, D# S . m% ^; w3 ~) D" y$ R) p. E
" m$ z9 [. f/ T9 M: V. S- q
upload对象构造函数如下,include/upload.class.php:25:+ W4 h8 M/ d8 A! \, B* C& j
) z! w/ a7 B8 J7 J0 X
5 J6 v9 m) _: ~. s Z1 r <?phpclass upload {/ w E& r0 a: N1 Z
! h# F& U9 l: L9 t) A! t: R Y) ~; [
6 S. A$ o C! A) n0 W/ p function __construct($_file, $savepath, $savename = '', $fileformat = '') {0 }6 k! C2 `& a4 u( O
& Y9 c) i5 G: z
+ B$ M- r2 u% t5 y global $DT, $_userid;
8 `* _9 m8 B7 {4 u4 W, v ) i9 O0 n' Z" X# Q- S H2 C, o
/ f! \% c8 ?, w- W7 [) v e* W8 c foreach($_file as $file) {- B( R6 v/ o1 L9 F6 a; ]/ s
6 N/ |# z1 ]( H9 U, e: f* O
5 A- V& X* A! s7 F
$this->file = $file['tmp_name'];" Z2 m- D- o; ~, j$ Z8 E
1 L- ^0 I. v. ^2 R
1 I- M! ~, d- C& y# Q
$this->file_name = $file['name'];
; B3 p; l7 k8 j- {. M7 [
! [8 |8 \, s/ b
% x# N" y4 s) j) t $this->file_size = $file['size'];
; ~# }) B8 x* ~4 _
) n0 R+ v8 ]" t9 P3 I% P
4 O i8 ?& W( | J0 K $this->file_type = $file['type'];
. V: W; G d% M$ t+ g$ g
+ \8 m* Q0 t8 U( U
- G& _' q8 o5 @8 H1 f* F $this->file_error = $file['error']; ]! E& T! w3 k. |+ s( N4 R' U
( y2 v' j0 V' k% f/ f0 @
( i7 Q% W- G6 G7 p
! U- m! U8 Q2 r* i6 r6 ^' B) k
9 s% {+ J- s2 I1 B/ V
7 B9 h3 ^5 |, O: o" s c }
7 f6 O/ I1 V7 g
( S9 [+ P6 q3 f9 o$ j* @# V
; L# O M$ b7 A) M: x* O $this->userid = $_userid;: X8 t# e/ R: T; g
: K# w# X5 w+ W4 N) q, O
- E8 t! M _$ H0 o4 d' g! l# D
$this->ext = file_ext($this->file_name);
2 n5 V- N* V5 _: S/ Q! g9 l
2 \0 l, h7 M+ X$ @, ~) Q9 q6 h& w1 h
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
. l5 o* d6 O/ Y2 I* \* R3 J . Z) c9 x; Z! `8 s$ `6 U
1 E# _; Z, u) q! z8 v* F $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;9 Q; x. _6 I1 n1 e( E! T
+ G' t% X' F" F% ~- j3 e
, B( l- d3 q4 C
$this->savepath = $savepath;+ s& ?+ W6 v: Z/ J9 K
4 A- k& i6 ?. }: J( r# ~
% v! H+ {& l4 w5 a! Z $this->savename = $savename; U/ I8 @4 ]' z; S; J4 K
]' W6 z1 E& A2 y1 ] q) k/ I/ I% H8 f6 u4 h) P4 m. H/ k a
}}1 k# a$ {) s% n1 W2 z4 p) r
& @* {4 _$ h# [% Q; C" c; j9 D( u
9 o2 k9 j2 [9 v
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。2 @2 W3 Z; a* k
. J+ i c- G+ M$ E/ { m( h
6 o: P+ S1 e. v5 l- P$ k( s0 h 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
; `8 t6 Q- x8 ~6 O% r, Z
( l, ~( R! L* v
& y& Q0 F% P+ u/ s $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$ C& Q7 Y: I, Z6 B
! s( i6 O. C% c/ Z+ g4 h" X$ c. g
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
E& N5 s2 [% A , r- W& O G W4 U+ V1 ~% k
0 \* W, x) J2 A5 ]7 m. y
: q4 ]# @9 @7 l0 ^
' N p) j' n4 F2 O( [, Y, K
- E1 @5 A) L, ~' |5 C 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:/ c2 Q# Y- [9 O# Y Q+ N! q2 Y
% r; `% `. l) J8 m- R
# ?: e, ~% C% Z& m <?phpclass upload {' B! q; W1 ]% \1 D) p' a" h e
, R9 j/ s$ Z5 u% M% S. N& I$ [5 x
4 Q3 F( D' i6 Z+ } `$ }! w function save() {+ m* y+ x' H1 V
1 I1 L4 f& l7 |
7 T( q1 K$ p. y include load('include.lang');
8 p: }( d! D4 ^+ n6 B
+ c/ d/ ]0 x1 Y% p& V H; ~: u+ T6 P( w4 x5 C- f
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
* q6 k" z3 O4 Z* u* ^2 z , l L* s. J7 V0 j9 L
: O6 ^ ]7 w+ u6 V4 l `! U5 Z. f
0 c1 F4 Q4 r9 S; S- u
, ]8 [' K" j, [% T
/ E# T' {4 Y# ~9 M% g if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');: v4 v& ?! p6 E$ A& D
! o; [) }6 l6 m0 A' f6 Y
2 v# \& s: p) }7 ^# c: N0 j* Q& k
1 o2 B% x7 p% T9 a! k0 Y
' w* u6 O7 q3 Q6 {% u
9 F- K8 ^- i2 F if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);# w9 B& b- o& m, {1 Q6 W
* c) a ~/ ?. o( v" y4 P" N, X+ B' Q, C6 k5 ^
6 f% `7 }- b% F6 |; c
' r" s& [6 q" ?& Z9 i l: |0 f" \ ?
+ s# I- Q* N0 K+ g8 M
$this->set_savepath($this->savepath);" D! B" C8 G* e) F$ b
" o3 a0 Z, u8 c4 C' |
7 C, C: F' O0 O9 }8 V8 c
$this->set_savename($this->savename);
( j6 j2 F, Y% ?& R F ( M# [3 a: {$ m5 w! y9 U
# j/ a% {/ \! {( c$ ~" ]# l' n/ N
2 H! r: {) I# a! w. {5 _8 v7 T
. Z4 u' S$ r; j: G, W" e/ [1 Q
4 a3 q, \2 Z, I$ P! M if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
+ U. Y* v8 V7 A* s( u4 \ , p$ x: f& N c) S" O
+ E. j7 J: L+ `# ?9 j# F if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
+ o T5 M z/ R) n- V1 ~( P
$ V1 X; T7 L; q: u
- W( `) N( \) P, R6 a if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
6 A! w* P: v8 @ & Q3 e& s! q- H
$ {' Q, ?' W O! G9 ~4 t1 w3 e# x
9 n1 \& x& Z. V
8 q. E7 C2 e, j& I* Q) H5 V5 ~, J+ j ]. X
$this->image = $this->is_image();
9 ? B* H2 q; t) S * q0 r; Y2 p: P }
- X u' v1 x8 N3 A' Y# L Z, W
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);9 c3 g& I' ~7 ? `
5 L- Y* h8 `0 @; s
1 p, ~, q" t2 a3 V. Z% f return true;
9 c7 S$ h0 [. M 5 T- b9 x+ V+ F7 P/ A* ~6 y
; q+ K3 q' A4 `4 Y2 g }}
7 x v2 S% I/ ~" R4 S1 T! Z7 c 6 p, t# d3 w, G$ X2 g; ?
& F9 m: }) M# e" r' r. } 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:" Z: X/ e, K [6 w
; q! [) |; b9 S" y( {9 }: U. k+ m9 K7 e7 C' i% D% i+ D
<?php3 a# G3 M1 V/ f+ Q0 ^
. z! l* y% L+ i1 l/ e: Z1 |! e
! @7 ?( y' m5 \1 B* J9 I" \6 k4 E+ s' D& { function is_allow() {
$ M" E: a# \9 a9 y/ \& e& ~ 0 ]$ r; k; \% }: ?/ n7 a4 W* Y+ \
3 Y2 ~, f% q: M) X
if(!$this->fileformat) return false;# s$ e! x% A- K3 D
) s1 C% x* ?/ B5 }3 ^" n
1 L& d5 R& }1 n4 r% ?9 g4 [ if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
2 W# v/ I; X# n* U1 C) P& s* d * _' v, v8 a o
9 c" c6 E& C2 n% Q1 I 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;/ }7 B+ z( M9 M
( B( X G$ S$ i
6 {5 V$ X( U4 X* o9 T9 n& a# ^
return true;
" [* `0 ^# A3 D8 ?5 m5 K! v/ T - z) ~# h5 O6 L2 j7 z8 u R
- _+ o @$ \6 T0 v) N
}
* {/ w+ ~2 E0 B; Z4 g * l/ M1 s% t: w w7 Z
( Y" G2 S+ u4 o3 A O& u, H2 D
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。$ m' G* Z$ z S2 g" q5 V
% L, r/ x( q+ e7 X' \ _5 p
" y! H5 r5 Z) d0 x( D" I7 Z* T1 w
接着会进行真正的保存。通过$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文件。9 Q6 V$ g; Z, i3 N- ]
. t& s4 d% N2 O* c) C# d+ d, ], k2 Y, p
漏洞利用0 u; o# X1 [3 N0 h. T
3 C1 Z8 U4 Y- j9 X( C1 S& y( u) J5 r H
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
: j8 a# ^. y6 [0 p- L 6 A7 y/ S3 E0 m7 a
5 i" p' ^( {/ o, z$ F2 C
; q8 _- J7 g, \% R
3 s9 Q8 p/ n |- X- l |* u0 G# B& ]9 n: T/ o' b
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
) F( k+ I n& I; J+ ]
# p0 |6 @4 }3 z9 G+ c* K5 s9 F7 j& g" d
不过实际利用上会有一定的限制。
' t- G9 J0 |4 C: \1 a6 ~% ] % X9 [& O' H- O X
0 `. [/ S2 X! M _7 j( n& g/ V! @ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。4 Y9 j7 y% q- R6 {8 L p4 p
/ Q0 z9 \2 h9 v
5 E5 `& {9 J8 ] + ]5 _- _7 i* S3 [9 \4 {' e B
5 B5 [3 f( U+ Z3 d4 a, `
* x: w' e: j- n$ T! d
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
3 i8 N8 Z0 K1 ~. i : H3 X$ D5 } u2 b
, d/ c W9 J; _1 c9 x5 Z 省略...$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]);省略...( l/ {! e& M9 @/ z9 K
7 j5 H: `9 B2 K" G4 z2 u1 i
9 x9 `# D' q, B2 @+ A
因此要利用成功就需要条件竞争了。
* f1 R0 b& K! v( V( ^# {# m _; z
4 g8 {# [5 l' w A2 F2 w* f/ x2 P& K: N! M* a- a% e
补丁分析
. Q3 Q- N; G) q) F; V
' O# f8 I) L; U9 g& O" Q
0 P8 z- } H; f- s1 a
$ c9 M! ^1 e) g" C
; E/ w. x( T. J% t
# t7 {1 m0 q: h5 ~" z3 J l' Q7 N 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:% d3 a" `& j3 G O
+ Z. a7 W1 k; d) u" Q& \
4 v1 ~6 C0 S8 F function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}/ R" r: [# Q6 P- D
7 K0 p1 L2 J: v
7 Z: e6 \. K! M/ ~) a" A 8 Z6 D7 J. _4 K' ?5 i
$ V. r9 q6 @& D/ R7 w) J
2 [( h& N" g8 \# m/ f/ @
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
1 j5 Y$ J. b& L4 a3 X' U
# p; d/ y3 {6 A% k d4 v4 I! g' \) U7 A& I$ m# B
在is_allow()中增加对$this->savename的二次检查。2 q* U" J l- d+ m, X6 D
9 ?( D$ L4 T+ d- F# k0 u0 D, t; e) Q4 C
* b5 c( {$ n" I `6 X8 w* S 最后. W/ E6 N& `$ l* T
; J9 B% E1 b9 c( I, Y/ {9 g4 I- K
' s5 E( R5 d) ]$ X 嘛,祝各位大师傅中秋快乐!
/ v- k4 w8 R0 C# B9 G, ~/ a& } + |1 W6 l7 l2 H4 J, A T
8 j9 F* H+ k' L F
1 @$ @ R# g7 T- j0 ] 1 I- T4 F; u& v2 Q( |$ ?7 o
|