, L# F: S, \) ^5 m( o2 p; b
+ c7 w) Y' L8 R# y. ^
' }& ~. X0 q4 s( j) B& O* _) P' r) I, m: x- \- J2 s7 ~
前言- p4 T- s8 Q6 m$ Q0 }7 Z5 U8 D4 ^
5 u3 D/ p l+ }; M# g" j
* P1 C- z( i' m* n6 v7 u+ ` 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
; m0 e4 a* ^7 k5 }
' g h3 [) A: N' d0 A% P- Q* D
7 O9 X( [- X- a$ S9 Y1 _; d
# p1 S& N" H, @/ t Q1 H + e2 }5 x) j4 Y# A- y9 ?) k0 B
9 D" M. B# l0 t; v
漏洞分析
. ]5 N1 Q$ d/ }7 ?1 {
' }; C5 ?, L9 Z/ J$ n5 o0 P H2 l2 C: C! k* {
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
( W& K: m8 x7 T' o0 N' E : l$ N% h* K! V) v1 r4 C# o8 v4 _
: \+ v2 R5 Y; e7 L) v$ X
' W3 K/ u: T) N 7 K) L: D: ^) a0 l
) _& f+ o" }, ]6 v! t* z5 G% z, O 对应着avatar.inc.php代码如下:
, x1 R5 T% M& o" Z; p5 e 9 V7 ^: H' O% h% C" t$ z( l
& a) y* q" m* 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) {
( G5 m+ M3 V. M+ r- ] T4 o
& S- C; s) m3 c2 X; O
8 B) T4 I) J$ c1 f& J2 I% o case 'upload':
6 E7 _- b. ]! K7 j" j/ K j6 A
, ?" u% I3 }9 R7 l3 y( c) }+ r( [, j! f+ y( A7 e% k3 S
if(!$_FILES['file']['size']) {/ q* a/ u' C0 Y2 `
7 Y1 H/ m& I7 S0 `1 }
' h2 C% ?" v4 n+ y
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
8 h3 [# K1 a' u8 p0 x
e' ]/ L. u7 C$ K" z' i# r2 _0 R4 _8 {" Z2 x
exit('{"error":1,"message":"Error FILE"}');
* \0 d; o, F: `
1 c3 U6 P( W9 \, W1 a* J/ P! _& x! ^, a9 x7 [# D6 u8 k
}$ K7 O4 {9 C4 ^+ C5 U( \6 d7 J. [! a
2 _0 r& W" }; I
" |, ^) @; ]+ C [- p& H9 w require DT_ROOT.'/include/upload.class.php';
3 D5 r' _0 W* K7 s* }) e ' b, G: O6 T3 J% M1 n8 R* J* J4 g
/ [5 H: Q& D) {4 x5 d 7 L" z9 E6 n+ b
' _0 Z8 Y) R6 Y4 N
, W4 I9 u: }, t, h, N! W1 c $ext = file_ext($_FILES['file']['name']);
( c+ q6 l z) X! o" t 2 C" l! B! i R5 `- [% t! S' D
- I% p7 G5 m: y0 A( g4 s+ g $name = 'avatar'.$_userid.'.'.$ext;4 |& a* g* I4 o
. z! M4 Y% t7 @* _
9 |! F& q! _# P2 M. w/ X
$file = DT_ROOT.'/file/temp/'.$name;/ D0 \7 v- N; z5 }
- }9 x7 T' T0 a0 f& Q
% Q# M0 l' e4 E' ~" `& C
( p2 ^& b. q- J: U
# F5 @( i" S2 E
! T1 D/ ?) S8 ]" g/ S
if(is_file($file)) file_del($file);1 H0 n$ q! A/ W. ^* m, `' H, u8 o! y
# T8 u, O: j) M. [; c
& X) w' t. R9 f' Z: n
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');' H }2 _( ]) k. |
, s+ T+ R+ p5 z3 C) `
4 ?' D9 m! \$ p0 U' V8 w: f9 {' B
5 q0 b) l1 |: `4 Q# ~
/ q$ Q6 L5 a7 [
, [1 W x0 ^, t5 \ l+ B- K/ O $upload->adduserid = false;
: Y3 z& u$ Q8 Q3 [8 X7 w
& [3 m& P- {- \; _/ m
/ _+ H' Y+ `. A9 ?1 ]0 N4 E
( W; @, t+ M r; E
4 d3 M) L! i$ l7 m. M7 m1 K# e |9 i
& B7 j% @" @- m/ f if($upload->save()) {3 z# f3 r; l5 e; J6 u
9 D3 `* s' N. m5 N7 h' S3 Q# {5 T
* y1 W1 d& v, I4 W+ P1 R z& X
...
/ G6 ~; L& F: C, | 0 W6 g. d/ i' N9 D L' ?- }
) D, i# d$ d! I2 \/ I; B
} else {
9 S7 \2 W$ h7 @8 S
0 G- s1 m- i* H. H& T, x( B" ?: g- _1 c1 \( p
...9 o; s, V) F8 O& Y x7 F
+ z% J7 j7 P0 h+ g' g: h/ [$ y2 H% S. N3 n/ Q/ d
}% ?0 V- y% W7 h1 s5 Z: O* K7 i' y
5 @: e# s; r! |& W. ?
% U* |" @( x; B! P. R
break;& H% z- w" ]; z3 L! x: r; C9 a
2 O; S `7 U' h
! L) c) `: }0 z! V 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。# H# `& \ a; V7 S3 C) y j( X1 L
0 e$ v3 L( Y" |. q4 { D: T+ q/ g$ q# i. Z2 R" t7 s
upload对象构造函数如下,include/upload.class.php:25:4 t$ Z6 a- o: e/ r9 O! v: p) \
2 m% _: e2 u( d8 T, u# S4 ~% o
! d# a6 p' |# G+ k/ F3 g* A* ` <?phpclass upload {" m7 ?7 h: l# S* K @& w
2 g3 n5 Y# P# _
; ?2 y8 P( S. }" X% ~* d( n function __construct($_file, $savepath, $savename = '', $fileformat = '') {
# `8 S; ^2 e- x, t
7 f% G: o0 R3 a! ?# T, x% B* b' ~, r7 F0 u
global $DT, $_userid;$ S3 h5 Y' z. ]$ m1 s
5 ^8 f/ ?( \$ ~3 {6 L" C
o5 a. ^& X3 I% ^5 ]9 k foreach($_file as $file) {
/ O6 h% x9 ^& }8 K 9 v" N0 [6 Q0 C# m. s
4 `$ T A5 ^ A; V5 l+ n0 Y: y5 E0 l
$this->file = $file['tmp_name'];
( o$ E% }8 ?$ t2 _ 9 g+ t' v7 a9 L0 h" h
- M% w, H/ _2 s' x I $this->file_name = $file['name'];
6 [ g. Q% Q4 @! S
4 G6 B1 F7 _: Q3 f6 D( ]0 s1 s( X: I, {6 H$ s3 r% S
$this->file_size = $file['size'];, t* Y* P* |2 [! t
6 {2 V8 t0 h% `9 f
1 B! |5 c# u i; ~) r- T- R& I $this->file_type = $file['type'];
1 { z0 ]$ p3 U0 D
! O! @% ~- T/ E7 _" i- B6 n$ a9 _* a( A) ~( J
$this->file_error = $file['error'];
0 d% `/ _; A" j ; H8 c" R+ P& }- b" ^9 ]
" s* E6 s) D8 S
# g- n9 ?! O I- B/ w9 ~" @8 S* d; j 9 ?4 ]: j7 U" f n7 ~
4 _( M6 A3 V* ` d$ u( O9 h. ]* C ? }0 }6 A/ r5 f" v, @+ s! y
2 A4 G0 a" P9 o; K2 X0 A2 u
* r: s/ ?8 @+ ~ $this->userid = $_userid;0 s- i" l( V0 R
+ k0 O5 E0 m, J# c q" Q. R' Y' }" P6 @9 k; G7 ?
$this->ext = file_ext($this->file_name);
+ v: G# k% E9 o9 ?& E6 X1 W; ?7 f
+ r) J& p' }, u) E
9 c! b, h$ |1 I; [: z" _4 ?& ? $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];8 ^ A: i7 |: Z+ p
5 W! c" D; Y. b( k/ K8 I
6 J2 K9 `1 u; [2 W' ~. k7 v- _7 m8 }+ i $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
! Q2 X2 x5 G8 I9 h1 P& S" m 0 @& Y* t$ ?) N( z K5 C. B0 B
+ o# s; K) R" S
$this->savepath = $savepath;8 H2 n+ Y6 R- m1 F: N
- S- P$ R2 G2 \1 V. o3 b/ Q% ~$ g# [# L
+ v8 k2 T( @. U: w- K' t0 e* t9 C* ~
$this->savename = $savename;
5 j4 Q3 X( b9 I9 r* D2 [ 7 k( U) R- Y% U- L
% j3 w1 \1 ?0 Z9 Q
}}
D' A; C1 ?4 e. L4 d; | , z9 \$ s; F9 k# N& T) u
2 b: [ W6 d6 G6 d5 k) a 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。8 e; H4 `7 r& A4 b- ?- l; Y
5 N* }$ Y# f" c; A7 s! S# |% c' C! h" J9 t. W! [
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 7 _$ @! Q6 r; |0 L& H. b' m8 ]
4 z8 U* U5 ?( l/ w. i, \0 f
z7 @2 M6 C$ C# ` $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. k: u, N; _( p0 l
6 D/ Q- ]7 A6 o; K; j" S
( W/ Q$ u% ^" ]0 v/ Q3 w: l5 Y 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
3 B4 g1 |) D) y r " V7 v, F' p" L+ ~# T
* k" d! N) P& ?$ t. n5 A$ w " I0 j/ e8 s3 }. G! @( P
, {' r3 N: }( s$ Q
. \7 ]* u. f( K( N' @ 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:% s; R- U- B/ s5 s' j3 c o
3 {: s5 t9 J* B. g
& l0 U+ }! ~' U) }
<?phpclass upload {
1 K5 P+ t* T9 X3 d& } * Z' m, B8 O' O" d5 c/ C1 q
2 H- A1 S) r2 M: k* ` function save() {' x0 }# E- F- O& W# P
6 {" D2 i2 K- a) |0 {) X. J- H3 G
6 {- Y( b/ u1 t, m! E: y% d3 \( u9 H include load('include.lang');( d" O! w4 v8 j- |+ j; _
+ K& {+ {. W4 @% O% S& o, A. d/ Q) Y% L7 Y$ _
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
+ R7 g1 Z6 h( G& s
# T4 O1 L) H" N9 B; J0 n5 y) |. }+ E- ~1 u. E; @4 h3 I) ~
( c: C* F3 o. i; L: a6 c) l
l6 h( X8 C- s. A& D
+ J3 |+ V B" I* E' T
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');5 N; d A4 }, q) Z9 u( S
: r: k. ~% I& y/ Y: t
% D( D5 x0 g' a. O! X/ g7 ^8 ]
T# q3 y W# r( a) d8 y0 v @6 G( R. M3 @4 U" T/ e) n
1 s: A1 t2 [7 W
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
! \$ U& ?( `+ c' }3 g2 z' `; c
4 W* j7 N$ W( T H1 G8 B+ ?; A' T- ?3 G b4 d8 l+ Z. p
4 u8 v5 N0 Z* u8 k; c0 M" | 0 E% c( H4 F! |( Q
) u& S9 H% U) C' m& U; l* Y$ d2 n
$this->set_savepath($this->savepath);5 q! O& J& ^. l
! g3 u$ l6 G" r. o& k5 U4 j0 b, N8 y0 Y" M. B
$this->set_savename($this->savename);
$ _( Y; V$ b, |% j ' l$ g# y: G6 q" ]* ^1 b2 v3 s- L2 M
6 W# r3 O: N3 D: I # n+ g$ @) Q. ]* m. f A" U
1 i2 s0 @2 a6 B
( f `! P( e/ D2 m if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
. {/ Z! l8 e8 i: X8 k1 }
- P; y. {) d+ t" q1 V! h4 P; H1 n0 q5 U2 e! ~" v
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);8 K4 I* t3 K' ~# \5 U
9 s0 I8 \( T: \: Y# s3 m
1 n% O7 O2 i9 I1 \& B: N6 I if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
* J* [0 S4 n, G ]7 [! j
$ {+ W7 }/ v8 ^9 n6 F+ w$ w* h) O4 v: g
/ _2 v4 V6 `/ K* Z0 [4 p
5 D j0 D- A7 T) g& O4 k3 N! ?% R
0 O1 k5 I, P& K- d $this->image = $this->is_image();# Z8 F5 U: |4 O1 f& D+ T
# p# r! o( r/ Z, o0 r3 g
+ r: ?8 C9 I+ g if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);7 z& `/ Q z3 I0 B% A% m+ R
, ~- b2 q# ?6 p, Y8 _: [- K
) x& H$ M0 i# t! b/ N/ {; z2 [# N
return true;( C# `' R+ ]0 K* G& k- Q
/ X5 d5 d( ]! b! {; s* ~" o
& u1 \3 T9 e3 x' V; g! C }}
. o% r% Y V1 ?! L$ I8 x7 a( j 8 Y+ c0 K6 _. n
% P; W# A: q$ B1 L: t8 o, t: A$ k 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:8 R/ j" A% Z$ W( ?: M& T: r
: P* f' W2 `: } d- o
% C' \! i1 e' B0 r <?php$ l, Q( n7 t( \- D9 C8 F# b
& u, h% B; C# x" `8 W& X% A% k
9 k' f; _ n/ X+ `( |: t function is_allow() {
( {$ W0 m9 a2 J6 T+ U0 q 1 F6 T G; {/ V j
, ]0 j! r8 k' w& W if(!$this->fileformat) return false;" B' q: Z: b" p8 R" a$ I
+ X! |; b, @) n' Q- P
4 g. V* ?' E7 B if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;% L9 x7 z0 z# s/ X) h7 ~) z7 |
& S+ u0 Z3 r8 w) S% T% [$ s6 c
) `; R: o, l. E9 Q g F
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;: j1 ]. m6 s# e
0 I. M1 n, b) I, |. e; r
$ Z2 ?3 o1 E, } return true;: `( g" W( n! n- m7 Z+ ?: t3 z7 k
: E! s y; T+ I8 Z) j& E
0 k) b1 J Q" ~7 f# G4 T6 v/ J5 @7 \
}) Z& E d% D0 f* f4 r8 M- I2 X. R. f
c7 Y# n% r. `7 D I. U
$ r7 x* h7 P5 z
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。* P) k) c: ]% u: @
1 ]5 l: m! C8 c9 h/ @7 ~0 R) x K
5 [3 w* L X; X' E$ 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文件。
; M7 m- N6 W" c0 E0 `0 F! M6 \
2 b; W6 ^/ Z7 v' o7 l% [4 r: g M) y- l
漏洞利用; P0 Y" ?2 r( |% u2 J6 h
2 T7 y- |" u* I3 X
8 ^) G. O; Y+ U
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。/ u5 P" |. c. y" P" l: [, i2 Z1 R- b
0 l- z6 a) L9 Q" K) d7 u
' Z5 d2 B6 `. i9 k% f# P" s
p% a X3 r8 |2 C8 F' ~
$ u6 d1 c {8 C5 h, d. h* S
7 N+ w' _! l* T- K 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
. x: I B& C1 l% @6 w. E+ I1 i * r3 j3 [8 E: B G; G7 v0 K
" p* ]1 `8 Q, Y" X: w
不过实际利用上会有一定的限制。
0 a8 i; c' r% x1 _4 @! k' ? $ D& {, f' ^ R: O; X* h6 \
) U ^! w# ~8 O& ]( } 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 X& u+ X+ u# E+ j- O5 y
0 G/ B5 i" T$ o1 m( c
. B& e3 F2 h6 K5 E% Q. M8 B
* G' G6 k6 B. D* M* K
0 q* v, A8 Q7 M0 G0 v9 P2 c& n6 N
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
. Z1 g/ D3 _0 @ : A8 K% n. Q3 H& ]
$ M# g- Q. k4 h 省略...$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]);省略...& c# \0 ?& y" g
1 p. d0 B0 D- X" |" |7 v" O( ? [4 j2 E' j
因此要利用成功就需要条件竞争了。! Q' v( S3 X- @
?& S0 t1 x8 t: O6 u4 H
/ s" k( w* \& F$ O7 z7 `1 f, z Q1 Z 补丁分析
. |0 M, ?7 L8 ~9 p" _8 a' M* V9 Z, z5 L- v8 F* Z% @
* s! B1 q9 P9 D, ~/ O7 z. K
2 @8 Y4 j0 y' H* B0 _ 3 w6 T" Y! _# m9 a4 l- y; x. m: [
! N6 c! G4 h7 J; v. P3 H% F# E% l
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:$ }& k% Q& N* ^1 `& c/ d3 j
$ d7 p0 z6 g/ d* a6 p% e; J3 A) Y7 m* {
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}. q7 K+ B+ J* [2 V9 x$ Y
# d0 Z; d9 A0 K N2 l! P) {
6 W. ~2 b% `1 p2 s" ~. [4 w
0 z# b% C3 {3 U) E- M4 P % q" q/ D; g" u
$ p' J1 u/ ]' K0 q* o0 @7 F
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
8 K$ j& i2 h L
_* K: X( x" U5 F9 k) n7 V9 C, u* J3 J# N3 g
在is_allow()中增加对$this->savename的二次检查。% J4 X3 g6 L# {. V5 s& a7 b
7 B4 r. [- J, A
* O9 [7 |! T6 |0 b: J* A
最后: R o) \/ o) R6 f' N
1 V( ?( N& c' F$ @8 z' c( L+ U3 L8 O
嘛,祝各位大师傅中秋快乐!0 I: H* |' y' A9 g4 d4 A
4 u! r& X$ B# _
1 |7 Z2 y! }8 E' K( ^6 \
: M# y1 Y, V/ ?8 A4 a+ C
) e d* x1 v S. z' w
|