6 Y; D2 ]7 L" |% q3 [
, m! m/ M3 W4 g! z# W# X7 c" `
$ c5 s" |) r* b) s3 m! [* z2 Y9 p3 R: l, R
前言* J& J' V/ v# b
4 O8 E4 ?3 a. }
$ Q8 T+ B; A3 u) T3 B' Y
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
" e3 y9 F! ?) |4 e# f( q- n
: H% U9 d& W6 B6 F$ V* Q) ^# m) y4 Y2 L0 X% F* u1 b# f, F5 n% @
: p, r1 V# \* k" R# n/ l0 c9 z
( g5 e; f2 Q4 }3 D) z; o( b2 g; S0 c
# q! g: f0 v8 Y' Q, U; Z8 R 漏洞分析4 N$ u+ c: S; S# G4 g- p2 B
* [1 x k- _$ o: t
$ S% d0 o3 e, w4 [, a5 J" i g 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
C/ f7 b' r0 S5 f- b6 ?
2 m9 [$ ^/ |7 d3 F! h* ?
9 i! x* p1 k3 M: X8 u* @
7 ^5 {4 P/ t' }7 t) v, D& S2 F - Q4 l8 N% ~6 u7 A( y8 U
- X& i5 `. Z" @# n: w 对应着avatar.inc.php代码如下:
: Y+ y) O/ A8 w! L" Q + ^0 N$ R$ g0 ]% p% K
/ y3 i5 r# A( v
<?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) {
0 n, T1 ^9 Y3 z/ b# y3 q% t5 f: c, x7 p
^. e+ q! a* Y$ O0 q" ^" Q
0 A7 T P$ K( V+ w( _. d case 'upload':
& \& p& L9 c' ?+ q
: J$ t; l1 F& Q! {9 S& }* g0 m; ]6 _( B
if(!$_FILES['file']['size']) {2 U5 S4 w, M1 X
: g0 e" r* {8 j3 n$ E' d
+ ~% {5 X! c r9 j6 m' p if($DT_PC) dheader('?action=html&reload='.$DT_TIME);/ Q- n4 K& G/ `
8 x# x0 X3 v4 i T& ]" V
" |7 r7 I8 X& F6 S; U exit('{"error":1,"message":"Error FILE"}');
; d) f& O) j: j& x# o- ?
, @: L( ~! ], x L1 |2 q2 f( p! w" O
}4 u: l5 `% P, I, R$ N
+ t+ m& X1 p. u5 X/ w# i2 k' `+ q
: P1 W7 c$ W% `
require DT_ROOT.'/include/upload.class.php';+ @5 H: C. g) @& t. V
8 K1 ~; i4 Q4 P* w4 p0 B4 W9 t' R/ b& l7 ~2 ]. [
& z; T& R4 ?: C& m
8 E$ }! U3 `: _2 g+ J
0 z" N c0 n" j: p' n ]! a
$ext = file_ext($_FILES['file']['name']);
& _% W% k, F9 u: u2 v! u 0 C% ?3 t) a8 |( w
5 f: T. a) j+ O
$name = 'avatar'.$_userid.'.'.$ext;
+ M) }0 }. l% Q( L- o9 e 8 c9 I$ V9 _* ?' k L+ _
% }, n j. B7 e) } $file = DT_ROOT.'/file/temp/'.$name;
# B; w2 ~5 k& t: b 4 Z# n* i/ s* ]( D7 d
6 h2 W) Z: q# G! Z - b( w. v9 c7 k. w4 r/ a
8 [, L* K6 b1 w
! h t5 K% _+ I |4 o
if(is_file($file)) file_del($file);
3 r' m8 }: A9 B6 u6 T3 F! U
: R$ P( Z p) a- b1 \0 M2 z. h( k$ x. n4 a1 h: \0 o- m
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
; a) p6 E- M* c2 d% p8 [5 ^9 w
S' s( @% C# v' Y6 g
8 S' B8 s+ i+ A- b4 s
2 W& y/ r; N' a6 @) C1 n6 J9 U
; j- @/ o" q1 W% d9 t
s6 `% F* p# J& l* Y. f5 E# h% T $upload->adduserid = false;
% B4 O# x' _3 X
% w/ ]. W- u+ y7 o6 S$ C1 x8 n3 G7 U9 w# ]" j
7 i6 E4 D E9 L+ J+ K4 o
7 ^4 B7 N, q1 `+ a- {
6 e: ]; |) T( l( J; M5 V) [ if($upload->save()) {: O2 H8 ?6 A8 H& h+ a& {1 f0 M/ Q
7 l* F( O( ~6 \! ^1 y! a. I
# f1 t" P( k& V3 `6 u
...
4 b0 r, S5 J6 O; c) |3 p7 @ * p, k- {5 ]8 u6 c5 ^" C2 S0 `
* c# z. T" v) b4 w/ U" P5 N
} else {! Q. i8 M- A% Y5 F8 ^9 J/ ~
& Z- M7 V1 y, t' O; D. c
- s. g' D& P, @& {$ S ...: B5 v9 L; v* H F$ B) B+ D
* i) F1 J8 O2 w( M$ F6 {
' u# j7 s, m \+ y }
9 x$ O" q' X; r # Q- j% G# v) T1 p4 n
$ k" k+ o7 Q2 b- K
break;. W$ O* `3 y+ T5 _( t
& w7 `) _8 O2 e! s# u1 _# M1 g* H6 O6 _( ]+ J, e0 a1 c
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。# y$ }* P$ J% X$ n! `& H3 i
/ c6 c9 q1 V4 x3 Z8 V
' W- G4 Q, N9 J upload对象构造函数如下,include/upload.class.php:25:
- T" T3 [% p; b+ U. [. a$ b
! {6 \, }9 D/ ?/ x/ W5 L' _/ s) V e
<?phpclass upload {, s0 z3 r b. A* N& l2 k6 J
8 n9 ^- M& P& a) I/ ~' o1 r
- z% ^( o7 u* h Z function __construct($_file, $savepath, $savename = '', $fileformat = '') {4 Q' U$ d- f" C: p
' ^; w+ Y: q- S" w4 z' k$ R1 F
& w+ D, T1 M) ?. K0 ?
global $DT, $_userid;
. i0 Q4 o5 m0 `
5 M+ x: a2 H, i) u1 @7 q" y |+ D4 h* C! L' W& F' j* j
foreach($_file as $file) {& j1 e, s" X, f8 N) J9 P, V2 X
- R* \/ h; `5 L7 _ b
5 i. c' F+ D) d7 R $this->file = $file['tmp_name'];1 `, K4 m, D! V0 `8 o
. n" ^+ [6 c; Q6 ]
4 }$ i+ A; |; ^* y" O N! @7 I# t $this->file_name = $file['name'];& c+ ?; r* F$ B& M* ]
- x/ }, k& ~9 ~ g$ a# ~* O. S' l9 @# o3 Z3 f1 N/ }
$this->file_size = $file['size'];
+ V* m, M# o7 F% @
# D* `: w4 W6 C& |3 b) r# a
- J u% T; {+ h: |' |! V $this->file_type = $file['type'];7 b8 v" K$ H0 J% ?. t6 s# A
/ U* [) l3 F" q2 C; j
( _: u" b0 e, J( @) e. c
$this->file_error = $file['error'];
$ Q5 s: z, f& a( V7 J: t9 { 5 Q! p4 S& g. g5 ]/ K8 u
, `# R* W& [2 H6 d) r
" f7 A. C+ |7 _ 6 h# g: l B& d2 s# T
: n9 \, n( G2 B. h8 z }
2 U: u9 y1 o2 ?/ G/ Y* J+ T $ o5 k: D/ M, R0 n1 S/ ?
/ d% W3 a- M, u+ ]4 a1 K* V $this->userid = $_userid;
, O T6 G& n+ j! T' D c6 k, X7 N: V
9 {. \; i7 ^" g0 s% r6 o1 b" f $this->ext = file_ext($this->file_name);
+ }. N% a& a# v N+ F- e # @$ A L. x& B! e( W; p0 |8 R
, A" n: A8 Q0 |" P2 _
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
, s$ P! O8 l/ j0 {- e$ @9 u0 {# C
0 @0 w: X V5 q2 k* I+ T1 I4 W. l) k1 Q) ?2 M
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;- E+ o. j. D. k) s7 x# v R6 D
% J! {4 a7 L8 j) v4 X- M. Q6 x9 p/ s+ ~, `7 ?3 j
$this->savepath = $savepath;9 K0 A2 r ^3 C+ |
$ D* E: q1 k: |( k
6 o- ~ e" t" d% n+ _. F
$this->savename = $savename;
" ?& a( n2 g2 g2 m+ b8 L4 r. i! F8 I # k" g; a Q* A& t
- ?4 Q# H: a5 C1 t }}) f' Z5 i; O( M
3 ?7 E3 l+ I6 E' N& F9 _( G0 z4 n/ U
2 l Y9 m4 @' f! x. P8 n
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
1 c/ f& L' g7 K5 W) v7 O9 \ ) f( R( E& F s$ w
) N8 T6 g2 n2 i* n- y8 V 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
5 }' X6 d/ |; h5 q, h . J& l* E- a7 d4 l9 A8 w
8 }8 o& {5 J6 [5 e, I S( O. 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.php7 `, u; t! v) z. \: k
7 c" t# _* M$ k6 G4 c
8 _! v- l" Z [( {3 v- ~# L 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
! j& {8 J% ^, ~ e ! G: F A% x2 h# \9 }
! h4 z* o! _" t Q- x3 l" y) V9 E8 `9 u2 q
) j+ A' X" H4 U! i1 r' e6 }: {
7 L4 s( a+ P) l M$ I$ r5 P' {
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
! v2 s5 _) }0 \* x . A' q7 q0 c# s2 A& v' t7 c `
( h' }! c8 s( X$ a4 A
<?phpclass upload {
( ]# ^/ h1 |6 d# b: i% c
6 R* w8 [$ W+ U% K; M& c0 u8 f$ `
) @1 N* h7 l/ d; W8 y9 v function save() { b; j4 r1 V4 G9 h$ w
, [ b- M' G! b
" ?- G1 u- Z% H0 D% t include load('include.lang');+ l9 O4 S) h0 K& \+ }2 F" j4 y
& [8 g' q7 Y# R3 Z4 _1 @
% g: p. n; k, b$ r5 i' e& E
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
6 `0 G- u- m, {" \& ]
, h, A1 X# P+ K4 {3 X) @1 V$ B( l" n( F K I' Z
& f8 d* H6 X8 }) b" P
5 \+ E9 ]( f3 p
% w$ L2 g5 q" n' h- _! n$ h
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');, W7 ~) K( r8 E6 P3 U, N* c
1 \1 s. ~6 f4 r+ Y8 g5 ^: s, J! r0 a+ f
9 P1 M g% C2 N: Y* g 3 U4 e- J1 O' N" ]
6 |) ]/ c3 D0 |5 A3 b. M* C! ? if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);8 v6 T4 [& x0 k" ?$ Y4 F8 [9 I
4 e1 D. P1 l7 a8 _* l& c# I; y5 J, ~, ]$ ?; @) t7 y( j* |9 \
; M' _4 p E& {! h5 N- H0 L7 A
* Q5 W9 P( e$ r& P; P% b; N
; {8 v0 u7 ?0 r2 c$ w $this->set_savepath($this->savepath);
% i5 G( q- @2 A/ `3 K
5 [1 U v* v0 Z/ t( j; M4 L* c- B, {5 B6 f8 p# H; b" r- s4 d
$this->set_savename($this->savename);, r" M3 r# D5 w( v7 _/ Q
3 A( W0 J( _ V1 s! }8 }/ T, {) g0 B7 k b! M& {; J
$ x% ~3 U) K0 o4 T# q- r' a
9 L. x: |# h: z. J6 Z0 ~2 D
. r% w. ?, {. V if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
6 Q( D! w' d; {3 U, L. R
2 {4 S" ]* R. ?7 t7 r- L% |7 j% N5 a7 _
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);0 g4 W2 o' k6 f' J2 ^; ~
% w6 k1 @/ b4 @" b% t6 f+ W$ S' B, ~) `" j' M
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
+ N8 s9 g r6 u3 `4 p8 f- {7 D; L
* ?+ i4 y5 l* | a; F
% ~$ I/ K% s d% ^' B% p( }
0 g8 a+ G( W9 S6 @
: A( q. A. Z) Y, d$ n e; M' W' ~7 `: {3 W9 Y4 [' k
$this->image = $this->is_image();" |0 Q: ?/ m. i4 O4 _% M
2 v' S4 a3 Q2 O6 t
5 X' {$ `. F, h1 I+ r2 ]1 S
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
' o3 M4 r& k, z. C
- X: d5 I9 b$ g: ]+ l |8 D3 {- }, }/ Z; E4 n
return true;
" @1 ^* A2 e+ G. X! D% c4 y " w( s' T3 ^; H! D' j, Q" m
, ]& ?6 L" B( ~* W$ V3 Y* u
}}3 }' C% v2 Y) V7 _( t
" X" s% Q# \1 M1 {
9 D5 t1 r% [, C0 R 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:3 F0 `1 N) b7 z* y' W1 O
8 o( u5 ~4 R' k; W" ? p7 R" G- Z6 U; E( \3 n
<?php
9 A8 j6 B$ j! x" Q 7 Z2 x9 B6 W: ~, p
3 O- D+ J$ b( c- I: j1 y/ p function is_allow() {
8 k: U ^0 Q" X$ \" ? 8 o) T& v3 M2 s" W7 D4 W
- j5 ?! [' m2 l* l if(!$this->fileformat) return false;9 E3 h6 |! `% I0 ?( W( w
$ k( r6 u: G7 d, y5 w) I; l" [! P: G" m$ m
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;$ j& S# U5 e2 S# `- a& f
4 U) G% H! \- X# v J: X" v9 D {3 g/ c4 T! b/ Z) ^. ?, R
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;8 L8 L- m6 q0 Y) `
2 y C# k% n0 e2 ^3 `- e
6 A; H* x, C8 s; w5 \
return true;
% N# q( U: h0 B+ b9 L, Z. _
' E5 e6 Z1 O. A8 P4 S9 K
8 V" B4 ~! M+ x2 N0 M; r }
* \2 G! R3 A7 d& s! ^+ e; {, w
1 N9 D- I+ R$ ~; o+ I/ N& o0 j8 X1 V8 f0 _" X% l8 t
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
0 r! H5 ?( A, E0 ~* r 9 z5 g' D, {: [( ]$ ^' ?
B) C) m3 n0 V3 h K
接着会进行真正的保存。通过$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文件。
7 b" O- j4 _; w. u s5 ~6 b9 V& a
- X+ `, g$ ]' A4 | 漏洞利用
- J+ U$ Q' j) E* S4 N/ H3 l" g( ~/ ^* _$ h Q* s* E4 \5 I
; w, I1 T7 b3 W 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
+ x) M9 w+ b# f$ w3 d e$ q
1 p- G) Z; H8 @7 ?+ K
- k z2 g. d& y( D; t9 E- d( @1 R L* @; U' ]* D" f- c4 e
8 L/ c# O t( D! p/ k& q- |( a& H/ p4 ?' H# O0 A
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid o4 Y) d. c9 ]& H% s# g/ A
9 v5 n) S2 j m2 U1 y$ `# A) V' U6 R5 i! q) i
不过实际利用上会有一定的限制。
7 M" r% ]* @5 ~. ^9 Y . u: |7 r# g" u' |( m
! v: t' ?. y/ u9 c$ W 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
) ^( ]! D( |5 P- F+ Z
2 u, M/ s3 z7 V1 c4 m+ w" W; m2 D
6 S9 L2 `" ?& B' b9 q + B/ g! `7 P9 O/ K& P8 l0 h1 m6 S
) b; v |* C* f7 G" `; b
" \0 p$ w/ _7 J 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
9 S9 @% ? w' w; K4 y) m 8 D, h. {& d% T7 ^# v
1 b% X) D1 U% {4 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]);省略...5 e5 N' U. I# u' E! f# \( U3 U
* |( ]" j3 e& p. T" z0 y4 B' v( X
" o9 \% x) W1 `9 ^ 因此要利用成功就需要条件竞争了。
7 p- M/ d9 a \/ A
6 A% r% R0 L- E6 E! M
" X2 |/ w# l1 T( M% k) F+ U 补丁分析
2 l) G) T" B- a' I
& f) a6 [- l) u O* C0 m$ r* D$ O0 P; \( _- E
; s5 }8 l8 U& E
2 O& H# T& \7 @7 ]
( U2 b# a) ~: H
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
# K' ?' k7 B- F# f* d' f 4 e& p2 U, I( ]5 g! \$ S
, B" Z/ J: K" ^ h6 U; D function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
- U3 B* c& U9 j# g
+ U7 @% h0 f* D& b* i$ c- U5 @" @
4 t5 Z1 I1 } |3 T
# P& Z' b( M q% ~% S & P1 @+ H `* }& i$ w# C) p# O) O
) V9 Y( w# H; U 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。* X8 q) z- p$ E! o
, U1 P$ z T7 U- t; s, W3 `0 q2 O8 Q+ z6 ~3 s
在is_allow()中增加对$this->savename的二次检查。4 v1 q5 ?; ?* [ K
7 n L$ K$ P3 ]
% x, X- t$ p- K- L- G 最后+ `) {- V# m- M8 [% X$ v% C
! ^, D" R* p& s. p$ P( Y! u6 r+ I
- C3 r$ ]& a% H) R+ V/ i1 g* N 嘛,祝各位大师傅中秋快乐!2 n( ]# @4 ^/ r$ P! G
D H( Y) X: n6 E: u
, [5 ]8 F# a) ^' D
$ G0 ~5 I+ U; \0 d' l
" M/ N$ W2 k8 j( e+ C
|