2 S1 C' t, f: r3 {8 l" u
7 w6 x7 l6 |0 A
$ y4 }' |. W7 [ u( z, T! d5 X# ^
y8 E; A/ c0 Q) W2 Q% \+ l 前言
. n( B) {3 A" M% m0 G& k
% i8 w& t" k3 g) L4 s2 z h/ G, h8 d' j
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
% p4 B9 x3 `% }% ?
- \& U& g d' @6 |6 f
" Q" F8 |, L% \+ o4 n3 [ 0 \$ e; \. ~, D* n
) j* M, N+ _/ S% W8 @' I$ ]8 `2 W5 h" I7 i3 {# R1 o; p8 }
漏洞分析
9 V7 B% g4 s6 e- S* {9 M
" o* Y' o( A% @# l9 ]
) t; n, A5 e( |# O- j- S 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
9 P+ }9 m2 y0 a! \9 ]. T
0 B3 p& N7 |0 q8 |2 z' D- }8 B9 f* c! R1 m
% k& U4 F; L# ^- l9 U. h7 ~
8 A) C! |1 q8 n" J
5 k% P% {4 Q5 i, [0 G8 V3 Q 对应着avatar.inc.php代码如下:
) W) p" m0 k' X4 o
. ^8 Z: y" O4 d8 G1 b0 `1 X) x9 S/ H
& C$ D1 w3 w& u# h2 a% Z/ x <?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) {6 @5 q, y5 q4 K1 E6 W/ ?; j0 O
& ^, s# D; g6 s4 Q6 Y4 k3 @. S g1 c3 s
g% u- s! i' f case 'upload':5 J, |( Q+ S ?) E2 @6 [
' z9 Z3 L. \2 a& \
! u2 A4 [4 c' y6 C) f9 i$ B if(!$_FILES['file']['size']) {
* u% |, [4 H# g+ g
) m0 j3 c8 n/ o. D# y9 T* Y+ o# s n8 K6 v4 K+ ^" @2 e
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
& B- \: A: v& p B: s * h2 i6 E3 O% z2 G- o
+ t5 v2 T+ Y2 W' W exit('{"error":1,"message":"Error FILE"}');2 `- k3 }) Y: A4 \3 x
7 l. \. L6 L% t! Q! v$ @5 u% T
$ C% o) t! [0 h: _, Y0 z }
0 D- c/ z5 x7 W8 g+ X " {- y- |/ a1 x- R+ N) _
3 `& m) G0 j+ h" K6 _ require DT_ROOT.'/include/upload.class.php';
. K! L/ D. [/ k
0 Z- v+ H3 c, w- r4 ^, O
. w" G. _& x' ] F
# T7 D4 a2 E$ Z8 j" E5 W # K6 {- j6 L6 @ @) v, c- M! M
) l# K0 a6 t D0 d
$ext = file_ext($_FILES['file']['name']);
( x$ }1 Z3 `3 N, Z - Z) `; k8 k% m& Y2 a# e0 \2 X
+ o# @7 }/ ~1 Y8 @% G $name = 'avatar'.$_userid.'.'.$ext;
% g7 q3 F4 E! E! D " x" {" m+ @! j' e
/ p2 Q; ?2 v) M Q6 z: ] $file = DT_ROOT.'/file/temp/'.$name;8 k& O" O8 _* j! O0 [0 A; W
4 Q* F* Y, i W. C: b! o+ u" ?. p0 C3 @$ ?/ p6 A
q n) O; V. {# b* |
- _5 C: v' C# A) V. _1 l7 f
. ~3 B1 `7 F( m if(is_file($file)) file_del($file);
: S% a4 T6 U/ l2 q* i8 i 4 @' R; o$ B# m
) y) n3 b2 H$ ^: e- m3 o$ C( n& e$ K3 h $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
7 s- y8 l# ]5 i( d, ]6 s4 S 2 e& O, ]# M4 |8 d& v; b
3 m9 J2 q6 e' b% u, d
4 m7 I# C5 D9 X9 W3 z: c3 |
) K3 u# _1 G1 b9 k- o6 N+ X6 T \! O3 F" e6 _. [4 p
$upload->adduserid = false;
0 x4 P4 Y% ], Q0 C- _ : h2 ^, P& Z9 h: ?
& Z7 L- B4 I! c; J! @0 c9 e" ~
( m! P# C% e3 K8 {: S; {9 ^9 h3 S 8 @0 {: o" ^& @
* P- m& a$ \5 X" x if($upload->save()) {
) _4 L& p5 K) E$ P + j H3 [& a/ w/ [& b# v# z
7 y# p' \2 c9 j# D7 K: D6 `
...9 c6 Q9 z' t* f- G2 V% o3 l# V
7 ]; S- F- F* c3 M9 _
( @/ \8 D! z( j } else {. r: s! Q% N5 t
( [2 _! j' h. e
! }0 M8 w2 I% p* L9 t9 @ V
...7 [# R0 @ {% E2 F5 M
4 E j1 U( `$ ~, _5 F/ a# c6 J) l% R$ T
}' m+ s1 a) @6 x X; ?8 u
$ V& b7 e. b# q8 S2 q( Z8 P( y1 L2 g; m+ Z/ T
break;
, ^2 a# E% V' j: O" n5 R9 H 8 T- K) Y# v2 G1 b2 h& N
8 C. Y& Z; @" K 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
( C$ Y8 Y* X1 x* F7 V$ U8 [$ r+ }
, ~ x% J' N$ k, `9 c) j/ e6 l: K5 V' i% I# b
upload对象构造函数如下,include/upload.class.php:25:* q, u/ ~" A# @6 }0 J- ?; m8 v( H# I& i
m2 V4 t9 n; L4 t" E0 Q) \
! Q! W& E4 K, r
<?phpclass upload {2 A: c0 ~6 I7 V8 V
! y1 c/ N, E6 X: g' n
- l# n2 P" X. i" [" d! x
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
, T- ^5 a+ L0 M$ j# E
# |+ U) y9 J4 z. j% M z! k3 ^* G: E1 s; x6 d6 [0 N! R3 F
global $DT, $_userid;
6 o. q9 Q" a3 c ) L4 A. t# T$ k' k
8 H' b) e& D6 p8 V5 P' r! Z
foreach($_file as $file) {
' m" Q0 w1 i3 O9 v: ]+ z # `9 c7 X& E3 i7 d1 `
; g# h1 T5 \; r `% [8 @
$this->file = $file['tmp_name'];9 b9 |3 x! q- W$ _% |* t
: v) F- J+ B* O2 J# a
7 D% I) Z6 `$ Y) t+ W+ S$ Q; ? $this->file_name = $file['name'];
+ g9 z: {4 H7 y) w & G# ]7 i9 i! S' |1 B
x8 f- p, q1 r7 m2 W7 X& J+ }
$this->file_size = $file['size']; T( [/ Z1 K) M$ P9 Y3 z" N
! b4 C9 Q* R$ @1 l g5 b4 ~2 n
# j) ~1 x; [" b1 W# g2 f; {' b$ b, y $this->file_type = $file['type'];% T$ I8 Y9 u; N9 u% G0 _& W
$ |) h" N5 W1 Z6 L# p8 x! _2 O
: V" S; e6 @ D, Z" K" f6 {, c r
$this->file_error = $file['error'];
$ A& S) u3 \4 e
0 R! L% p/ K O' k
" q6 B- q& V: W. v1 [
3 r1 M3 J6 F) X: v. I, P
# h* ?/ U' u* B) Z( H( C; r: ~% [ n+ J+ V% a$ v5 Z- I: d$ |
}/ Z4 b/ ?' N7 N
9 c, W; r; U: X6 f
8 P+ ^3 C, ?; F# z( @' ] $this->userid = $_userid;
$ g" o7 c' A2 `( z. i5 |5 ~! C
$ P1 V% S2 |% d3 m, M
# o5 g; i C( t7 J $this->ext = file_ext($this->file_name);
' ~, c' ?4 C8 k
. T2 H. a2 l* d: ?
% ?- ~8 B! P0 E* J) t $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
: g5 o; R' k. E M / x7 K& S& U8 ?' ^8 P: d9 S3 P! Z3 A
3 Y6 G- G: @" E5 c5 @4 p( e6 m
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
: B0 f! ]; Z2 x0 A3 L
( N: D Z. e1 s% X2 G" m3 t( P# H3 U# H) q$ g
$this->savepath = $savepath;
+ O t% N: L- ^% T/ G+ ?/ [ ' _& K1 d/ r& e2 k
' B: o, f7 f g5 O7 g! z3 C
$this->savename = $savename;, _ w L' T/ Y& i
% z9 B& h7 i& ~( [( K# N* _5 r0 _
3 k. @! V) {3 Y4 H9 a N' y }}
+ W- F8 w! n6 p0 T4 [; `4 v+ n6 ~) [ * s1 X2 T% R: C" O- c
G8 _( W5 y& c0 f+ M, L( Q 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。# T2 p* o) ]* m# L o4 a$ o# s
1 r3 Y7 M& M- u( H6 D
w0 F& u8 j: K/ G8 R 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
7 d3 p2 o( E4 p8 i$ i
- e7 d9 t. i. i. f+ G. J- [- R$ ~( q/ \6 \( E' l5 r
$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
1 z. j$ {* I8 X, ^, E, x % m0 o6 H' [, x' |9 i9 i$ j: s
% F; M5 M* k% B A! h" ]8 W. |
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
I5 O) F% }* k2 o
. N1 N" l' C' F/ f7 H& I; T2 o" _: b6 j+ J* Z3 ]
: Z0 @' g% z; p3 I
" k9 p/ I2 w4 U, w" U1 B, n. J. X C
" m8 N: w# P1 b+ i2 }# O 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:0 G. D+ e6 H2 s- o f7 A0 f' R1 {
& O C) n2 Z E: i
: j9 v: [" Q a5 p) \ <?phpclass upload {3 b4 S9 E9 P. Z/ }
; {8 Q& g# f6 k& [$ H
M/ G1 @ M, w2 w& \/ q v function save() {
# _/ `3 i( ^: M" X4 r. f 7 Q% v* a9 s% S4 a
+ |3 P0 w. r* j9 S7 H include load('include.lang');
) v4 I* A" n% t N+ C8 ?/ E) F4 v! g
( x) C& [& E h7 E* p- | if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');6 k' B7 ]9 w9 X
) F5 z: z5 Z5 B" I7 h3 u) P' H
5 Q& A4 `" t' H8 v! W/ x
. C" L- Y0 P8 ?* @, C
: z$ W. E4 N1 H8 _2 q. g/ Z/ m) r. u+ _5 J- r3 a
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');- E8 `9 j- O; T' p! \
+ D) E# ^. R$ s" @& [( h
6 v) s& }% D5 e. v! E6 {2 o8 q
/ d, c. z! Y3 b1 u
W. U! s- \/ w8 T6 s# ]( c
0 ?, }+ y, i! g. f# j if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);0 g% n; x; ~; i: E9 D( y ?
0 t" ^( D, M G8 z @3 F/ [4 S$ `* g1 [
( C. x. _7 ]6 \2 V$ j8 {. F: t
% O# {2 c0 w! I0 r8 h- u" s
$ |" A" l7 i: Z8 F$ `. F7 u
$this->set_savepath($this->savepath);
( Z) d7 ?: n' W/ W9 b
! `6 E D' [$ l; y, N
3 T" t5 I t/ ^8 j$ ^9 n $this->set_savename($this->savename);) w! h" D8 @3 t# ^& |
; ^' a2 b' U& Y
+ b- |/ V$ }/ G% _3 k 2 S i5 r* w/ A5 J5 j
1 i* l' b" O7 q7 Y, ~3 |" A" c: I
# D1 L* K, P; g8 \8 n6 A4 Z0 g if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);8 m# W! n# D8 e+ c
" a+ C, P$ k- H) p) N5 F2 R
9 b! k( q- n" s4 m6 v7 L) C6 J
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);- ~9 u# N1 ^1 k6 U9 n
" z! I0 l0 V; B: X) g9 R& n* P
. |* p c" D7 a6 b& x$ w if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);+ E% j0 D }8 C' c2 }
8 O* K) z* ~7 Q: @
3 y( n( k4 G! S # o- d& G4 b2 d C4 ]$ [. J
, H8 y q% o9 q4 T
4 N( @! H! [7 \ $this->image = $this->is_image();
- M' A% S& H0 k3 w6 |
* o/ m6 n" X5 Y6 t* W+ Q+ {9 K
" D2 ?, x- p" m, m) `, O if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);" k+ A$ ]0 X! ?5 l% x( {
) d7 t- ^9 t5 B" M* g
' T) \6 M' ~! E& K$ z return true; R5 W: h' m8 l
' V" o9 x, O, b ]( j
, g3 P$ A; z1 z3 E6 X; b3 M
}}
4 b! F6 A/ H: ~) ^. S6 v
) g k: v3 s* m! g
! ]- k- X& ] b' Y0 i. p! r 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:9 @2 A9 O2 O. G1 T. n8 m1 z, h
) \+ l+ T7 h- D2 A8 p; J' M
0 D; i" d2 P, w- j3 n, D1 J2 { <?php+ H( @. G( B% s( d. S6 W; c- _4 C
% i, ?0 G/ i9 f3 W
2 Y. t0 i! }& n function is_allow() {
; c: C1 _0 [7 a6 T4 r
" f( P& l& @7 o2 ?7 G
: _: z* n' L: N* v e1 N: m if(!$this->fileformat) return false;' h, q- S+ B" z8 L
# }; B6 s$ N6 n2 \7 _' v, |
% _" p+ a) q% b1 U+ F8 A, _( e if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;% z' M( E* c/ g% @
6 {) r( } Z0 k" n! M
: \5 b0 G: d& p* o" F" e5 a2 ] 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;
$ l+ x% h* E3 I4 a$ x3 \8 e ! C7 S1 e, L" l, h; V
/ u3 a" Q' R4 v% J, L8 b
return true;/ X2 F1 I2 D: W/ D& b
3 z; M [# U5 i0 G7 Q! d; N$ H' B# H1 [. ^$ E3 Y; u
}
$ S: t- o6 r% } 5 z: d+ O' D2 a1 Q
. a5 O* k1 A4 _) R7 \) u 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
- p' |- M. E( J* P* @
. w0 j0 D4 Q# Z0 C, P7 q, y n8 W
; E3 [, d# R6 C 接着会进行真正的保存。通过$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文件。
: P& r+ z* }9 y
c4 p7 ^+ P U3 [. J2 C
* m" q ^) L7 q1 ~ 漏洞利用
: }# y7 J$ H! [6 r3 U9 r2 }/ E5 S
% e, Y, F, z0 R3 K$ S
" h; ?% T( S, L- `& d 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
2 w$ Q2 C( O1 [8 d ' H f! p$ W# t) k
7 S$ k/ | Q, h" n# w
: ~+ c/ l+ B5 I, g( _2 _$ ]
. Y$ V8 p) }. i. }3 N2 ?, j5 @+ ]$ n3 |6 y
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid7 Z3 E0 H! a! h; P! Q9 J- G
N5 v0 M6 D; ?0 b! o! t
3 B" z) \+ `7 B9 T
不过实际利用上会有一定的限制。0 \* x. o2 M' X3 U
# {2 [' g/ m( Z( u% b8 j8 v# T/ y: `# z) _
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
& N( F2 u2 o( @7 c7 W9 q
, E4 d7 A& O! \. `5 S, U. W8 _2 v- H- [2 C# [) h3 A, w. i
* W4 S1 ^" F% h7 q( z3 _
0 ?1 w5 ]& i' I& k. ?4 o& z( D6 j" }, j' B$ I0 |' W/ C
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:) ~0 n/ {, T" ]) d1 [
: R$ J5 w% Y# t0 O* ]" d$ Y/ ~! C3 ?4 R) q2 b2 g6 t% T
省略...$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]);省略...4 O, m. P, s1 V; S6 X; f' a
. x) S! u8 {! Y3 e, n% X* D- k+ F
) ^8 G- u$ l; g# F1 t! P
因此要利用成功就需要条件竞争了。
+ w; N( i5 X! D8 ]3 r + P' i; K# _" M3 F5 ^
# Z; v9 H! f7 B ?$ y% E 补丁分析
1 U' S d* s( u! D+ }
' J& ^& V( O: ~' q$ F+ y8 ~+ j* R- f3 p
/ @! F6 z1 O( I8 p1 s% l4 L! G) t8 G & `8 e8 n, C, G. `: O
/ U) l) P. u% ^& P. y( ]( @ 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:( E* V& [) B! S+ R
! T3 h; c" X1 ?
9 v4 h9 T8 B& R* y% H' d n8 U
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
. b8 @& @; Y3 ~# V 6 `/ r) l _' j/ v, m' H
8 g/ e( w, l" Y7 u & E% M( }. D9 @7 i) c; h1 Q
% V9 n4 f+ T/ J
/ `7 X' R2 U) y' I) X0 M
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。7 p/ h/ I( J" g5 R1 b
0 s- c z" V( Z. P5 L9 ^, H. H
{7 }. e% g0 d. h7 x6 d
在is_allow()中增加对$this->savename的二次检查。
0 T. ^# k4 w3 r6 L . L5 Y- [, i2 R* Y* J
. n6 V# m5 V/ [3 g" T4 M4 ^/ J* @ 最后! g8 \, ~/ C7 b; H9 z/ X. O
% k1 M6 R* V% v5 o0 H. E0 C5 W$ h
& J# A0 e7 v6 r3 W$ J& p0 H( `0 _! Z 嘛,祝各位大师傅中秋快乐!0 j6 E: u' N4 C6 S
. S- J, a! d+ I$ X8 \$ r
. R/ k3 [. ?/ N9 E. Z 8 z" S# ?3 G% ~7 U1 @8 |8 q
$ d- A! e( m7 M& f- ~
|