, h. j5 D! k! s6 b) Q
! a% }; \. A8 n8 o
3 g. _3 F, _# J
& P) z: m; z b2 j! q 前言! H+ W! N3 q K' \0 j
4 p1 q: }2 e: ~% j& b* C) Z$ x% v) {) _; |6 d6 Z# U
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。- X' X+ d2 U- o9 U5 t
1 f) L7 A2 @! o9 j: q
+ L$ c9 y+ u3 K( O' q% i
3 X. h: P2 n8 n+ m; y V
7 }0 F, p0 K% o3 R& q
7 k: r7 R9 e# r( g* J 漏洞分析+ y4 M+ P' V% q+ J! [
/ ?: K5 L/ G# p" q. c# ~' d
9 m: p" W9 l% V3 u3 c4 [ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:7 M5 V E/ Z# m- g
' ?, A2 q9 O3 S+ y" y' \7 ^. j
+ X5 n3 C, R* w) p9 D ) u) V4 U6 ]3 b: X! w' `0 f
3 ^% s3 t9 J: h& U0 e I' H+ x) Q) C8 J5 d+ m W( `0 z
对应着avatar.inc.php代码如下:
9 k4 \. p6 C2 L0 A( D 5 a' T+ e9 y6 }8 P7 a: |
& S5 Z8 i4 T" f; [+ O <?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) {# M L1 {1 v' W B
% F0 {7 Q& N* I5 k2 ~: q. E. w* H- ]: ]6 F9 k G5 W4 Y! G
case 'upload':
`5 p$ Z3 E3 p! i+ \
% J: X- n {" B$ }
( V' Y; P! I9 \1 @ if(!$_FILES['file']['size']) {
0 y# p; ^, ^ j0 D) B' K+ i$ z1 d
a( z: ^( ]. |0 G
9 E0 G) D, Y4 O9 p* T7 r Z if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
! [' D7 E- l- L- W5 f
+ ?/ r4 U3 P0 \8 C! L! k- k* A1 C
5 I5 J& }' `& k* L& C( {1 w; [% U exit('{"error":1,"message":"Error FILE"}');
$ m' G# u/ V6 G% K * Y. D7 q8 ^; B& U9 T& p
5 ~& h4 B, t! ?( J8 @- \1 a
}
& P4 b" m0 {( f# t6 l* z
4 w$ H J. |/ j4 {8 A. X# v6 S j6 _0 [/ m3 V
require DT_ROOT.'/include/upload.class.php';
- B0 U2 j& v4 X$ w! _9 l: _
O/ z' W2 _2 ~, Y: ~) _* d- a0 v" ~( t$ W9 A( O# t
9 A1 f5 I7 x5 X: @' X& l7 f- S* ^
- Z: t( \% f( m. M
/ V9 w$ N' F; I1 X $ext = file_ext($_FILES['file']['name']);# i5 {6 j+ D4 q; ]( P& g
2 P7 h6 Q& v/ I' ~8 `8 t& ~6 `( Y2 q: T+ Z
$name = 'avatar'.$_userid.'.'.$ext;
- b6 Y+ j5 u+ R: j
# `$ o: A) K( c1 X; V' R" W- r& m$ C/ z8 o' N0 s$ o! B
$file = DT_ROOT.'/file/temp/'.$name;
( j/ k/ U. a4 ]& q ]; [ 6 m# {) a2 c, }: @. V. V) x
* d. ]9 @6 A2 x8 j0 }1 | j9 `7 y 4 t- ~- g$ w- v8 S/ I
" r j. |5 p: r/ N/ Q0 q
; ]5 \( C6 |* o6 h6 A if(is_file($file)) file_del($file);; r' a7 a+ a# Y# S; V' r6 F+ U
5 i r5 J8 o& f7 ]9 U8 \3 R+ m1 y/ d/ h- d; \6 @% K" Q+ b; {! W
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');1 g8 z! u0 \; u+ Z u5 q s
5 D( n9 T! M2 U- K; u$ d0 H4 k
; q5 D- u4 X' N$ ?8 U
- A1 Q* p; B: g" G2 Q
6 D7 B N% A& w5 g* O: s0 f3 `" w3 |4 z1 d
$upload->adduserid = false;0 y1 }" F* }% {+ a3 i
+ r' r# ~- E; @- V, v& i, {
+ J' P% T9 a! Z' H1 M, \
! f% U* |# H" k* V' d
6 c; N3 u |$ w0 E7 W7 [5 U" S6 q' B1 L/ f9 ?6 z9 h! q
if($upload->save()) {
: {1 y* w. u( {2 `; l' `( S
# d' n) [7 W% }1 ]% B! R4 `, X" N) Z4 w+ w! M+ L
...6 s, v3 P0 h7 L9 l d
% @% E, s3 ?0 U i! p5 c4 E$ x* n8 S: m
} else {
/ I2 @: d8 ?6 @3 D |/ u 5 P% k: y+ U1 Y; v9 b& K8 ^; D9 P
. y) [) o; r5 k( W' } ...# [5 n% ^9 t& I# ?3 k
1 v' L5 o+ j( |" p- O& Y' p% b
/ h1 Y9 a0 r2 W( {, X( j( M
}
4 t2 r1 J! C) J5 N0 B! N, k
( I3 [, c9 Q2 g% K
- _8 H! y* J2 r! B; B$ l: S break;0 h5 w; R, u8 J( ]
# `. N; K$ _1 n" q! `
, D, R3 h9 k( D! j& z; @
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。0 V& r1 ~9 l8 R6 l! ?" V( S. e+ Y
1 p8 h) @% F: L5 A4 k; H. m9 {# {% o( j
upload对象构造函数如下,include/upload.class.php:25:6 F# U% P3 W4 e
[' d4 j1 \0 i8 g* N i- x
& v& A& x6 P; D5 ?# C# O <?phpclass upload {
( k% T7 ~; E N1 n3 K2 h/ V
& K/ C: k6 F/ r& O5 y( ]& {$ E6 @2 Q4 V8 [1 [6 L0 c4 w- v7 H
function __construct($_file, $savepath, $savename = '', $fileformat = '') {) h. d+ a' w; h3 Q. I
: H& W5 g% _; t/ ? U4 v: H; W; {
' x" H; L% q: M+ \ global $DT, $_userid;( W* W; j" I4 U
1 L3 `7 Z. e2 R- k# M) u
2 A+ e# ~. B7 [+ z o, q7 C foreach($_file as $file) {& u( [3 G( j/ k8 A7 i
# {' ?- I! S9 [$ F$ h" i j2 c
' ]) c/ E$ P4 s% ]. t& t9 l4 d $this->file = $file['tmp_name'];: ~( s! x' o5 n
% x0 X( T* q7 D0 L2 K# Y
# p3 |! c4 F8 {6 O# }2 n# L
$this->file_name = $file['name'];3 A1 l u/ u; B. j+ c2 B3 e
5 x& y H9 f3 i5 n+ c
" I5 _* v4 ?5 J; z9 m $this->file_size = $file['size'];2 s& _! h8 U8 Q% [: a8 r
4 E' g- j& W5 \) t# z: Z
! j1 K* l, w, n $this->file_type = $file['type'];
9 n9 K5 A8 \" K* D; i - j8 N' ?% ]7 ]
. ~7 L, I! r% U* R4 i
$this->file_error = $file['error'];- B1 ]$ t) m3 o. i2 L4 X
2 o. K( B7 Q0 g; D8 h% N! e( m9 _& \: L* i% N" T7 w
+ o. @" c' l7 x$ P
8 p3 `. {6 A; F- Q4 j- K: N% s% S, n
}, c7 f. U. T5 x& x8 P
$ x1 X" _) b2 Z) v2 h1 c3 m
" Z/ U: y# H' \' j $this->userid = $_userid;" B% ?8 F/ \% \. K3 u
% E8 H- o; u/ {+ ?2 {% m( M1 {) ?$ C
5 k$ q+ I& F2 [8 M! G, @% o% d8 v; s $this->ext = file_ext($this->file_name);
- o4 ]1 v3 g8 B- u, ` * x4 P7 E% T7 M: ]
: C' c! p1 { X; h- f
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
$ n Q* Q6 K& l# ?0 J
7 _+ z+ L! S+ e! p! \- N* L! {" D
8 R* w/ P9 M" L $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
; P# M; V3 L2 o6 `, f s( O
4 ~( A1 p5 D5 W% k* O
2 p9 ^) w# \& L; ~0 { $this->savepath = $savepath;
o' n% S+ R3 M- j$ p: G, J
4 ~5 U( t, w1 \; w7 Z( V& l0 w
$ T" ?- t# x6 g/ m0 G) [& { $this->savename = $savename;
: e3 g, c9 G+ o) j$ R ^. |4 a, d2 H4 K. @
! u: J5 d: f$ \! S6 W8 q
}}
$ {; P) |5 }8 k7 Q6 r; M+ Z2 `! U . b M0 ~( I; V* U
" }' L( ~1 E; D& f 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
5 V7 {6 j3 i8 F / R3 a; S* [4 Y; ~# G' s2 a7 f) d
2 X6 d9 P$ Y9 U8 V O3 c 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
. ?' u# F* T* `
* p/ f4 c: p3 L2 [( p
4 r9 _3 G8 p2 C8 V $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
6 X0 x/ D! r+ \* F i4 Y ; c& K! A' x- B# q: l
4 m, N) v- R' D; O
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:1 `5 k6 A7 V6 H [
$ X$ ^: A1 [( Q& ^) N A- V4 A. l! f3 M) H& b; ~
$ _3 p; ^7 ~' {0 d% Q2 a
" h5 h& Z. h A$ ~' N8 G
$ ]6 v& O ?) ^5 r6 b4 } 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
8 z4 r% I* {3 N2 N9 F " J7 k& K+ Q8 E2 ]8 e; J k
8 N7 d- Y8 k& X
<?phpclass upload {
! z. L8 f5 s# ^& |3 O& h
r2 j4 v1 V) Y) ?' f7 ?7 `# U& N
6 t* f" I0 v4 k+ m' K% a7 @/ g$ J function save() {
' `5 t2 T, Q- R# @ `
& g1 I$ p/ ?& ]) l
D( W) q. f6 x' t; P# n; o% u include load('include.lang');" a. @' i9 ^1 p: ^* K/ h/ ^" _
+ H: {6 F& {# E, k" k: o0 s
( O; S% N. Q1 r2 B, B) ?1 I if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
# ~( T3 U' O( {/ `+ R
. m4 l5 |2 J1 e1 K8 z, J( E! m" n7 h2 p1 | h3 q2 x, i9 e
$ I1 @/ b: N U) M/ R/ A - Z! s5 O8 }+ }' w' a, ^5 j9 M
+ `0 z j5 v& L if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
6 o0 [/ T/ u. U% H: h0 m; U2 x
5 E% V x# Y" p9 {/ {# o4 J& ~5 [' G( \+ E/ R, h
" V- [4 p% B: Z+ `) Q, W1 W& ]
3 ?( y* B5 [1 W7 d# I4 Z' h" S0 }
" t1 |6 b1 a$ q8 [0 v if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
+ d0 F0 c5 v& y+ h } % Y- \" |, J' L1 E x2 Y; w
2 p. o$ [% y1 V2 P4 ? 0 h0 O. E6 |/ Q
- O: P9 C; Y& w8 R6 F( O: M9 ^* j; c# |, E4 I
$this->set_savepath($this->savepath);5 v& Q Y2 b0 p
% w ?* I: A0 s
! ~1 [, l* s* w $this->set_savename($this->savename);) Z/ P, E k- s1 t' k. Q# {- K
2 M- d: h% b8 c! H, F
% O7 t$ c# a6 s0 V9 ?- j- t) p- O( } * E6 @4 E8 ?9 c3 Y
! X. ? l. |. {1 w; m6 t, n& u- T, e4 Y. _3 |) V
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);! H+ [$ X$ E: \ ]( _0 V( ~. O
5 C$ n* S* k2 Z6 d5 x9 I2 v$ j- ]5 u/ K
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);, z/ I5 y; g/ {, J+ J
. G1 O- z; [5 s7 }3 O% q' Y9 l
+ r9 ?6 o. u4 N9 r- _
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
7 A/ E& w2 C6 T3 R
6 W# y) _% e7 ^% j. d( B8 _9 d2 P
. Y- {6 D1 I/ g2 A; t/ M
! H' R3 p1 [9 V1 M0 o! ^" X8 e
2 O$ d M8 j8 w- y+ [/ i9 i
" x9 \1 X7 v o0 d $this->image = $this->is_image();% o: e" ]& C, f
+ Z. d) O9 C; H/ k$ a! w+ D
Y* k1 D7 c! c5 {
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
1 e' Z" y$ y# a; B- o7 c8 A
) R# I! m$ Q8 g1 o6 W; _3 m
) e2 T3 Q; Z2 [% t$ c& J return true;4 o! D- d7 F4 N& z; a% ^ U: T4 O; S
0 t5 f( S4 I( e% c
1 Y8 _, x9 Q7 s }}1 _/ w) r1 n1 U0 C7 X
3 b% G+ R# G+ g4 j. ? n
4 \. U& k! h% m3 L8 D; ?* f7 u L 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:& X' }5 j6 m/ T" a/ S
& b% m8 g s4 O2 z, G9 g( p; a* N* Z+ R$ v7 h& J) n
<?php4 I" x6 b; k, F' X! q8 k
: V1 F( C4 Q8 z) F. }! n- x* J4 u6 ^7 r- O( S: z
function is_allow() { @ p, {# P' g# w
# ~" O, J" @1 w9 }* s6 j( J9 J/ l. t* _# w" _5 j4 Z9 ?
if(!$this->fileformat) return false;
7 U7 n# c. B7 H6 D ) _+ J/ C) Q4 Y* X0 s
- k$ L5 b5 {8 M" v
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. X; z5 @. R+ i! m3 O9 V; i" V5 U
' A4 l( o( F* g+ I' P
& H$ q; D" k7 e+ `6 R% u" p
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;
" x+ c& m9 m' X! P$ I* K
- X# P9 j) E9 A8 M
3 j5 x x; k7 X return true;+ _ \2 Q' p% g2 }: k7 b$ [6 X
( p) J" R# y8 q% v3 Y
' w' a7 O8 }3 z1 ^8 d3 ` }
; K! B% }6 K$ g4 I( r: }# [9 n* K
& h5 q) e& c' l) U% E2 U" u9 g; }# t0 i, k* X. P" C2 F! v" ]% h
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。$ l0 h/ p6 V' g
' G N2 ^) Q- J" M4 B/ i/ O' c2 p; M( V& }5 l8 F5 H! N/ `
接着会进行真正的保存。通过$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 W5 a: P) V, B9 z3 B- w
; W& k7 L% G. ]9 y: I$ e
5 e5 O: u! v0 Z) b 漏洞利用2 ?* [" c& Y9 R
1 V7 @. @; y. a
" W+ c, M; q0 M: e- i* A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
% F1 k3 B: G( u. K
5 f8 R/ r# E7 s1 a- _+ A
7 U1 z( H- A3 D 0 m4 d* W1 a9 M: k+ A
6 v- t6 A4 `' w( u
0 T2 F% @6 l. W7 m
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
* {6 }' S7 l: O% ?5 D& w: n
7 p; d( i( {$ o( p( A$ w. m+ K' T$ `' i- {7 {" q3 o
不过实际利用上会有一定的限制。
7 z! W. ^- _1 ~! ]# z! Y& U* d' p 5 T9 o) g( q$ U4 ^# U8 w2 I
: v% e6 C+ H, R, }- T" t V4 e
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
+ |4 O$ \9 ]" ]4 R1 P* ?
* ?6 v! p, ^4 Q4 o7 g( w
% R% B: ]" N5 U1 N% ?1 H3 j
# e0 k. V9 K3 J: p : z! @$ F; X+ N1 j' u* o
4 @1 l. {6 K' e+ h t( P- D
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:: [/ B* p t1 C. |: E/ S8 b
8 w' u5 H/ e+ A. V } J
( i+ m- D5 A( s& H V9 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]);省略...
( F% r, M& m; B8 [% S5 a
* a2 Y V2 h* e3 U, @+ T. \& O8 \2 M8 j1 C/ W# M5 M6 b
因此要利用成功就需要条件竞争了。4 g6 w1 q! @# {- c
# p, f. g2 a# E& v7 G- m% ~. G
/ Z7 T" w( i* `5 x/ x% P4 x3 i2 B; W
补丁分析9 _: ^* e/ R6 X$ M
) ^. r& Q6 V. W3 ~8 L0 O+ ?
7 u: c! \, G W0 o! }* s
2 {7 G( B% X3 X7 S" K4 L. u& C 8 _1 ]; |" h. i; f5 A* M! n$ M
" Z2 b0 ~4 ?/ |# N4 O
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:6 ]) x5 n4 O6 q9 _: `* b, S/ F% B* b
7 a( t2 }& Y4 [' ?+ s0 [1 g
, e M4 G: ]0 d& }" ^6 x% x function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
) b! W \6 ?5 E : W5 H0 h u6 n; l" j
% X& n0 `8 B7 O% k( X! s5 ?
- k7 s7 R; R. g- i + X: M4 N0 Z- r) m; m- Z0 k
6 @8 \$ @: C) j5 \' _- v& ]
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。+ \; l6 r9 p" A' x% d
% c0 r; v5 K Q- ~$ @2 o' Z+ w8 X J* Q" {5 K- f
在is_allow()中增加对$this->savename的二次检查。6 U2 k$ b# R' H8 j, \* M
/ ~, N( z0 n* q) D% ^0 k
, B- Q2 E4 D+ k9 q0 L4 J3 u 最后
. J/ f0 y# V5 l; e# ?: J S: {' M( c, F( r+ B% `+ D# q2 M1 q
, `, o- N0 X: P2 @' I2 i. P
嘛,祝各位大师傅中秋快乐!- E3 d# i5 k& @" ?( l
4 R( f& C; W) M X9 F/ \ z1 {6 O8 ]- L4 `% q. J; o J
# ^ b& {1 Q; D% O! X# n m
0 f4 B$ Z, M) T
|