找回密码
 立即注册
欢迎中测联盟老会员回家,1997年注册的域名
查看: 1392|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
! [- k; Y9 ?+ P+ z

1 h7 p0 g( j# F. D' r! d

. J( T1 I3 G' V

% v# h9 u& B# A" W 前言 % A- L* g8 J( s7 Z* K" W

/ m+ J. u; E6 s/ b" ^

$ I& b! B4 y' o* H2 Z$ \ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。7 a9 W) l" V$ ~" P

2 Y9 Y% u, S; [: o" D

8 U4 m7 _( T3 u   ( _5 I0 l0 F0 \7 u

* n( ], u2 F3 Q7 E! b& w# k& C' t

: b" ^8 a. a; l) t- x* [ 漏洞分析 0 N6 g( h, E O; C6 w' n* s l0 ~5 u

2 U8 B% [( n* F8 L2 [

7 T8 a- w+ h6 g8 {: r M4 C, q 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 1 L, K% I& W @- b, z9 R* j

' U# a: B# z. n2 x6 |! [

6 s" s* J, M; k( q- ~: j  2 H: F( D& t) z& U& g: _5 F8 u

; G( X4 }5 ]1 t5 x

; ?* [/ K H6 n5 v" i! G5 L, a/ V: v 对应着avatar.inc.php代码如下:7 p& J& ?, A! |; \ {/ x9 I

6 f) m9 \1 z& u9 a/ [

/ T+ G1 E5 Y" n- k% C7 M <?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) { m7 o# _, x- a1 d# g

1 p+ ~6 H' p8 R" I/ ]9 v

! ]; _4 l* W! t5 a% m) O- Z* Y     case 'upload': ( J/ G! S% N) O; Z7 |/ [

) g: u& m/ N% @

1 Z& f" N% C( a0 b         if(!$_FILES['file']['size']) {6 j/ K/ P/ h0 s5 k

% K4 X( j8 s, m9 r7 O D

( n0 X. W. Y0 [3 @             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); A6 w3 s7 O$ K2 V0 Y7 l5 v

: y( i p: [. P) U# `4 R

% z0 d2 m7 R* P5 Y J             exit('{"error":1,"message":"Error FILE"}');4 _& I1 Z4 o2 I. j7 w: R1 L

/ p+ z4 T' _; }0 w# o

" s" O! ~7 |. d: f0 {5 U6 N         }; F# W7 S) k/ l) E# _

7 j U9 [( M+ E$ Z0 n

3 T! z! S* h) \' Q/ ]         require DT_ROOT.'/include/upload.class.php'; + l; \4 z- t6 t" j; [; z

: E2 ^' @- \# e% E# d) i! A

: e" b' n+ L7 p, p& K9 b  3 ^6 K# k/ L7 M+ W' |" r

$ M1 R( w2 d$ I5 }+ y

4 n: W! Q; l; ]! H         $ext = file_ext($_FILES['file']['name']); # Y' D0 v$ M' n0 R

) p% D8 t a5 O

9 J6 t6 u) H, j         $name = 'avatar'.$_userid.'.'.$ext;- Y( G0 W9 W" l$ @, w. g& a1 W

$ \6 X2 Q; P3 O. o. z2 a$ V0 ]

2 x' E1 a; K- A: H9 m( I         $file = DT_ROOT.'/file/temp/'.$name;- O7 A, \/ }, l8 J: c

% P' s; L# ~8 E- f3 w' F7 ~

; L% D' R& }( \   + }( O1 f6 a* D+ k

( ~$ r& ]4 B5 ~: U7 Z. j

7 o% @7 H' @4 s7 }! N, i         if(is_file($file)) file_del($file); ; f% ]; Q# C% m, F

& L4 _1 m. J0 J- x0 m# M" x1 [

$ \3 l5 G ^5 P B         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 9 K I, L6 {5 ]$ W6 ^) g. b4 E

$ z/ _- V8 L; [, g0 x

" T7 t/ t: o8 L9 K( j   3 d: V+ z# S) b0 p3 |& F

+ L7 J k' @$ t- n' x7 Q# @' ]

/ `( l7 R- q& u. c- U% M8 `3 \         $upload->adduserid = false; ) Q2 {7 U5 Q% ]9 J

! O R) Z1 a0 r7 I' y' P

* H# q- `- x$ |, S* d2 K   4 C2 ~, y9 w6 K; ]. n5 ]

4 I+ z4 w8 f/ z S( c5 {. i: z* A

$ P4 f2 b2 N! o \. v1 p         if($upload->save()) {2 o$ O1 @% ^- l- s1 f0 E; x$ f- X

, r( v8 U! \9 m

4 |: m" ]( L% ~# ?             ...; s3 ~- n- v; B8 }

6 c2 _+ ?* c( l1 J% T

0 J! T1 C7 m$ G         } else {0 _* E, D$ P' h* ^; G

$ v7 p' e( w, H+ e9 S

7 w. O2 h8 T4 d4 e) g2 x' w: q( [             ...) D: D# U' o' Y b3 J

: F$ V: _7 g6 n8 a

" N9 G5 N0 c# D. |1 I9 h, ]8 y         } 6 J7 B. i; l* D- _2 _3 c& q* V' T

2 y- E; `" U* M7 H5 O- {) J

F; f# o1 k, {. |3 M: t6 r     break;2 m, ~4 j- `* t

( Q9 v# {7 r7 v4 s% q+ q: r

- Q9 M& x" \7 W" \ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 9 x+ V$ @# \: w4 s# M

) |% m3 k6 T+ u2 g8 `. M Q

5 L# U: d1 @* L% W upload对象构造函数如下,include/upload.class.php:25:$ h2 l3 W8 ?1 `0 v0 j+ Z

+ f& {, V9 R2 ?4 I0 T

" F2 J0 f& V a4 p! h <?phpclass upload { : s$ d: b+ Z" t; P

# k) q0 e0 R4 j5 v3 q+ X7 g) k

1 g& O, h- p6 p+ \# i$ q2 M     function __construct($_file, $savepath, $savename = '', $fileformat = '') {3 M1 ~$ Y* ?5 [( Y+ s5 a* w

3 G7 U6 b7 _9 d' A7 S

& ~6 e/ q3 i! | m- k3 K         global $DT, $_userid; 7 q; |1 o3 o0 G1 y9 t, j

" j3 v! J4 S) g

4 q9 p) W; N& ?8 z         foreach($_file as $file) {6 W" g) Z' C# s, k# |+ S

9 }8 F: F9 }) D8 V6 {

( N5 A4 c9 N! z, k             $this->file = $file['tmp_name']; - F& K" p$ S$ I0 R. f! M

0 L+ J$ c/ P6 Y2 Y+ x

, L# @! P# C; A+ w! }3 P' f6 [             $this->file_name = $file['name']; 9 o- v8 I/ S0 E% }3 l4 O, X

z: n/ Q8 ]/ s7 k. ^2 L" {

# |1 b9 }' K/ {% x! u* V             $this->file_size = $file['size'];6 \: c, B) h% Y9 G# Q7 T' R" b

% P$ k/ A! s, q& j5 ?

\! w+ M/ ^& X3 l             $this->file_type = $file['type']; + ]- @2 I+ d1 i8 x1 |1 m8 e* W1 T

. H; v1 r4 z) n5 i9 a" b

( ~7 d: V5 y0 _+ \9 n6 N5 B             $this->file_error = $file['error']; 3 r5 O2 R3 W- E2 e R5 w

; j& x }( ?3 C8 a# P0 |% A

7 l9 Q5 f4 } H+ c   # d- m0 @& T4 V& C& R

0 e6 D# n; {, ~. Y N6 \6 }0 j

& c9 k& @: i9 g; m4 H2 C         } , ?- u8 W$ m( g& X) @ h

9 L$ _8 O, P* H' O1 W! } a

- w% n5 Q: U/ y- @% y         $this->userid = $_userid; 9 L/ Y X/ ^5 v2 r& m: f) w/ ^

& f4 F( v" [5 Q. S# S

/ I2 } i/ u5 M2 E) r+ U. T         $this->ext = file_ext($this->file_name);2 }' f2 M) ^% q

! e, P3 L- U' Z% S1 M

, M, d! `6 a5 \5 }         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];2 N, Z$ C4 N7 o* `

$ w5 ]3 v; ?0 \

/ R j4 _! e8 [         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;6 \0 N) \- T6 Y4 i# z! F1 |8 D

y) Z$ X3 M% Y6 x" }5 t

: I0 G; B: N9 Z$ A! x# ~ I$ H         $this->savepath = $savepath;" [7 a5 p2 N* W8 Y

6 s" q! W) A2 ~6 C3 [# e' F

' n5 }: V( ]) h( Q( {         $this->savename = $savename; & W6 K5 E3 |* k" e% P3 V0 K

. `" P; u7 o/ b/ o

8 _- G6 E* }" d- q     }}) v7 ~1 [" ]+ a8 J, g

. ^5 V' t$ V& p$ }6 ? F' E

& o& a& k* f _; r6 x3 j& K 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 / V0 m% C$ @) Y+ V; ?% H2 \

. [4 ]/ X& U1 V6 k

5 }4 ?& b0 W2 Y9 j+ R 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ; G! C- u! X# l9 ^9 S

' i6 Y7 Y! O6 T! m

% m, I0 |; E/ r- f; ?1 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.php5 c2 V% w8 A2 ^4 A- O( N3 E

: K# \" |# d3 R9 `

M( N5 U! e! W 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: : c0 x9 N. P/ v

& V: W7 @4 b6 e: @0 e, T

' x9 L' J" A/ E  ) Z' `5 X9 K% m5 p3 `7 g2 F

# h" R6 r/ u8 w/ i7 n1 p. h- V

. E, u: x/ F. L5 v9 ^% T/ `& ] a 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 0 a+ e! k- K# g, @

0 X! j" a6 {) _3 u

, V9 y* _; `8 Z1 o& o$ m <?phpclass upload { ) V1 s8 K u' ^: W+ N& P0 s

2 h( g/ v; `, F) `$ w$ z: X6 [

D* u7 n# t1 j) {     function save() { * m( h# g/ B( e4 I2 P6 ]

B9 ~0 J+ s4 I& y& s

3 [7 q" b( }; O/ k# N" X5 [; Y. ?# k         include load('include.lang'); , H& W* Z" `, q3 e, r

9 C9 H; n5 U; [9 _/ |( W2 v8 e

7 R+ n1 h3 F. O. j8 N         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');# s* n2 e# q: Z& J& L" a1 {# t

& z: D" o$ Q# S. w5 l

# ^" x! x5 @3 Z# N c" d   S8 L6 W; |0 E' R

: j7 O( h5 S3 z. | v+ q

* Z5 X# x; d) [6 G8 w4 E$ g4 \         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); n% N4 z5 K# F

" e, H, v9 x+ R, l. N

! J* a- R1 h% v3 l7 q/ L* |% n  8 b, g- B" C- y& L3 s

, m, S$ G/ A) C; Q& D6 |& e% w

5 h( [) f3 h& w5 z/ P         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); : f* Q. A' S y

9 K: v" Q9 f( a+ Q

; H/ i# V4 Y6 p7 M  7 E8 L B1 n- R4 M, G& H

$ m' E8 n% i, T3 U# o8 x' L5 [$ R

1 }: X) e/ q& h; Q: V! S         $this->set_savepath($this->savepath); 3 g1 N: y: `: A; h5 @ J3 \; x: S

' G" q0 c) @) n2 |& T; ?+ k2 M

; v9 m7 V% q1 F" @9 x         $this->set_savename($this->savename); ; H- Y4 M* L9 F5 V, h

/ V6 q D- C( [

! v; _+ I8 ^: C7 {  . \' h" k% X0 b% c- l5 J4 P/ S k

* c# j1 i6 J$ b [/ I

0 d. f5 f$ }( D$ h4 S% \' Q         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 0 R' t6 i7 Y) S

0 ^. x9 @3 w, G/ o) P

( Y8 I' F& h$ c         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); & i, O0 S. v- n$ C

8 |' V- W, k; ~' j) \6 c- ]

2 v8 M0 ^- U3 C         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);2 \6 O, \" q& S& J

9 g ]; ]5 E5 u) ~: F6 M

$ A) ^) |* i& F6 k, t6 y% T0 s# a) h) z. e  0 U0 ]' d0 ~0 a+ h6 t* `9 j6 C1 Y

5 w; r, A' d$ K7 i, M

' t+ J) r2 f, ?' k2 G$ ^         $this->image = $this->is_image();4 U! t0 R/ r3 n0 E# t

! L D, x4 E% T) H4 ?) f$ L" o4 n

# I, n( X* r$ F' U1 Z- B# i# N# B' N         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 7 r, L3 K& \* N+ {, ]% O' v

8 B" r5 ^# G2 i% e

3 l4 S# X( n8 Z* H# x/ G4 o         return true;3 F8 H" F8 H& ?2 H

4 m% U( S8 f, _& P& d

0 u6 W1 S* D( k, \7 Y- W     }}" q$ v3 }1 _; _' H* n

3 H( r2 o" `/ v2 @" G8 `

! M9 M! W' t* X2 @, p4 _4 [ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:6 N) v% p' v2 r5 V& ]$ R0 f

, x+ \; x4 }+ }+ h

- i/ D- g6 r! J! k, s4 t9 [% H <?php q: P6 I+ m4 I9 p" [" V: s. v

8 r5 L( P$ @: {0 l

& K0 E1 P9 @$ b6 j2 Q* T* c0 W     function is_allow() {* a" ^% ?- G" k% x# k

( p, N* Q( G2 B5 f- }/ K

' K7 b3 f2 U) N8 t6 t2 c+ q# ~' o         if(!$this->fileformat) return false;; s- @% d% |& y

# o% E; z/ `* l; x4 p0 J

8 V! q Z4 m) H         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;& O, B* Z6 R1 z7 _

: D; |) S: E0 o7 s2 W

+ ?; T% R& a$ | j; ^+ 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;0 b3 b* ~1 u4 {

7 v" Z5 y7 Z: x6 W- m

# p( c# ~6 L& B4 U, @         return true; 3 @. W& x7 f/ T# g0 F* @: _

$ q7 \# R1 H" C: T# w/ M. o/ s

+ i+ T9 T I( G; [# p     }" |0 w% W, |6 S, R! C

/ r) m+ P5 Q$ E c: G* }- T

7 h& M( U: b( u0 U' r# } 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 2 J, b+ J6 F5 X3 u6 [/ d. Y4 P

; Z7 l7 X$ |( W7 j# i6 y' o6 w2 h

+ Z+ }+ N0 L& u% Q: d$ {6 p 接着会进行真正的保存。通过$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文件。 " n9 u( L w8 N7 K3 e' Y- `6 H6 A0 F

9 ~4 [5 ^3 W* ~2 M: }: d

1 v8 k0 P8 y& ?0 ^' n$ g& N 漏洞利用1 D' i6 I" o0 N- v4 k

1 ^; e3 J0 F( B2 Z: ]6 v2 ~

& X! U0 ]* P+ w1 a 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。$ z1 ^, |# F! z# D9 c; {

! C# b$ ~$ ?8 n; Z

0 H8 W6 {" N5 q5 C9 O3 b8 w. b   9 n/ u' G" Z) ]: Z1 R

- ~) j; F' e) s7 Y- h

" L6 y( K7 Y. ], e 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ! k( f* f6 _2 {

+ X0 r, u# @+ Y) K9 f

' L5 ?9 r. @, B 不过实际利用上会有一定的限制。 1 b; r) x% p: u6 X# n" Y1 O, D

& }4 }9 E" N& h& j

2 o( [! z& D/ \' ^ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 & {7 a; Q! ~! P8 _& s) G4 t q) J W

, L) t6 \( e; ~2 V2 V; n

{" ^5 w9 }2 V# i' ?& k   * \) U1 R/ P' o! B" l

' C; Y" b% I4 ^3 [

; d6 [0 [+ \; l) g6 z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:; @: B1 D* J! t) S. h

; q6 T5 x6 ^ j* N- j

9 V9 r Y5 `( p# U3 t7 I+ A 省略...$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]);省略...- P4 Q% ~3 M% \ j6 G

: {9 F( g8 l% Z# i* C5 d

5 R" V2 h% {& a1 |+ t3 ^, k! ^, a2 X& l 因此要利用成功就需要条件竞争了。# b/ I' }1 ]7 k5 o: u$ F1 u

7 H3 P: U; F, a( R6 N1 ~' V

& G9 V5 G( h# ~! F0 f 补丁分析 z3 c, W O9 q( n- s

! l- l S4 s* V7 s

( d. n' q1 V T% E   * ]4 @) P9 ]9 V a6 ~

. N( }) F a' W& u- A& l/ e

/ E1 l' |: W& S4 M 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:9 m; g. ^% i5 y/ u* z, ^1 Q

5 b' P' R& b, w- y* j

( t# N( Z; `5 [ B; M, n) N; G function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ! Y) M5 K& J2 ?! t9 I. }$ E

# v5 }* I! O: `" _" s4 m' N

# Y: M: X( g; M0 J {, q' H   4 F- o1 Z' ^4 q

9 O. C, [# y( q0 A4 `

: Z7 \) _' B$ ?! `8 m. A7 W8 Q 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 6 u( T/ U: }' B% a [9 z+ F6 \) O

1 c9 {5 s7 Y* ?

1 X7 e$ |6 u" ?) y- N- H 在is_allow()中增加对$this->savename的二次检查。 : t/ O2 H$ x% q( U5 H7 [

; Q. v% ~( S6 {; l

5 ?5 E. `; S* r7 S0 @( y2 a 最后 6 _: ?1 E4 }7 {! M- U

# n l9 S* t: C, K! s& L/ j

8 V0 l) X+ @8 s/ B' Z9 P 嘛,祝各位大师傅中秋快乐!3 o4 I5 t, _7 m! D) E# ^ n

& ~9 R4 |0 H4 S, y+ V% G& s

/ J5 U3 ?* p$ q7 j6 w5 U   6 B, `1 w( b7 q4 ]& @$ d

9 F0 Y3 W+ {( ~' U& }# A# v
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表