; x6 r4 S8 [! |+ W+ h/ G1 X/ u2 N; j5 K
4 c: e" m. M0 |: \4 {
" Z1 |1 \. ^! y `6 f v4 q
& y1 b( Y/ ~- A+ I$ R
前言! ]0 L5 d; f& l% d/ O$ y
7 R4 g9 h/ [- J1 C; r2 G
5 f2 @. Q" I+ x2 y. N k- ^ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。. _% }3 T1 ~" j6 N3 b; t7 t
0 D8 \$ V7 f* [5 l: h
. e, W2 J6 c# |/ s7 |' s
+ f S. B& H. k3 x& ~& c3 _! d ! [! k3 t+ q/ h+ f$ P% v
! r8 S) G; Q4 ?- s! E# o9 G 漏洞分析
3 \( _8 A8 Q& u. A! i8 b4 E: i e% u/ z/ m5 I z6 k
4 Y6 P# E+ ^! A0 _6 M5 \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
3 Z \8 Z( u, p3 r, t1 M 3 r# v7 K7 E% `# V2 d8 P9 K9 A" O
& o7 d% x+ x) B1 {
4 i* H8 P+ N+ x( Y, a
8 ]4 p4 d% r, c. [. l
6 p# t1 A" w: S 对应着avatar.inc.php代码如下:
7 S2 h1 p" _ O
3 M, l! }! N. |" S+ c
% P9 l+ y, _4 L$ i. B9 k/ w <?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) {# V1 T! R4 }( y! r7 S/ a. d
1 Q& p$ H8 [: X/ E/ o/ p. i! y- C: N2 l6 F
case 'upload':
1 `9 y. Q4 Z+ }7 x# ^2 v , P" l. I3 L" M3 ?$ A; Q, N
4 }8 o. [% }1 y( j; z" a7 | if(!$_FILES['file']['size']) {9 P6 T& Q' E$ Q* [
' `6 [! F B0 [+ I" L# }' O
. ^; ^( G% O) r- h
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);( l1 R# c4 j( L x. W
2 j* D! @+ v/ Q' P2 w; a1 M" n# }; z+ C2 a- ]. G3 D5 _
exit('{"error":1,"message":"Error FILE"}');
) Q2 z7 q! j, b, @" |1 Z1 `( Y# Q
5 ~* p( U, s# @( M& {5 i) B3 r
8 i% n& b4 ^8 ?' w2 Y# K7 D8 e' O }
, `& m' J6 Y) S3 |0 s) I! | h/ g/ J; B" _
( B& K: Z' I2 ~+ s2 K! { require DT_ROOT.'/include/upload.class.php';
1 o4 I3 E/ a+ u0 E
- w* X+ F7 {( }4 g4 ~7 `0 V3 Y4 C% V5 {2 b0 \6 e) f, z
: v6 ~/ K: w" e) i2 U0 I4 Q
/ q9 R6 ~; a# W# }2 X* B, W
7 l) t' C! k2 i. K2 u2 r $ext = file_ext($_FILES['file']['name']);" v9 v; _* w( P
% Q8 Y7 U" R5 b0 b9 w8 r
0 S$ J* z1 P- Q
$name = 'avatar'.$_userid.'.'.$ext;
9 F M, s& U1 U : |! `1 v, h8 g
0 F+ c' [# U+ R/ I: D $file = DT_ROOT.'/file/temp/'.$name;
8 u* _7 n3 |# d" e( ~/ \1 |( y
5 f3 e% L* \3 Z5 p' x: h% T: K; b9 R( x9 M7 j
$ H$ V$ L6 k2 ^) \9 p6 e
' _, ^! l: {! |) Y! ~0 T) L9 h
) D+ E7 _- g/ V if(is_file($file)) file_del($file);
$ w. h( t: Y; p8 A! D6 P' z
) _9 _; b. o5 [5 w% Z- `3 ~( Q9 X( t; j$ W$ p" X
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');$ x( y& P& q9 `+ c+ _, R
! a+ E9 `, i$ C8 o! F. z1 L
3 _$ y7 m% a. e4 @ }+ k " o6 F1 x; i) M+ H4 c' o! P4 K
5 P! f, V: y, q: |" W+ ?9 O
' v% V. ]4 `/ @0 L# j3 }; [! m) k $upload->adduserid = false;; Z7 S% J! ]* W" t' h7 j7 @% D
/ C( v |2 j! F* K4 q
$ u. x! O* i' k; V
2 b5 A- D/ a H) j+ I $ |$ S- H3 z1 p0 h: C- |
9 L+ V) h7 g' O3 M0 W7 m
if($upload->save()) {7 `5 T7 @9 F, z8 I
% `/ M# s3 [9 C" B. r e r1 Q# k6 {# }5 E2 M- |
...
4 v6 b4 @- z# x1 @
; `3 l8 Q& Q9 g3 G+ h
! M+ S( L d: K } else {
8 ]3 c( y6 o) Q% f6 ~+ n
9 m1 X; Q5 d$ W1 @8 D
: J- f4 i$ D+ p5 w/ R( ^! _ ...7 {8 | }( I$ |8 ^* R
, i" h% z/ N @
5 r" ]. I5 h. S& A4 R, ~
}
- S6 ?8 ~8 ^% Z7 n' ? , ?% ?. U5 A8 o
7 a) ~: U' a! d/ M5 I& S# G break;" o! o+ K; [2 `4 A# T, j
" \- v; x. i* |8 T7 S& [
/ u' ]/ F) [* V, _ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
' W/ L" H# F @9 ]' L+ _. F% F ! d c( {6 E8 K5 ?* \
5 ^5 K3 y4 L/ u$ }1 V X8 M
upload对象构造函数如下,include/upload.class.php:25:
/ k% N7 S9 W' `1 b1 k0 o+ A
/ D& e- b8 ~" ?* q) E! L; Q: U0 X- c
<?phpclass upload {& I( {, _7 h* n8 M4 P1 A" s' w5 m
- ^, U3 ]6 K- ~5 h5 ]! u B3 r1 x- t8 W8 J9 H# C8 k9 t6 s
function __construct($_file, $savepath, $savename = '', $fileformat = '') {$ C; `6 z2 ~; n( n: I
' c# J( \/ b2 M5 R1 g) c9 ]$ `" ?* T; ?% {# B
global $DT, $_userid;5 U- l1 ~7 @$ k( u6 G
v. X. D# ?3 y! r. M- R! r$ t5 G
foreach($_file as $file) {" t/ x% Q6 C4 ?7 q5 t
/ C+ R0 P' W* U- w* \9 L1 |; U) I: l% ~$ }
$this->file = $file['tmp_name'];5 L3 K7 c5 H6 _- K5 y
$ K8 U4 o5 ?; x0 f
! D3 L) s C) ~ $this->file_name = $file['name'];
: M) Y! C( Z5 r
. I, c, w2 c1 G) ?. B, x$ W( k6 e
$this->file_size = $file['size'];6 k2 _' [; U, l( `: T6 N
% r- v* X3 r' d* z7 x! K
$ V v' T/ C" `# s$ @ $this->file_type = $file['type'];0 b- U; O% q5 c# g
- O1 q2 A% n6 d3 B3 m
& o! l+ c' }! F+ A6 _ $this->file_error = $file['error'];0 ?9 D& d8 D$ g& s }
. n# l+ W2 \' d' t( \; J
4 I9 X1 J6 V2 ?. _$ i6 B6 A z7 r , W: r& H2 \; E$ G0 ?
s* l: z' Z# r' M `: I. H$ u2 ?: c3 V4 q4 C" r
}* E. \2 f. V; O2 w4 D* S1 Y
2 p8 b/ _) s7 {3 i2 `8 M
' M5 T3 D4 n- D' N- ~1 S j4 H $this->userid = $_userid;
8 F1 M: W- L% W4 F9 \' k
+ U8 l4 K% e* `* ]# s6 s: F
* W f% _5 \& i# o $this->ext = file_ext($this->file_name);& V/ a. N4 P* V. K6 c& n
0 D3 |' Q1 V3 \) p; h; J' @ n, G
8 F, w S! _9 P $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
, q# X5 R- t& E0 f( B3 Q
5 Q# x5 d: h$ v. Y; ]: W: t, {( s, Q- L/ P) d5 y
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;& Z$ }3 Q3 c6 E! s
8 c" J N9 w! ]3 E* G! p* s( `% {- _, s6 D6 t
$this->savepath = $savepath;- b& z h- G) z6 }: L! q
( q1 N' e: K& c
, k$ \* m Q+ x# x9 J1 D! t $this->savename = $savename;1 v; J" W3 |/ \8 q2 E A9 R
" b+ o5 Q e! g j; J% Q4 h
$ u' o* J5 @& r }}
; Z# J, G$ J3 r9 B* ^' o
~4 `4 z$ [- i6 j, P! [/ [
. j- T# O- c- ` 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
' Q) {. ~) d; ^# v4 E# \( E5 @
4 b, |9 @" g: Q+ Q6 U. `, k: l0 t" I5 w4 ~8 p2 P8 N$ C
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
# Q q! W) U7 r* K
- C, r6 c* U7 K. e) s4 ^
O& I+ ?" x8 L4 ` $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
5 w0 V0 J5 l: T" d) |1 G/ D: b" U ) p/ I. \' G+ j. h+ @1 i
2 n) l! ~0 E' o3 o1 O! |
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
2 z9 ?: J1 c3 ` R" F5 g
6 L- [* m+ q% k1 l& x1 _6 r- N: ?4 x* K' B& i) ^8 j/ j
4 }5 _1 o3 b3 |4 N
* V# M, ~- _, v( ^
+ Q0 D% d* D9 @+ j# L# j" W6 L 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
7 }9 @! {( v7 T5 p, q- z
' D0 ^8 ?0 ?/ L4 L, A2 w: \& R7 A. }3 \5 l8 k( M
<?phpclass upload {6 D: N+ }: K: ~
/ T" E& I- Q, z" a6 X
: H8 i$ L0 v7 C1 W. G9 n$ P- G- e function save() {* p1 L& B7 O; K
" z( K1 e9 }! D. n- B6 p- v
/ I9 Q$ n" D1 V9 k3 p include load('include.lang');& `+ P1 m6 N. B, u
5 ~3 A8 w1 H, D0 _2 o6 l- Q5 B
. m8 }/ w* X8 B6 r9 a* b E- ` if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');5 i# E( T( K! V( n- L6 |
, Q" z8 a: ], e4 d: O! U
' c( F! X6 |& f% c9 P" n+ P C7 b
. F, ~' W0 a+ c! p& O \& Z7 z7 e
2 ]& _9 m/ J1 T5 n
8 y/ h3 u& S' [% S) T if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 n( {; N: h0 d; X) u$ H# |
, A. d$ |1 r: J* x9 u- C
* e1 R1 V8 i/ u4 @
5 g) R& }- B; B# w5 ~+ R 6 k1 z! |& Y0 L& u( M5 M) t* |# Q
( {4 @; _ @( d: R1 |* x+ S
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);5 n1 U! v, M* x9 n2 U
/ W% j. M* w3 B1 l4 W* p+ o: R. f" O3 t* Y/ U
) Z% I. |4 i* @* Q
3 r1 |7 i7 x6 G+ X" j" S
$ Z0 R% \3 [: X7 X! s. \+ ~ $this->set_savepath($this->savepath);
6 w, n" t% I6 \; p2 Z9 \) t4 M , l j( V" w# ~3 X# y
: }- `* j' l8 f $this->set_savename($this->savename);1 q b z4 g: d0 E j
# Z# k2 o$ Z0 e8 ^
; j# M- P4 L5 k; P: d
; r2 E# [, J$ G% q; i! T+ A
) z4 K* ]: D+ U* j k' `( i5 G
; v! a, d& O( k7 j if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
- |1 m# Q. @0 A8 ^6 T5 _* K, x
' ]6 Z2 F5 C$ R5 z
; e& f1 G- p1 c2 x if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
7 R' j0 t; k8 Y/ r7 q# R9 d8 ^
: k( ?7 r/ [3 m& t4 r# g
+ x% n0 P5 r4 |( F1 v/ V if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);! D* {7 q, r; I" J: l
( y* P) f4 w' b. _ B7 I, ?
7 m2 l9 e* b) O) g* X
- D9 d/ Q2 [$ K- q7 H* c
" Y: f! e' J; D/ x9 y% Q7 M6 v! R) V
$this->image = $this->is_image();# Y ?3 P3 c0 U
" u' X% E8 e8 _3 ?3 |) C
& K4 y& B) C- w1 r Y7 c# l% l if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);1 |$ u! X3 \8 i; |3 |
7 m! N# \" e0 X7 m! i9 L- M* [/ H6 P* f& p% D
return true; R3 e- w# f S5 P7 i9 t
8 W* g7 b% @2 `9 {4 i2 S' \/ O: J" z: j4 }7 S8 I$ t
}}
0 s' M) S8 N/ W$ M0 j2 G
8 I8 N( ^0 E$ }2 n' H9 c r0 [5 h# A' b$ ~5 J8 S" W; U7 ?/ M
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
+ i: M, G* m5 j" G( F& R% c- C# K6 I
+ I8 X7 V0 z& @4 [+ M9 m0 \: n5 _" q/ }& N
<?php
5 d- ]5 B" \2 T* k# Y , l1 r( C2 M9 J# P9 n- e
* L- T. w/ V6 ~% u6 k
function is_allow() {
$ k2 T! i! d4 n/ i6 U8 i) Q* d / K$ O/ F. Y/ I8 N7 d9 D
5 x2 |) V% i' G1 x. U1 ^ if(!$this->fileformat) return false;
: U# ]7 G8 ?3 J$ g8 v+ F
+ p1 ]' m1 p) j4 ]! C' |
9 S) I+ W. i3 F7 ]! u3 Q! F. P if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
' |6 V- u! d# p* z6 u; Q* N
. a9 [2 v* `7 J- Q5 g. Y$ G2 M7 U7 g8 y& P" 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;
: v W4 [/ h0 C
6 B$ b9 q- q* }' O
+ F5 h9 M) d2 V+ K3 ?/ G' _* C9 i return true;$ H5 c, M1 x7 V2 h; ]
3 j8 B5 U' I% F# E5 W
% k$ Z8 f0 T: \! I }) e) b+ Z2 P; ?: F( y, E! l
0 l& Q! e% ]1 J" l
, h. N+ C( H r2 E" T6 g+ F# S 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
7 {! r, a5 _: X- G8 X' o, N8 l 1 ]$ h3 J+ S5 H! h7 `' l
, E1 g; I3 n+ V$ v
接着会进行真正的保存。通过$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文件。- q& U. U8 R6 ^% i
; o5 _* g" ~1 u b8 W) c' m6 W m
; ?! N' ]* V# h 漏洞利用
k2 b9 \& h' M. Z& [& Y" P& J% v( j x) Q8 j6 _( f
u& y8 D: B& X' a& T 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
. a ^/ f# v7 j9 a: n$ ~& g
% `6 H& F) C& C i4 J3 O1 I3 w# ?9 _, u4 r+ W# R/ j
! Y. N" T5 S$ t+ Y4 U ; ~. T' i* T* n
: o$ G! Q9 Z' {' Y( p$ ?' Q
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
: Z- C* i, `3 h- {( T- w: G
6 ^3 d; S- P }$ x$ k' j. [! `- C& @: |, Y7 I5 H9 `
不过实际利用上会有一定的限制。6 A5 U1 {4 W4 d
# a5 Y" E* \. x+ H9 ^
6 ^% y; M/ ]7 K
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
9 A" @# i1 q6 A5 t
. Z4 f/ I* {3 y: n. v! L8 L$ _& { B- J, V
: y3 e! f9 O# v' J$ a! R
5 [. t& |/ L, i {; a$ w$ V3 ?5 E/ k" K( `! x q* {
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
0 ^+ B. Z$ M4 t$ z2 W
2 C3 c5 \1 g; y+ J
: X0 M/ _" C% 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]);省略...
3 B' `1 ^) v2 x# ?! h% k
% O' f V6 R$ C4 n
* x+ { {5 d$ b9 Q/ o: z 因此要利用成功就需要条件竞争了。 ]1 r, E: o' m" s
2 q0 ]( Z8 M+ I" M0 q
$ q" `1 Z+ _4 B3 ^1 D4 `# e
补丁分析- u" m) B, ^+ \. p2 v. z
! M6 R3 f; L8 L
3 l# ?- @. X1 E) i$ F# X4 o: z, K' M 3 B/ q# `, w0 F! D. o: u1 v7 G
1 G2 |, S8 E& e" c, D4 c
% s7 L1 W" I. C% L- C0 }2 v# c: @ 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:! v8 x3 G5 }3 N! R9 [
9 G @ V/ w& B) V
# ]3 N7 A4 u# S! B- q: P, D( H, h' t
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}! @+ W& y- O! U! ~2 j& P
1 G$ ?2 [- [2 B
" j+ h9 t0 w- S $ h, r1 r9 b- V, I* ~" Q
, S0 P3 ]4 E; ?& a4 e4 X' o& I
. k2 U& l% {' W 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。4 m; Z2 v/ l7 Q8 S# ]
: w) G( f0 L8 V* v8 H7 S- k0 }& o
4 v3 Q; i/ P7 c) q3 H3 b 在is_allow()中增加对$this->savename的二次检查。
: c" B p1 E1 D3 ^4 a- K # o: ~8 O4 o* V- b* `) `
( H: t" a. {( T; ^/ R9 b 最后% H# Z2 b* E9 `- F
: _; e2 X) Y! R/ T
Y; l. C* U$ U4 `" C ]( m' P2 O 嘛,祝各位大师傅中秋快乐!# j) s( {' _- u. p9 I
0 }( q; E. W# H. M; k2 N k- l
) h- Q, c2 r% E# e! m8 C, c; ]- T
6 T! {; d" X0 [) p
' G: |# p) d( Y! j4 |1 z# k6 j8 ]. x% X
|