1 p/ _% l# f( A: c
) [1 B( p9 R: W2 c6 x( w |8 ~
& E2 _5 `9 B v6 k+ v, {& Y
4 g2 I; i% F' x$ M. Z0 o$ ? 前言: D& ~/ y) D1 H! l
( P) l$ C4 Z! y- x
, h# w7 G2 d" ]
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。; x0 n/ k: c. K4 I: P" E1 \1 l
* }( j8 o. {8 _% A- Z# I# L' O
9 ^9 ^7 n/ u# i% [
. a: k v/ W' s' U0 y2 k
" x1 M- j5 P& b% P& T; t( z
" n4 c s3 s5 `1 r9 O' B! }% d
漏洞分析9 R7 |! S) y/ r! R& W
( B( }: w' B8 S% j h" _6 |! ~
0 h/ U- F: i$ o% N0 U& }- T 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:0 `0 r' T& ?6 W% N& W: N
( _1 Q; K4 o# a: W- P( J2 E2 L
9 V7 R) H+ k% {" X: A6 y8 Y 5 x: e! S. l3 _# V! G
0 \# j, B/ Y* N( f+ n
3 m( J5 { n, z5 H i
对应着avatar.inc.php代码如下:& I( \4 S3 R i) @4 O
1 Z# ]% q% [ Z0 z( s' C- _, C
" [" T8 K1 h, ?5 D% ? D <?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) {' B" m; g' j# s @5 W: F% w
4 s1 B u: h% q* V
5 ]6 T. X7 z {3 k
case 'upload':
K$ y1 |$ d5 w 6 ?5 f" o4 k; o5 f6 |: r: h& I; y$ C
m! X. H. M0 Z6 M if(!$_FILES['file']['size']) {
5 ]0 J* d3 \& F# z; e. g: J) F $ O5 P, R/ b3 S5 A, a
8 }% [6 U0 o3 }" d
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
" {* }; A j1 M3 v2 U $ a8 l' L: c$ m4 v0 s
$ s% X1 j" x$ y7 p b: Z( G+ J
exit('{"error":1,"message":"Error FILE"}');
! C& z- @9 a5 P6 K( w E f# y) y. c( r
# k% y6 d6 \% k- ^! ^
}
5 B2 D. t2 S2 v
& i" m8 S/ H9 b
6 { @0 @/ M8 b9 b5 Q$ s3 C/ ? require DT_ROOT.'/include/upload.class.php';/ p+ \# u" @+ s
! _4 |/ G- m$ S E( i3 ?/ C; i, ]# B: n5 Q6 T' _+ I
4 ]0 Y( ]% v+ x( E
- ~1 y' R$ @2 y* _; i" J( m k6 O8 R3 n" N, ~+ T. O
$ext = file_ext($_FILES['file']['name']);; C$ y# k( d4 v" e/ w" b) Z
9 }& Y& ?$ k; P* _% q& D+ _% C5 h; y
/ u* a6 p4 h. y
$name = 'avatar'.$_userid.'.'.$ext;- N% k+ w& X( ]; g9 K
5 U/ ~* H" n! d, s
( G! A: n/ ^$ e5 n
$file = DT_ROOT.'/file/temp/'.$name;
3 X8 f4 Q( d( u; T$ L: t) F 6 d; T. T5 k) q: C0 d
! N0 d6 K+ T, P% M3 O D. `+ W
! ]0 x$ K7 R. o6 ~/ r ' t9 N1 i/ K5 b$ D+ p5 n8 a" i& G
) A* M4 v5 Z0 `" c- v
if(is_file($file)) file_del($file);6 _5 w: y, y/ l+ B9 _% {
0 l) O/ q8 ?$ D. a) ], N$ d
% x3 X2 o7 d# b I( {- {
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
7 I7 m$ G, B% g& I! {# t 7 L8 o/ r& E) T) ?$ ~ L
9 ]7 q/ c6 ?5 h5 p3 V9 y6 G5 i 9 x7 r( o8 a8 M+ s! @' ^
' e( n6 U1 F( D4 Y6 E! {0 W2 t$ w; ?
8 j- Y, l- X) i& v! r
$upload->adduserid = false;
& ^& V% I7 b- g) M; R5 {: X
5 d& g- k( F! W& y4 E) t" B! I4 A# \5 R' w6 L" M5 [6 m. `
' M. g& ~3 R, `- n2 v
! b0 s" {. y" |0 K9 R( |# g" X1 q; X# ^& i
if($upload->save()) {% u. C# c- }/ Q
6 t% o: F+ N3 v+ p6 f
% p6 W8 F8 F: b; v ? ...( h, W" Y: `; a& @
1 @: |# t# b& M! r0 u4 F4 @2 M! s
8 n( c/ E' s- `( }* i
} else {
( Q! Z( I+ u0 G6 l0 u$ E ' y. u0 x' H- a& {% j+ i% r
6 G- O; v" f: v# L- J! p ...! O4 |- K; Y' J" x" q
1 \$ t* I* k H7 M+ f, \- }
# s. s$ l2 R: Z }
% e+ P1 D& U( \4 k7 q2 \: j- } % ~1 u r% u# P( I4 k D; g5 K( K
" @7 o: ?/ r9 u. @
break;
3 H" W/ X6 q& W. e8 n& @: v
! @- a- ?2 h# g) f I1 K2 |. \
) I7 F L' o+ M1 Q7 h 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
2 B6 v' Y r. n. F 6 N5 j& {0 `) o8 J5 V/ s
( y C d4 P9 u* M3 h5 U( L3 l upload对象构造函数如下,include/upload.class.php:25:! y1 v( J& S4 P6 @" y
2 x6 f# _2 ~4 b M3 C- Q: ?9 X8 `+ Q5 ]& a7 w5 c& ~
<?phpclass upload {# P$ t) e- ^1 c5 c3 b9 l }0 h
% {, p+ R: T3 H
% O+ ^3 i* g% i% I5 B) m4 r function __construct($_file, $savepath, $savename = '', $fileformat = '') {
$ _, d. r8 I" } ) e# ?; u: D1 C I& R, e- v* N- l
, Q0 O8 U0 ] _0 |0 y! k global $DT, $_userid;
. W6 Z+ a- s5 p6 V7 q
2 U8 V& C* U2 S7 M. L, N3 x; q3 G1 j6 J7 [
foreach($_file as $file) {' y( u5 ]( V! A: a
+ `: e# n V$ L& d$ Y
8 k' ^! S+ i; a/ a# P+ B; ` $this->file = $file['tmp_name'];
- ]# [4 k4 u: T- y , [* d% d/ {* Q
) U0 y1 E; ]9 R; v2 w
$this->file_name = $file['name'];
& h0 `1 J# H' B" M- t# E% o5 k 4 w( k3 E6 ^- n
0 [3 ~/ E9 G# Q $this->file_size = $file['size'];
- E% D h% i7 ?9 I/ e! R; | F . U3 k9 S* _1 y. s8 z
5 L4 r( _& j+ R, ]" w $this->file_type = $file['type'];
# P% ^ a' L. d5 g: \: d, o$ E% F
" l8 U& Z$ b- Y$ a) V+ M; [
# G1 P& o$ a+ c7 l, p6 w $this->file_error = $file['error'];+ o: l8 U7 z1 \
. G* U+ c( W' l* R6 i6 V
4 A* ^9 ]( D( o0 j
0 u+ I' @+ V5 [# a' V6 ~
/ z6 D# D7 L- K. _, s/ Y h: `$ [( y9 d; [
}& w0 Q. m5 N A7 o) X0 i! a
2 k/ D1 b0 q9 ~0 D- h9 g
% |: t9 q r" h- H$ w0 Z$ P. f( I; V $this->userid = $_userid;# o2 ^& u9 V9 ^2 k$ {3 u$ d
3 L2 ~, r3 y6 [, X7 s4 W0 R$ k. `
$this->ext = file_ext($this->file_name);
& a6 a8 p( P i4 j$ \ $ F1 D& |: i; j5 z* S0 Y
" k+ o6 Y! f5 O0 h0 L/ [; G! I
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];: {, n9 k( E- ~) h; q
; W0 ?& Z$ |2 \4 E
* ^8 A& y. k# l0 b5 M: @ $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
3 A4 r' M0 V4 M; j# k; _ D
, N( K C# F2 ?1 T) i6 j
$ L) e3 j8 d! A/ ^- m/ Q $this->savepath = $savepath;
! P$ I4 n+ Q. K) _ ; H! D$ H- A1 _- ]( @# X' O1 D
( k# x: Y% S. a) O" `5 e( D. D
$this->savename = $savename;: j- H) |! n2 X! _
+ v' {7 f1 v) V' A) p9 J
3 c. m: N, _4 Y m }}
2 P% a" i. k# F4 D% i/ u; U0 t1 Q" R 5 i0 @8 K8 `8 i# B% j
$ N9 |- m5 ?( [# C( j- X3 V5 [; ` C+ q 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
8 d8 u5 Z# G' w/ p1 C1 H5 {
) G# f/ q% I( E8 ]0 m+ `/ F6 j1 m" z: Q. @% y2 H# t
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ' m2 s( ~4 n2 o
0 P, B0 R* g: [3 n M3 |* \9 v Z3 ]( b
m' t! K" N7 i# i: u $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.php0 F0 d) ~; {: I) R9 \
) o6 ^/ z+ c, b' ~5 I; q" s- `0 t( E4 h4 e
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:- V& k0 w) `) u/ [: c# ?4 ]
6 I Y/ @3 _6 f: G7 p' Y& T A. A$ v3 x1 |0 c( L
6 R, r& u% V/ C. |
$ r) ~1 U6 ]: O( q1 E% }
3 Y3 ^. T0 f. N' {3 V 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:4 l: N; x: O) ~/ C
9 f& j& G8 K) y6 `" f: J' d
8 @/ o8 L' J/ x" A
<?phpclass upload {' p+ V( `# W" E) J& \; Q
' ]7 x. J# Q) X" `* e% v
: @, q! y$ A5 W3 C function save() {
; V! `& N8 W. X* v4 r / q0 s& U( Z+ A9 x+ s& s0 j
1 e; Y1 b4 {5 u! ^7 y# r" s" M) I include load('include.lang');' e4 o3 M5 \+ W) o- P: U
' K: W& L9 d9 Z: u5 F! s
3 b$ O) |( A9 G$ g if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
- \; V, ~/ y' L % t( t9 G( o0 w9 r
6 R* M. y$ Q% b! I' I
0 S1 \" S7 c* P* R# y9 J( ?" h& _ ( j: U$ o9 B, x
! m2 ~5 l H# V; Z( S# | if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
1 r: i$ m5 B3 S5 D
1 a5 U! h7 X \0 y1 b& F& K2 v
5 b/ l. H9 T6 P" H" s
& X1 H" o% o3 ~+ G( u
6 [/ I/ R* i" X9 Q% P3 |% @/ p
0 t, r- g# \7 ]* ?- e4 L if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
- V' x- d7 Q' a ; ~1 H9 C' T1 G% O, J: H: e
! ^, b5 S% q& U L4 Y$ Z2 P/ i" @7 i( d
3 X. U( Z6 \7 S5 M
. h8 L! h: f+ g2 B: J- a' L% w; |% B# S$ b
$this->set_savepath($this->savepath);; d* w" j" K% _; P( Y
7 ~: v0 C' O% d# [$ u+ j
/ A: u6 @! ~# H+ q4 N% V- ^8 d( S $this->set_savename($this->savename);! q* Z8 Z' @4 K
0 ^+ p$ n9 w% Q; q! Y
- k2 h' Z% ?" n9 t* X4 L! B
, l8 H2 {: s" F, _4 O) b/ ]) t
1 z; G1 r8 w* g7 G( m& \3 R
0 W% X8 j8 _2 G" b) d! L0 R/ @ if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
9 b. K9 u! d1 i3 W8 ? ! x4 u+ k3 L h! {" h) n% n
3 _( c$ T; I; J- g, j3 G9 n9 _0 O
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);" V1 o9 P4 W3 j6 T& z }) U
; l. }8 B" @2 ^8 I' a
5 B% I& q7 }+ k, E. Y, _ A
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);8 Y1 T" }- f2 o" L7 i% y7 _( g
3 ~: F& l* Q; ]% T
" k0 Y% T! Y7 j* w7 B. @; P 4 @( {. a& h4 z6 e% B" ?6 V: W% |5 N
) R8 d6 [) g [: t% l8 w
4 p: b' b, k5 @) q4 J" e
$this->image = $this->is_image();
; f) m) e) w. u4 R1 c
- j$ Y( o; o8 Y
3 i* H7 F; S8 m L, {2 f* M if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);. h# s4 r2 P( ]5 J( G; r
( x( S( T( F; V, u
7 X2 l0 f& }( S, \2 Y A$ x return true;- H& S1 Z' ^1 A) H( U: F2 L
; t) s% g1 U4 {/ x6 \- r
! R: X& j3 B4 ?7 l# a6 g' e' g; v
}}
( | \& c5 P- P/ H# t! k
! C6 P$ f8 A+ _1 j2 w0 S2 _1 H
" `/ j' D' E: C3 V. K 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
5 l6 w' z2 b/ e0 j! [* B & n6 [, u A' o/ ]5 w. Q
9 X' {; x' u P$ p
<?php
, J N5 g% E- i, V7 h, v
$ r% _3 c7 Z9 ?9 D7 o7 X/ n& f K
function is_allow() {* ?8 X7 v1 q# N& `, [
0 n6 Y- E( @: H9 K
& W0 D. ]: g3 p! c' }& D$ h5 I
if(!$this->fileformat) return false;& C/ y/ M# F0 g1 h, k
- L* s& J. O7 \; _, w$ m( W2 R9 v- G& L/ J- [# \7 v
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;" y* b; b! F7 I/ p/ O
% E/ H( r" e* e4 b& L( |6 X5 C2 k
% I8 x$ ? D1 `! g 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;5 E3 F. _" \# v( I, t# Y
7 c0 g: |% E3 j* {. A4 p# s/ Z
0 B7 s% P( S" d& H return true;
4 w9 D* Z3 C5 x8 i( c( j
4 W: h" N4 u, B: h5 i7 m; c, A. i+ c/ d
0 G3 l6 t, ?3 d: v }' d+ x3 I6 Q q5 q7 J1 z; x
L1 G- ~3 ~) ], k u
: e! L. V2 E" |4 H7 s 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
3 ~0 T. x1 @( \- W+ S8 \) |. M0 M ; C7 T5 T( Q% ?- `
# }+ s( Y" l) D7 ? 接着会进行真正的保存。通过$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) i0 H4 n6 x* o1 o* M) \
6 G/ n9 d$ X# ]* q4 j- r) d% k( h$ j- B* G
漏洞利用' C7 i0 C6 [# m4 Z9 R, U
% U6 f0 f5 R! ]& Y( ]/ j6 E" w& y/ {# `1 J
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。+ L. r4 r& y% e5 Y; u
& g k& j! G) [/ P8 }4 ?+ P2 A. t
; x% {4 F8 w+ M K1 V8 \; ^1 Z
z5 G% v* a4 r/ N
9 b5 W u, P, Y( C, }1 B 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid( Q* X. Z z, `2 e s5 l5 U- o
9 Z) c$ b5 y& ^; V
! y) n2 ~4 g7 _, r
不过实际利用上会有一定的限制。6 \' b( G; i4 D' O
/ d* M* H3 f' f9 m5 y7 {- B
2 V' p4 o" n; O' ]/ D7 ?5 T/ G# D 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
: `# d: C* j, e L: v7 [, ?* A ( S4 z2 q v- [" H
6 b# N7 J; O' I) o: g7 L
! u" |& F4 {0 d# B# M: }* n& B8 p
5 o# ^' X6 s' d' o% Q
$ Z: ~' c; F7 I0 F Y- ?1 R 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:. a9 ~4 K( V c2 ?
0 F) ~8 m' [, Z( C7 b9 e g
~ N4 E6 U5 j5 A, A1 c 省略...$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 y* T* d: s& e/ x/ ?
5 q$ ~4 B. |! u$ f, |) S
) s8 {, \! g/ ^0 _; ? 因此要利用成功就需要条件竞争了。
8 n/ L y, `0 ]. ?, C
8 X. @, f+ v( |+ z, T9 c+ K
: F* X4 |$ q3 [& j& L 补丁分析
6 f+ }3 T6 z5 @* U* l9 r' S* g' Y2 {( r% E6 X
4 l- ~" @: x3 a& X3 O! G
& ~- O2 K" F. v$ v7 ?+ I* k
1 h# g/ i% r6 N* e/ f$ j
% e8 C6 v- V( k4 u" A) E0 V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
6 B! A+ K' O0 ?9 b! [0 q' x- z7 r
0 {; F8 \2 u% n7 s$ Y% m- @* H- G. a$ D
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}, z0 e- x4 i& q6 k# i" R' N$ T
1 n$ b* v1 w; s$ U: N* O" [1 g" c% ]- ]. q$ O& }; B1 V4 X: i
! D) Z4 |) l) D5 T2 t% i& n
3 V) J# u' v* U A3 F) v I
. K h# ?: a9 |; `! I" S0 W 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
/ O# m8 B4 g/ a5 v1 G0 N
) u% s" t8 R* U" d+ j* L. Q# F! F& \6 H
在is_allow()中增加对$this->savename的二次检查。
. J5 E0 D1 c7 b
* ?7 f+ B7 d) f+ X% ^
$ D* \ T4 I5 U' Z- F, _ 最后5 v: \( v" V; W: _; @! c* u
9 R: B) I4 K- P5 h( d H( `
0 G! M* D2 x/ F5 o 嘛,祝各位大师傅中秋快乐!# G- ^; u( ]2 X! K1 c7 c8 W1 U
, n; K/ v- C, Q* V8 `1 R& Y
! l) ?' H, K+ Y" k; U0 @. n6 [
, a$ q g5 K! P# U$ H
O* X% v# B1 b) `
|