8 ^0 H8 \% J4 @) K4 b/ T
/ {7 v. d2 A3 L- D" B% c9 {
z, I' T& V- h1 i# }
8 v$ ^- L2 Z$ E" W 前言3 J5 J( G; z. X
3 ^2 J$ D% f# P" e1 @" Z* C
; L0 }# J. G4 c; ~ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。) H9 n( K. F' D* U$ d _2 U
/ R/ G/ n) C" e+ o N" X) @, A+ _- W/ B
' y; Z: K8 c1 Y5 C& J! h
) S# T9 q) T) H# U: L8 Z
+ e" S& a4 `/ i1 d& l" @9 G 漏洞分析
5 [* `- _8 ?$ F: G4 Q* B9 F7 G2 V3 s9 K$ z# s+ Z
/ [1 m& o" F P0 r0 T1 C/ } 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
3 y$ O* }6 D5 M% v4 y4 \
0 [+ ^! F4 F, L d3 g3 `* Q8 n v5 ]. }; f. [9 {% w
5 i& R6 N6 g- w% R8 I5 t
0 W9 @4 a2 N, j' F' r/ Y
6 Q: E$ u1 w0 t. Y+ v/ q/ j 对应着avatar.inc.php代码如下:3 X0 ?& A3 P# L# k1 K
; |) ^! W. t' ^0 r( l* }
v# \( b2 m/ I" t1 d, 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) {
% T# O/ b' m* u1 `& o) ]5 H 0 d) {0 ^0 g0 [* m6 ?
6 G, H; E& L; u' Q+ r. a
case 'upload':6 s* @' o4 r/ w
6 ^9 M3 E) V. s
9 c& S5 B9 Y1 j3 e4 M if(!$_FILES['file']['size']) {- @0 t* j+ ^/ C9 g- _# q, r. S2 N
' _+ b# F. o" V0 v! p3 N; x% o( l* {8 X7 ?
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
& R( M+ @: I) F
; ~( n% u2 i6 ^
+ _" u% @- t0 P exit('{"error":1,"message":"Error FILE"}');
# ]" b3 w, m0 x) l& r4 P
7 } C* G i4 e$ N/ {& g4 T, v) Z' \+ ~# \7 e! N+ |4 ~
}3 V& e7 k F8 E7 g( F3 V
8 @* k9 W) G5 \ E9 J
2 n) A0 F" ]% Z8 f require DT_ROOT.'/include/upload.class.php';: F9 [% F% a* Q9 o7 ^/ @9 ?) e
/ O! U3 u$ m4 j; Z! ]2 V# U+ E4 ?5 M3 \( u$ v4 r {& A8 c
; N* X' V0 Q" Z6 @4 G % @0 U E$ N- \/ e5 e- g
9 T6 M) S; E/ U
$ext = file_ext($_FILES['file']['name']);: O/ v) B7 }: R1 O ], G/ S: |
4 ^" y6 V% s& B8 j3 K- A* e/ n$ H* r) e C. @5 w/ }
$name = 'avatar'.$_userid.'.'.$ext;
0 u8 [! g# C1 X, f+ G
9 s& m5 Q& f7 G) G7 Y2 }9 Z! y. J. ~% R* C9 K! [
$file = DT_ROOT.'/file/temp/'.$name;
; |, g2 [5 T$ p0 D/ b; f' _, y 7 ?6 E- a/ I7 O) I! m2 H& D
$ f) }6 R0 D `7 }4 ]! [, q" k
9 x3 d) v3 Z) Q, B# q # C- F! z! e/ l j9 N% x
6 ~8 Q& Z2 P' s0 W if(is_file($file)) file_del($file);
# t+ b" q) T8 ?9 P3 H * [8 x3 _1 i2 |# F, o$ z! N9 B
2 Z0 W! X C, ]: E# o $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');9 z' B5 K/ q4 C4 R; x3 H! `) ^
. J6 L; @& _0 k* k
7 }4 u2 j' i8 z9 H- a- f6 A- r4 ?' \ , S# M9 H, \. X. `' Y' z
$ _6 r" P& ?! \( N
, \! K4 J& A. g1 c q7 x$ G8 v $upload->adduserid = false;
; e7 J2 O$ n: H8 t6 D6 b 1 ?9 B5 e0 U2 x) Z( X* t) x
* {* i7 @, p' _( }: j
# J& g A4 v$ q: ? - v) u; Y) c* }! k8 p
- _! X q6 \/ @ if($upload->save()) {( B' i/ I7 ^ }$ [
" D! Z& B+ O* X$ W, F* r' Z
5 ], [. F- A! ?: _" c! D# D e. s0 _ ...: v! N& e6 Q% V. W. V6 p
' ~2 v8 b/ w7 j3 m/ A5 f* g
1 c# L2 e. q& o3 [& a* T
} else { p8 T0 T- ~: v% A2 [8 O+ h
( n- B. y5 ?5 s) O- ?) I: |0 \+ F( |# h5 y, S/ s7 ^; F. ]' o
...
( t9 o, ~9 D0 A) e + R) X& m) f: ~
( `: _- h& d* l( h' B9 C5 r3 O3 @
}( v) l) C4 H% u: h
- B$ P3 M5 ]1 |! ?% J% h
+ |& @ G+ p: ~ break;
# I" z! T L+ R h# _ ' E$ l8 A$ [$ h+ b& ]3 w
3 ~$ w+ r$ n7 P# y+ l 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。) t8 @. M* ^0 p4 F
; ^+ E* x) P" g
4 L0 B, j) m' d' \ upload对象构造函数如下,include/upload.class.php:25:. i, @) }) e. {$ `% h s
% A, a( R8 Y* t; U- |) u" v
% [1 n5 j2 w# S+ W' |0 g
<?phpclass upload {
P, ^$ o% S) N9 j # q6 z7 O3 O2 v1 }6 E% h8 t
+ u$ Z5 A1 X1 h% u function __construct($_file, $savepath, $savename = '', $fileformat = '') {1 D* X2 Q! s$ N0 E- G0 r. o- O
# S9 V* {, h5 J: p' d. ~: l4 i3 a
8 R' Y; I I3 S' a& [2 b( ~) V global $DT, $_userid;# r8 ]' S$ r7 [. O% A9 _
% L: K8 i* V$ E
/ n4 A a! B W
foreach($_file as $file) {$ b$ _4 |* F& N+ ]2 M! I
& d( l N: l! T" }& ?& i6 ]
/ c0 Q, g9 w: f $this->file = $file['tmp_name'];9 m) j; {6 S% e9 S& U( E: m
" \, [- I! N2 r" v
& ^9 M y# ^3 y# M7 S1 P) O $this->file_name = $file['name'];; G* M2 q' p) `3 j
5 |) k/ {: W% c+ P2 Z
8 q1 ]# D6 Q% L8 D9 a' h $this->file_size = $file['size'];
4 l& S% c/ M! I3 w 1 q* k1 V9 i. N0 c5 B
. `% ?" x" X) m3 {+ g
$this->file_type = $file['type'];
/ ^1 }0 z n6 m& l+ q" B- u 0 h5 ^. E& l% G1 B/ D0 S& Y
! |8 T& O: B8 i+ Y3 D r) J8 g
$this->file_error = $file['error'];
9 l/ k' X7 w* h4 [% H5 C 2 D! o. f+ e- d. C: ]' A: y
* V7 N! P$ Q+ L# }
- {8 p7 u4 P/ K1 Y$ l 6 { h0 ?2 ~+ ?" V: w
4 t$ `/ W* L2 b: M }, N& D- n/ C, V9 G3 N5 C
. o( u/ B. |9 R# C
. g* e* N( x) L0 J2 ^6 L3 P
$this->userid = $_userid;
' e8 f; k' P, P9 U- d# Q, U
0 U8 O. Y+ y! O, _* a: J4 \+ h7 P t; k7 |& N0 Y
$this->ext = file_ext($this->file_name);4 s. a9 s# P5 s# D2 ^ F( S
7 G1 P& x: L/ C% e$ L2 J2 ?- F
" C$ f- e, D% g" b7 D: o6 U $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];6 Y! \( {) ]) I3 @2 s) j
- X, h+ A% e. E5 Z% A! F: m
: ?( r' Q/ L/ u l
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;' l( Y6 Y* P' A
5 C- i( _; W# p' w
' `5 }9 C: H2 h& o9 T5 h( a) e
$this->savepath = $savepath;
& C$ ^3 X; J) W # J4 E7 C0 ]+ T' e* t6 G
7 A, r" N- [) K5 T' Z J/ H $this->savename = $savename;3 z" h' \7 \+ v4 F. K4 u& E+ r
6 n& n ^* B; L0 Z* t& x% t* t+ S- Z0 o: g) o A
}}" @# z9 x5 k6 {6 m2 }
- I2 B( A/ {7 M5 @- _2 C5 M; T# }# A9 b" ~9 T
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。: ]2 B' h/ c" G# b2 o0 R
1 L" d/ v7 ~( V: C+ M% c1 N' c2 ]. y8 x; z; a. V
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 " E. n1 s J/ R7 h: r
3 p( }' g' v2 X: q
7 d V5 N1 u" K* D# N( ?2 z $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( n* a0 _: O# J0 p0 ~
A$ u0 N1 I4 q& K x7 Y7 Q p2 Y# T& Q7 ^; W
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:- _, q/ t+ |% x- q6 n C+ h
' s3 \3 z7 Y* }) S1 p
4 w- s) e* o" G$ u4 W# ^4 a$ x
q$ Q/ a! D3 |" d7 t2 N) a/ r 9 G0 u% ]/ t; Z9 Q/ ]; Y; ?* n
8 U/ Q9 v& M: I/ `$ { 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
# o6 m$ a8 x/ } # c( X. Q9 {) w3 C) g
) H6 _8 f$ C" w0 O4 N8 M# W
<?phpclass upload {4 g- }# G$ o" ?7 n7 \, w& s
5 z2 J/ F9 l" x
; X V, D: X, U function save() {4 Z( e% ?2 O* L7 M3 v
$ ?: ~* D! r. [5 D' j) ^) c* |) v1 \1 N& O
include load('include.lang');
$ Q* \3 v2 x/ I" S5 @$ I$ _ ! c4 h9 [! M2 @- @& [3 U
. g8 c$ F7 t; F" T3 e. p' L
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
+ i5 a& w- P9 _' z1 O( s( W. v+ ^
8 y4 W) B; C/ i# w* U! \- F/ b# o; c' ]2 w9 r( M" v0 s
$ ~' t% H& F/ S1 I j
9 W+ y/ S" d% o* c5 v. F8 x% d7 s
" W0 t- Y7 T* x5 @/ G if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
6 U6 f. k8 K, ~& O" J- f4 J
: F* \0 N5 x( u0 t: I; l2 x5 p' [8 T& ~$ s: L! v
! s3 e4 T. |4 ?* `3 R% ]
+ u0 D5 ~ {$ }, z4 s4 D; P3 j5 J! s# w% z7 o$ x4 q% M
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);0 _. m8 v& s7 J8 W6 @
- q/ ^* y L! s- M( @
% S) X6 v" v2 b& E6 P( q
1 K" J6 ?( Q4 ~, @
& f- d3 @5 e @" ]# g" \2 g: Z9 `! G* W% C4 m3 _
$this->set_savepath($this->savepath);
. p' ~2 c' z r
! _3 @( r/ r* S6 n) M- n S- ]$ h! k" Y U8 C3 V, V0 x
$this->set_savename($this->savename);
0 g# w/ J, z: n& p
# T4 Y! L" H+ K8 D" q T+ ]4 t9 s4 B
% U9 D- m+ R* f 3 [ u( n( f! t' G# K' l' C/ |
9 S- j: q+ ]9 q' L/ x& {: \2 X
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
. [" Q4 D9 S( j: f u6 f& F* J5 m2 O- E' g
* V; h! ~5 p! U- z/ I0 ~
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
& P+ x4 V( S! \6 }
- s% X* e& ?. J& X2 N8 N% V
! T$ k3 A7 l g: A- c( d4 a T if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
6 p8 g0 A* i4 h) c3 I6 w% i0 [, R
7 R3 I6 g3 f! y! B5 @ t
# v+ A* O4 ]3 Q+ L- h
" C3 E7 A" e4 E0 J ' M" [7 [. M* o. D
/ Q8 C9 a1 N- l $this->image = $this->is_image();
% b8 L7 y8 N. F6 x. Q4 W 3 ?* x3 L8 B6 K3 e4 E
. `- [2 f! U' Y+ [( O
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
" Z9 M9 x. ]! Z) C- b- l7 j $ _6 s+ Q& x/ q" o3 l. v2 m
9 i. o, s& Y2 \
return true;8 {2 e/ [0 L0 Q/ V
: r9 S2 Y3 Z4 [
6 D+ J2 v' ?% V! c1 ^3 m
}}; y+ s& g! @. V( L7 U; [0 q3 W
/ E! V, |* T. p- K' `
: v8 h5 s- s' c: z) ?" O1 P
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:+ u0 O. Y" k, n/ X; k4 W
2 U; t( j: F2 I4 |5 ?4 r$ b% F. {# R6 i6 I) h- J. g9 U0 h# g
<?php
* B$ T5 l5 g W& ?: o* v
9 P+ @: W0 Q: J+ Q' |% k$ p9 X6 ]6 N6 ~6 f: [
function is_allow() {" J3 E2 Q& u5 ~9 F
. u' E# P! d7 c; |" `
: e0 k. ?' I9 p, }4 O if(!$this->fileformat) return false;
2 e+ Y, j! g* N) g6 g- F5 t) d5 h: Y
; b4 e4 ?* z- f7 Q( M/ x% V3 S" W4 V0 Y+ E0 \
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;1 Q: V* j+ A7 ]3 O* l' S- U7 G( T
0 e/ K; \! p9 r- H, b& o1 d+ `& t- M+ B, \8 ^" Z1 d
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;! b' s* N0 e3 R
. q+ J& t; d% L4 L, K8 v0 W
$ U5 S" R. g3 Q9 l return true;
" h, U, B- G& I1 `* H
! \/ o3 T( C# u* F
( i4 E5 P) r* { c0 Q, s }' @+ {. d" l* }* N/ E3 \2 g3 A5 U9 R
_: S6 N, P2 o9 P. n
: |) v- R1 J$ D3 l 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
( V3 ~( M: T7 Y, L+ ~
/ i" r+ [+ r) L% a+ A
2 u' u$ |8 H( v) _: O 接着会进行真正的保存。通过$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文件。
$ X# D! e9 L6 T6 W
. z1 }. k2 g) c
8 \& K5 a- {5 E3 N/ v8 F U 漏洞利用0 q& d8 I `6 ^- T: ^7 }$ Y; Q
1 v" t/ `: x& M
4 R8 k( |% Y3 k! j8 y! `2 A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。0 M6 l9 i, H) T
3 }/ y4 t, j, l% k% D8 C- Q4 Y* P2 T
: M0 X* u( e+ {. ^% {
! J7 R6 \" v9 S. Q
1 E4 K5 b; ^: g3 |3 z6 [ s! X 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid% _: E) w" ^4 |% {+ Q8 B
, G* L, T. `; B6 }7 b# M! @. G
/ t( X3 U* C1 H) v, R: F* g 不过实际利用上会有一定的限制。9 Y1 S4 @" B* P" Q; M& |! Y
1 J1 V( i# k0 \/ y) e
% T$ ~ s9 U1 W' b8 C 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
* S) E5 S0 Y7 f) Y
) z6 u) D! W2 F/ f: _0 S% t5 U1 A$ z/ C8 e9 C: |4 N2 `
3 F; _: w" E5 h- M
. U& `) z" h A: |( f( i- G
* v6 w5 |+ B$ B9 P2 L
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:8 N7 A7 y" N( U4 y2 d T, u' a/ Z
7 R2 z3 r$ M3 T; w
# T/ r$ }$ |0 _2 i% w& J
省略...$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 Y$ v; ~& Q' Y, A2 `1 m" t
; e$ F0 n" D8 Q- Z
9 A. u/ U, Q, P( \9 C 因此要利用成功就需要条件竞争了。4 p9 w8 c3 T; y7 V3 W/ b# Q
, Z7 c0 s% Q* V9 `
! a! S; @$ ^- G 补丁分析
0 g$ [0 c* a, w- P+ d
( a5 `+ x2 f8 f6 o" u1 N5 w: Z( W
- D. R; S. w7 ~1 O! t, o$ m' g9 ] 9 W' J/ C3 E* k: ]! H6 ~
0 z4 X6 q3 K* w2 [! s$ H/ _! k* X! Y ?$ m9 r0 ]: c
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:7 V) u" {+ u! S- ^7 L0 }; t; \
! W. x8 M6 V! s7 |4 c4 a( o
. Y! `/ S6 y+ e- Y7 r function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}; Z- z7 E" z* g/ S
' T( ~! J, S* \" |
0 M3 Q) v# K, i! F$ e
6 Z/ Q" \% h8 |8 A# {
: s5 n$ e* q {5 Q- a% X+ V
# K# [ D. ^; k& a: d2 M0 X. O. E 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
3 k- q) S L/ ^1 f/ h0 I4 R L * ^0 }8 y4 l8 ]* l' d, F
7 e; @7 b- ]7 Z0 i Q7 m: ^
在is_allow()中增加对$this->savename的二次检查。
/ y6 b( n3 M" z3 ]
, I+ |9 ~* a, f1 @
* ^# ?1 g, t0 W- s, c 最后9 ]( y/ E" w( x6 J( @
! p" Q6 K7 j P' X; O% l. }) P* I
+ m& G# R, h/ E/ ? 嘛,祝各位大师傅中秋快乐!( j) m! B; Q b
|7 E" d, W1 Z2 Z
- M# B- {7 s9 \) ~
; g6 k. Q7 \6 |5 x9 d
8 S0 n6 d* `1 @4 v8 y* z, U$ n+ h" n9 {. L
|