8 H: q# R. b" L% Q
* U9 N; n, ]/ ^! H" y, D9 k
/ H5 K& \# j% t1 c- H, [5 A3 S& c8 ]
前言; l) b3 }4 }& ~8 s; }
9 X1 @4 O* i- W5 ?
2 [8 V9 ^1 J7 d6 W' k
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。" X* a6 ^* Z' \ \% y5 p; }5 c' y
1 q8 Q8 |. k# K( p" _" J4 m q
- _3 B/ D; H+ v! z0 s6 O
5 [+ c: v- n% K, l d
: ]0 u+ K. K$ s1 Y9 s4 B
2 `" I+ a. C; G 漏洞分析
5 m( h* m3 T& T% x0 d- x& ?6 y3 J/ \' a8 r0 @ x
( T" M# P0 R6 I' e 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
7 z" B( l4 L, m9 |+ r0 K ) l. V5 A) K! m x
: v8 S* f3 G S( t' o
; u2 m# t% T9 }" A9 Y1 [ 0 u# X) K L' T# |& i$ P
' k' {; I b1 e1 T# K' J9 N
对应着avatar.inc.php代码如下:7 j( Y! q) X0 p/ r( A: U% J% x
0 r5 q$ ^( Q/ \5 m8 F) @
5 A) H6 C* ^4 K% _5 |) q8 K <?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) {
& a/ } z3 ~- v& v' ?+ K; A8 D & z/ B% N+ |6 H" @
1 x0 f; J, A6 Z% e' v
case 'upload':4 z7 k9 d5 V& i2 @+ {. Q- e
/ W! F( Q) r: _9 l
9 F2 O0 g) N% H# [ if(!$_FILES['file']['size']) {0 M8 p8 V) @- e
0 e/ G2 E! w6 ?, O, A" b: \/ r) Q% j8 R a; l8 j! A0 d
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
) n% t( s7 H8 ]! { * I a1 i, |& {- D. a+ z
4 I9 t. j# m* n exit('{"error":1,"message":"Error FILE"}');) q! D: v d# W1 }& ^
) g9 @% j; V3 x5 V4 r9 }
, g' L: Q& c6 `+ c
}- {9 x5 [- _; v* p; D3 V6 \2 x" \
" q% e) C" I+ c5 O* y" l3 F5 A7 G
1 v( ]1 I5 v( h, G require DT_ROOT.'/include/upload.class.php';
7 Z+ \8 h x u% X9 A, H
6 o. p4 P) p9 A8 a+ v0 s
3 e5 K. Z/ h0 h# F- v B
; h7 {, S x8 K9 ^5 F$ f; r
9 @/ M/ U2 L0 @, D
( m s0 ~/ Z5 T6 z3 i. ~; H: } $ext = file_ext($_FILES['file']['name']);1 l" ~3 e6 j5 z/ h
5 e2 f5 P1 G$ h: ?" w- w
* W: i! X" ` X! v1 p! n; Q) C0 E) `
$name = 'avatar'.$_userid.'.'.$ext;
7 O+ b+ T4 m1 K8 N6 c5 u
! B0 e9 M! A0 c7 V8 w- {4 w C; B
$file = DT_ROOT.'/file/temp/'.$name;% F8 ]! n0 C! k
/ u1 m: U4 u R4 [( W, w( B7 O5 ] Y' v1 s
0 f0 r; F7 ?& J) v9 F5 N$ x
0 u/ ]8 w P5 k0 `" z. v1 t
" H' m- K, z$ L3 ^* d: U! r7 o
if(is_file($file)) file_del($file);4 L) Z% e$ ~3 P$ Q6 D& C* O4 R
& t2 n; k- y; i @ I1 R5 u. C6 D- b: y' ]- R- Y% q
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');; ~ u+ j. q. x. K( S$ k0 K
+ f: q4 {% ]( f9 ~& ~/ P9 }5 Y. Z5 \+ M. X, v0 Z
8 u4 R8 I& V- P
$ d6 m- O! X0 n |! Y P
R* G# i$ J) ?2 F; V1 V/ s
$upload->adduserid = false;
% ?5 @ G& @, y! [
) X1 G' r6 A" g" ~; \
b- T8 d! Z' M! \ 6 p0 s1 C. ^- P+ l8 h
+ I% r6 q6 b: E0 _( w% n3 P' A# X8 D E
if($upload->save()) {) f# {: O T8 u* `! N i% }
8 I1 V, Z! I. Z1 S h9 @
0 N+ ~; A0 x! X0 R) I' L, H
...& A7 D: O8 k& o; X7 k! R3 _: \. i
3 i; \3 l0 p. r H7 v1 ?
3 @( N q" P$ R: ]+ l } else {
6 H/ U0 v2 G3 S: X6 O6 q
: N6 V- q5 @) h( c W! p! b) A7 B
4 R @5 B. p" y( o ...
+ d3 S, n8 ^. P- {7 p! ^) e% O6 w7 ^4 z' _ 5 i, l4 Y1 _, z o7 h) Z7 u2 u
4 @7 }' r3 M" [ I7 a$ z
}
6 q& |# [+ A/ q
$ H# l# z! P$ \, r; t G ^
! g( r% f$ S4 ^ T+ B( P. M" @- A break;
% n# e& Y0 R) x! \2 f
; h$ a& B% p$ \" N; M; W+ w- B$ ?$ e1 G$ T) P( }: U
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。! h0 G' l8 E) Q6 b& {
. D3 b, F5 M! ~0 T
# |0 y x7 ~6 J0 ]8 T5 H) i upload对象构造函数如下,include/upload.class.php:25:" ^; j7 y+ O3 M( `
( K7 b, Y1 a- f5 R6 ~( g% A9 I M
<?phpclass upload {$ l' ~* p T: T" z0 k# c D
/ s5 n% b3 @ O4 [. W
* ?/ N" Q4 J2 ~5 \/ ?4 Y* z: d function __construct($_file, $savepath, $savename = '', $fileformat = '') {
; [$ c( W' |/ _) H7 U# B3 U 1 M& E/ z4 |* l0 @, u' i
5 Y; K* B' E ?: _2 p3 S- i" ~
global $DT, $_userid;
$ i4 A, f# r2 u3 v
8 q+ Q P& ^, z, ~6 I
$ y+ s. N2 @; k p+ W foreach($_file as $file) {- O; E' A( R: h' x; m
& r* l9 p, B1 w" V. Y0 V- w1 z2 O; e$ y
$this->file = $file['tmp_name'];( l5 p3 |7 B: y" G& h$ }
7 n# L1 S3 h0 f3 r' x, E
' C# R0 a" F, P3 Q6 }: y $this->file_name = $file['name'];
9 ?0 `: r, v6 L) O
2 H1 p0 g0 R3 }- @# l! [7 V# ^
( X/ M3 X" a+ H5 d5 C- X $this->file_size = $file['size'];
0 O. E- L5 \9 H! X, a/ ]
7 j r* \; l8 M# T7 I* d9 o
2 Q, s+ ~& I/ A7 r* A8 c* F $this->file_type = $file['type'];
$ Q3 b0 C" F- b " o ~' ]2 M- b+ u" c" T( x
5 P% w6 {( ]7 b, W& v* A9 ^ $this->file_error = $file['error'];$ T# n7 s- @% G# ~" b) M
/ H5 I" w" T3 L: T0 i1 q# H
0 h2 r5 ?; j3 J! ~ % p: L) Z! j- ~1 ^
, v8 A; w5 C) M* [. U9 c
4 b4 t) [- r: K# w- |0 X- k, N9 ~3 d }
7 @5 c; i# b1 a( H# h 0 H) w& l4 E" t+ ^) l/ r& r
8 O: k( S0 H! y) e
$this->userid = $_userid;' e6 Q$ ]% S1 h
6 {) @: ~. T6 y9 N) C/ Z- J6 c0 `( \- q9 M
$this->ext = file_ext($this->file_name);& o l* Q. X" i/ C& d( N7 H; Y
$ e4 _* a: i6 ^+ T
. G! ?+ k0 | k2 f" A: O $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ X; u |# Z" A' q' v: Z+ d
. X& ^. W' {; q, T# b* N8 U
4 f o7 s3 D! u- L+ E: D- O0 u9 H5 W $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;) Z, `4 ]+ x U
- k- I' r& s% F; A* f7 {$ {" Z1 A
+ \, E8 R; i( \0 \ $this->savepath = $savepath;2 P+ m% f7 T9 X% B5 h7 t
6 n2 ]- ` S$ U5 ~% {1 j4 f1 ~- ^% z, |! L
$this->savename = $savename;! h! D& G+ I6 C2 \: s4 S$ h
' P" L2 C \$ G y. H
3 S+ a5 w8 }/ ^8 W0 `
}}
7 S# _% {3 u0 Y 3 r# y. @5 W4 ^/ k* b0 O# R
. ]/ _) h' r7 g4 O 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
8 x5 U3 Z8 O4 M6 \: h. ~/ N7 ^ 7 H2 p; g2 c9 h3 R7 @
; W% S2 L! h/ \/ \% x
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 / d& g$ \0 Z: _0 x3 P8 }, a
! a$ }3 x; m: V/ Y0 i* N$ G$ e
, m2 e( S! s: A; P. m( W
$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 D$ C8 q& j# `; K8 V& o
& T" [" \$ b0 ^9 q" w6 I/ d0 f" d, \7 g7 y# u6 u2 S
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:5 ~" K7 @, }+ ^
+ i; }, a6 p* }7 Z B5 z5 B; }4 Q0 V4 ?: K3 \
9 I! O4 [2 m6 ?
: |# W/ r% N+ p, @9 _: S' V) P8 q3 c6 f
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
. ]2 s( B5 O% r6 d0 ?( T1 |
+ I0 B( G' W# y2 V# ?# I
, J1 R: U5 ?$ _4 H" B( M& h <?phpclass upload {4 h1 z0 n4 i5 ]' ^4 N( J5 a3 {
5 b& q5 c2 [) A- }% `1 L; u8 u
, J* \- H& m* W4 k3 ^# `8 C
function save() {+ _& }5 l' D) L. e* T4 m9 X1 W! y+ [
% Y/ z) w# G, Q* z& z% W
. Q1 C+ \5 f" I$ ] include load('include.lang');
5 r _( G) W7 J' c, ? G2 k
, b9 c8 P! W2 g7 g+ N; }+ w: q# P9 t$ B3 n7 R* q
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
; D1 H# M8 ?' f5 J+ m 8 _% v4 V2 \, ]9 H0 i4 E8 v, D
2 N+ C) Q$ z0 w* t* E
" K6 {9 d; U! I [& z5 ] 5 {7 {* l) X( @5 t
- n& _0 \4 u) C$ B2 x" J q
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');3 v4 K; z$ E; D1 j; g
' Z7 t& A$ F; f/ [9 v0 ^! F) a5 U7 H; V+ ]1 i3 z
9 f5 H$ q6 ~8 P3 W- J) v9 u
- s) V- m6 K0 ]5 u, e0 q3 a
/ |' C" k$ ]2 T( t if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
. L8 L" i0 }! M' u- W
- u" n& n" y5 ]# ~9 l# W% o" z5 g! Y, W5 B/ f \ ~5 @: H" A
1 k" S4 J) [8 U9 a6 a/ y
% d4 s8 O4 H' K- N9 y3 G( L& u4 r
- X" s2 P8 E: B) N
$this->set_savepath($this->savepath);
/ ?- | T) e. V; K
. T8 R Z- L$ s( E; B+ g6 U
5 s4 s0 Q$ \+ A5 W $this->set_savename($this->savename);6 k. o3 b, J* }" y
! s4 z+ ^0 F, ^/ f I
K$ V& q/ J+ }2 `) J9 K- @ . x' C) V4 ^$ c3 e$ A5 \
' P5 \* C2 g1 u6 h; Q4 M9 ]8 P4 C1 Z: V, Z
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
* l9 E" X+ T# F6 _& M/ g4 Y- a2 ? ' _3 ]7 h$ g0 ^
0 S2 a; g* o: e" E; F
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);4 v' R2 \7 H$ x: C- b6 J
7 e# ]* }9 f1 d2 a
( E) ?+ ^3 i6 r1 E if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);% ~* p" C' L- t3 y ]
" D- T1 O5 {( l
! W( C% Q% ^6 D4 [% F
) I/ ?0 _- ^5 k1 Y# m$ B! u
" y6 w' J a% S4 l; [; V' M: l8 E
! Y0 h9 b$ ], F( U; ?
$this->image = $this->is_image();" d% \" Y! H F% S
e) A7 g% a" m2 ?9 {1 |$ N
0 V3 g5 Y0 G: K if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);! [; R% t# A4 z! S9 h2 x2 m% W0 E
7 z- X$ P2 m6 A0 z
. z% m0 ^2 a+ j0 m# B" Y5 V return true;" f$ g4 f' j7 J% G
2 M. [9 m, R+ i) a2 X- O; y2 O. Y3 o' V. \% J; W
}}
: G; C" [- b+ b6 s; d
, i, _0 i0 v- t0 z1 a" v* P* F% c. w% M8 k7 ^; [0 }0 ]$ r
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:1 p( z u3 v3 G" S0 U9 K E
# e6 _! L- K* v! R- Q5 i
/ l% @" t( @- U$ r# P' h
<?php* b. A5 O* V& w' J+ I, E
+ W$ I+ |. ?; `2 Z+ C4 \
. L9 a+ T" A2 w4 l5 I K2 g T function is_allow() {
3 Q3 B# v3 o+ E) w3 A8 x 3 t @+ |3 l# A+ i
: p3 \/ O. @, Y if(!$this->fileformat) return false;# x* x* y! T) i! ?* K
, `) w! R4 V3 v' G8 @8 V
" h1 a3 j z) i, y' X: ~ if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;1 |/ G8 c8 O' W' o* C% Q- }! @
, n/ w9 U {) V: x; f8 E. n
$ F0 E/ \. z$ 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;! F+ p/ |" x6 S. G2 B% w8 \4 u
' c" q. k+ c- ~( ~8 {
) D7 c& i) L7 l- x return true;, }: H1 k4 S1 z, Z1 M
3 F6 o3 F0 G. ?( t
$ f5 ^1 B7 f! ~$ \/ e6 r }
9 D0 \* A2 W3 w: w& A 5 j# I) @ C- g1 I" Q: L6 ?
9 L9 {9 [6 h+ s! X. A1 ~
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。$ O1 m6 ~3 f7 C+ P+ b- ]2 x
p* S- N, i" V
3 l/ |' k7 \1 C3 L# n1 _ 接着会进行真正的保存。通过$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文件。
3 s3 {5 w, {0 J 0 B+ f6 R8 u& e
6 `* |! D# t3 N) w0 a& Z 漏洞利用# M1 y: P" b# k' ]6 @& ?0 l: r; L
: s' z! l7 \" E- u# U
v) [/ T+ d! x r) z: x+ j. H 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
& E' R- N& p& j H# ~+ e, }/ R% y
) {' y% ~- N7 [' B' [# q# F% W2 w+ Z$ ?) i
4 ~ y5 Q8 ?) U$ {; N" H9 E% Q! l 0 ?5 W; o, X" \: {
, D9 K% @1 z# Q G8 ]" q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid& l, `+ ~$ }7 z' N3 b0 H# Q
6 E0 k) D y8 f6 w
0 W0 R Z5 j: e) j, J+ g' |$ N
不过实际利用上会有一定的限制。) n# |' x' G: E' j. B% k
9 i& }8 [9 c, Q8 Z5 ~- |; D/ u
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。5 Y/ F8 U5 l; z% |
$ K# I3 d7 `2 D. D! a K) k
1 T6 H) g7 I1 @0 l! j
7 w" V7 V" m0 S/ z, |& W; m; O# O {
' h2 g. j4 Y" ~& p0 a4 A! w6 f6 g/ D( e. p3 d5 P9 `" l
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:& D5 z5 O5 C) A- |$ v3 K; s
) m7 o1 i1 x- Y; l1 R/ i) C; d9 @# T. t. s
8 M8 _3 P$ Q. ~- Q* 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]);省略...
, p& G! @- n6 }" w F* q 6 k3 Q2 q5 p( g$ T" K
/ r0 P' N% O5 x* f% t6 F 因此要利用成功就需要条件竞争了。3 }/ t1 m; p% S/ I9 ]# J/ p
3 m; R% S$ {: @! J' v+ _( w; o
; t& R: O, [$ j# m* Q 补丁分析6 P7 B# F1 b) G2 n3 A' w
# p) ]* Y" |. R; U* A; B! M
* H* N% C7 q' ?5 L9 \
+ y' R2 W; p, v# z0 H5 D 0 E+ h m9 p" P- `4 v! e: u* ]
1 M. W! a9 m8 c# ~7 s8 B
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:. i! {5 b; w; m6 j
+ R# o2 o+ T" i' r# N4 K- v
- O$ e1 o" Y1 a
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
; x8 H2 u/ M; }
4 m9 X+ u0 X5 `+ ~; c. m; p' z' \: {' ?9 X
8 ~. [% [) a5 Y( l7 v9 g! @1 F
+ y& B1 B/ _/ J8 m3 D& U1 u" A
" c, a' J6 i, H b4 W3 a 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
) K: i4 T, K8 B% m" v& b) i; t5 i n
j. w. V' ~5 y# L
6 o9 l3 t1 I# j 在is_allow()中增加对$this->savename的二次检查。
/ a2 p! {! x) V( k
5 c/ N1 b* u% Q7 I5 h0 `" @
% M3 e v0 V5 h0 s2 n: J1 e3 ^ 最后4 V: k1 V" L! a; r1 D r
6 Y; d9 p0 a- Z6 s2 F9 N/ N: u7 z' ~
5 F( ]& h* v/ ?3 y+ w 嘛,祝各位大师傅中秋快乐!, {' {& F! q$ C- e
5 G0 }' b0 n% N9 H+ p# k( t+ j! c# H B" l& s
5 `; m, h0 t: j4 z
: N' h0 X8 V8 g5 A) Y' ~
|