找回密码
 立即注册
查看: 2052|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1 p/ _% l# f( A: c

) [1 B( p9 R: W2 c6 x( w |8 ~

& E2 _5 `9 B v6 k+ v, {& Y

4 g2 I; i% F' x$ M. Z0 o$ ? 前言: D& ~/ y) D1 H! l

( P) l$ C4 Z! y- x

, h# w7 G2 d" ] 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。; x0 n/ k: c. K4 I: P" E1 \1 l

* }( j8 o. {8 _% A- Z# I# L' O

9 ^9 ^7 n/ u# i% [  . a: k v/ W' s' U0 y2 k

" x1 M- j5 P& b% P& T; t( z

" n4 c s3 s5 `1 r9 O' B! }% d 漏洞分析9 R7 |! S) y/ r! R& W

( B( }: w' B8 S% j h" _6 |! ~

0 h/ U- F: i$ o% N0 U& }- T 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:0 `0 r' T& ?6 W% N& W: N

( _1 Q; K4 o# a: W- P( J2 E2 L

9 V7 R) H+ k% {" X: A6 y8 Y  5 x: e! S. l3 _# V! G

0 \# j, B/ Y* N( f+ n

3 m( J5 { n, z5 H i 对应着avatar.inc.php代码如下:& I( \4 S3 R i) @4 O

1 Z# ]% q% [ Z0 z( s' C- _, C

" [" T8 K1 h, ?5 D% ? D <?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) {' B" m; g' j# s @5 W: F% w

4 s1 B u: h% q* V

5 ]6 T. X7 z {3 k     case 'upload': K$ y1 |$ d5 w

6 ?5 f" o4 k; o5 f6 |: r: h& I; y$ C

m! X. H. M0 Z6 M         if(!$_FILES['file']['size']) { 5 ]0 J* d3 \& F# z; e. g: J) F

$ O5 P, R/ b3 S5 A, a

8 }% [6 U0 o3 }" d             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); " {* }; A j1 M3 v2 U

$ a8 l' L: c$ m4 v0 s

$ s% X1 j" x$ y7 p b: Z( G+ J             exit('{"error":1,"message":"Error FILE"}'); ! C& z- @9 a5 P6 K( w E

f# y) y. c( r

# k% y6 d6 \% k- ^! ^         } 5 B2 D. t2 S2 v

& i" m8 S/ H9 b

6 { @0 @/ M8 b9 b5 Q$ s3 C/ ?         require DT_ROOT.'/include/upload.class.php';/ p+ \# u" @+ s

! _4 |/ G- m$ S E( i3 ?/ C

; i, ]# B: n5 Q6 T' _+ I   4 ]0 Y( ]% v+ x( E

- ~1 y' R$ @2 y* _; i" J( m

k6 O8 R3 n" N, ~+ T. O         $ext = file_ext($_FILES['file']['name']);; C$ y# k( d4 v" e/ w" b) Z

9 }& Y& ?$ k; P* _% q& D+ _% C5 h; y

/ u* a6 p4 h. y         $name = 'avatar'.$_userid.'.'.$ext;- N% k+ w& X( ]; g9 K

5 U/ ~* H" n! d, s

( G! A: n/ ^$ e5 n         $file = DT_ROOT.'/file/temp/'.$name; 3 X8 f4 Q( d( u; T$ L: t) F

6 d; T. T5 k) q: C0 d

! N0 d6 K+ T, P% M3 O D. `+ W   ! ]0 x$ K7 R. o6 ~/ r

' t9 N1 i/ K5 b$ D+ p5 n8 a" i& G

) A* M4 v5 Z0 `" c- v         if(is_file($file)) file_del($file);6 _5 w: y, y/ l+ B9 _% {

0 l) O/ q8 ?$ D. a) ], N$ d

% x3 X2 o7 d# b I( {- {         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 7 I7 m$ G, B% g& I! {# t

7 L8 o/ r& E) T) ?$ ~ L

9 ]7 q/ c6 ?5 h5 p3 V9 y6 G5 i  9 x7 r( o8 a8 M+ s! @' ^

' e( n6 U1 F( D4 Y6 E! {0 W2 t$ w; ?

8 j- Y, l- X) i& v! r         $upload->adduserid = false; & ^& V% I7 b- g) M; R5 {: X

5 d& g- k( F! W& y4 E) t

" B! I4 A# \5 R' w6 L" M5 [6 m. `   ' M. g& ~3 R, `- n2 v

! b0 s" {. y" |0 K

9 R( |# g" X1 q; X# ^& i         if($upload->save()) {% u. C# c- }/ Q

6 t% o: F+ N3 v+ p6 f

% p6 W8 F8 F: b; v ?             ...( h, W" Y: `; a& @

1 @: |# t# b& M! r0 u4 F4 @2 M! s

8 n( c/ E' s- `( }* i         } else { ( Q! Z( I+ u0 G6 l0 u$ E

' y. u0 x' H- a& {% j+ i% r

6 G- O; v" f: v# L- J! p             ...! O4 |- K; Y' J" x" q

1 \$ t* I* k H7 M+ f, \- }

# s. s$ l2 R: Z         } % e+ P1 D& U( \4 k7 q2 \: j- }

% ~1 u r% u# P( I4 k D; g5 K( K

" @7 o: ?/ r9 u. @     break; 3 H" W/ X6 q& W. e8 n& @: v

! @- a- ?2 h# g) f I1 K2 |. \

) I7 F L' o+ M1 Q7 h 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 2 B6 v' Y r. n. F

6 N5 j& {0 `) o8 J5 V/ s

( y C d4 P9 u* M3 h5 U( L3 l upload对象构造函数如下,include/upload.class.php:25:! y1 v( J& S4 P6 @" y

2 x6 f# _2 ~4 b M3 C- Q

: ?9 X8 `+ Q5 ]& a7 w5 c& ~ <?phpclass upload {# P$ t) e- ^1 c5 c3 b9 l }0 h

% {, p+ R: T3 H

% O+ ^3 i* g% i% I5 B) m4 r     function __construct($_file, $savepath, $savename = '', $fileformat = '') { $ _, d. r8 I" }

) e# ?; u: D1 C I& R, e- v* N- l

, Q0 O8 U0 ] _0 |0 y! k         global $DT, $_userid; . W6 Z+ a- s5 p6 V7 q

2 U8 V& C* U2 S7 M. L, N

3 x; q3 G1 j6 J7 [         foreach($_file as $file) {' y( u5 ]( V! A: a

+ `: e# n V$ L& d$ Y

8 k' ^! S+ i; a/ a# P+ B; `             $this->file = $file['tmp_name']; - ]# [4 k4 u: T- y

, [* d% d/ {* Q

) U0 y1 E; ]9 R; v2 w             $this->file_name = $file['name']; & h0 `1 J# H' B" M- t# E% o5 k

4 w( k3 E6 ^- n

0 [3 ~/ E9 G# Q             $this->file_size = $file['size']; - E% D h% i7 ?9 I/ e! R; | F

. U3 k9 S* _1 y. s8 z

5 L4 r( _& j+ R, ]" w             $this->file_type = $file['type']; # P% ^ a' L. d5 g: \: d, o$ E% F

" l8 U& Z$ b- Y$ a) V+ M; [

# G1 P& o$ a+ c7 l, p6 w             $this->file_error = $file['error'];+ o: l8 U7 z1 \

. G* U+ c( W' l* R6 i6 V

4 A* ^9 ]( D( o0 j   0 u+ I' @+ V5 [# a' V6 ~

/ z6 D# D7 L- K

. _, s/ Y h: `$ [( y9 d; [         }& w0 Q. m5 N A7 o) X0 i! a

2 k/ D1 b0 q9 ~0 D- h9 g

% |: t9 q r" h- H$ w0 Z$ P. f( I; V         $this->userid = $_userid;# o2 ^& u9 V9 ^2 k$ {3 u$ d

3 L2 ~, r3 y6 [

, X7 s4 W0 R$ k. `         $this->ext = file_ext($this->file_name); & a6 a8 p( P i4 j$ \

$ F1 D& |: i; j5 z* S0 Y

" k+ o6 Y! f5 O0 h0 L/ [; G! I         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];: {, n9 k( E- ~) h; q

; W0 ?& Z$ |2 \4 E

* ^8 A& y. k# l0 b5 M: @         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 3 A4 r' M0 V4 M; j# k; _ D

, N( K C# F2 ?1 T) i6 j

$ L) e3 j8 d! A/ ^- m/ Q         $this->savepath = $savepath; ! P$ I4 n+ Q. K) _

; H! D$ H- A1 _- ]( @# X' O1 D

( k# x: Y% S. a) O" `5 e( D. D         $this->savename = $savename;: j- H) |! n2 X! _

+ v' {7 f1 v) V' A) p9 J

3 c. m: N, _4 Y m     }} 2 P% a" i. k# F4 D% i/ u; U0 t1 Q" R

5 i0 @8 K8 `8 i# B% j

$ N9 |- m5 ?( [# C( j- X3 V5 [; ` C+ q 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 8 d8 u5 Z# G' w/ p1 C1 H5 {

) G# f/ q% I( E8 ]0 m

+ `/ F6 j1 m" z: Q. @% y2 H# t 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ' m2 s( ~4 n2 o

0 P, B0 R* g: [3 n M3 |* \9 v Z3 ]( b

m' t! K" N7 i# i: u $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 F0 d) ~; {: I) R9 \

) o6 ^/ z+ c, b' ~

5 I; q" s- `0 t( E4 h4 e 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:- V& k0 w) `) u/ [: c# ?4 ]

6 I Y/ @3 _6 f: G

7 p' Y& T A. A$ v3 x1 |0 c( L  6 R, r& u% V/ C. |

$ r) ~1 U6 ]: O( q1 E% }

3 Y3 ^. T0 f. N' {3 V 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:4 l: N; x: O) ~/ C

9 f& j& G8 K) y6 `" f: J' d

8 @/ o8 L' J/ x" A <?phpclass upload {' p+ V( `# W" E) J& \; Q

' ]7 x. J# Q) X" `* e% v

: @, q! y$ A5 W3 C     function save() { ; V! `& N8 W. X* v4 r

/ q0 s& U( Z+ A9 x+ s& s0 j

1 e; Y1 b4 {5 u! ^7 y# r" s" M) I         include load('include.lang');' e4 o3 M5 \+ W) o- P: U

' K: W& L9 d9 Z: u5 F! s

3 b$ O) |( A9 G$ g         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); - \; V, ~/ y' L

% t( t9 G( o0 w9 r

6 R* M. y$ Q% b! I' I   0 S1 \" S7 c* P* R# y9 J( ?" h& _

( j: U$ o9 B, x

! m2 ~5 l H# V; Z( S# |         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 1 r: i$ m5 B3 S5 D

1 a5 U! h7 X \0 y1 b& F& K2 v

5 b/ l. H9 T6 P" H" s   & X1 H" o% o3 ~+ G( u

6 [/ I/ R* i" X9 Q% P3 |% @/ p

0 t, r- g# \7 ]* ?- e4 L         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); - V' x- d7 Q' a

; ~1 H9 C' T1 G% O, J: H: e

! ^, b5 S% q& U L4 Y$ Z2 P/ i" @7 i( d   3 X. U( Z6 \7 S5 M

. h8 L! h: f+ g2 B: J- a' L

% w; |% B# S$ b         $this->set_savepath($this->savepath);; d* w" j" K% _; P( Y

7 ~: v0 C' O% d# [$ u+ j

/ A: u6 @! ~# H+ q4 N% V- ^8 d( S         $this->set_savename($this->savename);! q* Z8 Z' @4 K

0 ^+ p$ n9 w% Q; q! Y

- k2 h' Z% ?" n9 t* X4 L! B  , l8 H2 {: s" F, _4 O) b/ ]) t

1 z; G1 r8 w* g7 G( m& \3 R

0 W% X8 j8 _2 G" b) d! L0 R/ @         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 9 b. K9 u! d1 i3 W8 ?

! x4 u+ k3 L h! {" h) n% n

3 _( c$ T; I; J- g, j3 G9 n9 _0 O         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);" V1 o9 P4 W3 j6 T& z }) U

; l. }8 B" @2 ^8 I' a

5 B% I& q7 }+ k, E. Y, _ A         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);8 Y1 T" }- f2 o" L7 i% y7 _( g

3 ~: F& l* Q; ]% T

" k0 Y% T! Y7 j* w7 B. @; P  4 @( {. a& h4 z6 e% B" ?6 V: W% |5 N

) R8 d6 [) g [: t% l8 w

4 p: b' b, k5 @) q4 J" e         $this->image = $this->is_image(); ; f) m) e) w. u4 R1 c

- j$ Y( o; o8 Y

3 i* H7 F; S8 m L, {2 f* M         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);. h# s4 r2 P( ]5 J( G; r

( x( S( T( F; V, u

7 X2 l0 f& }( S, \2 Y A$ x         return true;- H& S1 Z' ^1 A) H( U: F2 L

; t) s% g1 U4 {/ x6 \- r

! R: X& j3 B4 ?7 l# a6 g' e' g; v     }} ( | \& c5 P- P/ H# t! k

! C6 P$ f8 A+ _1 j2 w0 S2 _1 H

" `/ j' D' E: C3 V. K 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 5 l6 w' z2 b/ e0 j! [* B

& n6 [, u A' o/ ]5 w. Q

9 X' {; x' u P$ p <?php , J N5 g% E- i, V7 h, v

$ r% _3 c7 Z9 ?

9 D7 o7 X/ n& f K     function is_allow() {* ?8 X7 v1 q# N& `, [

0 n6 Y- E( @: H9 K

& W0 D. ]: g3 p! c' }& D$ h5 I         if(!$this->fileformat) return false;& C/ y/ M# F0 g1 h, k

- L* s& J. O7 \; _, w$ m( W

2 R9 v- G& L/ J- [# \7 v         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;" y* b; b! F7 I/ p/ O

% E/ H( r" e* e4 b& L( |6 X5 C2 k

% I8 x$ ? D1 `! 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;5 E3 F. _" \# v( I, t# Y

7 c0 g: |% E3 j* {. A4 p# s/ Z

0 B7 s% P( S" d& H         return true; 4 w9 D* Z3 C5 x8 i( c( j

4 W: h" N4 u, B: h5 i7 m; c, A. i+ c/ d

0 G3 l6 t, ?3 d: v     }' d+ x3 I6 Q q5 q7 J1 z; x

L1 G- ~3 ~) ], k u

: e! L. V2 E" |4 H7 s 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 3 ~0 T. x1 @( \- W+ S8 \) |. M0 M

; C7 T5 T( Q% ?- `

# }+ s( Y" l) D7 ? 接着会进行真正的保存。通过$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文件。 ) X) i0 H4 n6 x* o1 o* M) \

6 G/ n9 d$ X# ]* q4 j- r) d

% k( h$ j- B* G 漏洞利用' C7 i0 C6 [# m4 Z9 R, U

% U6 f0 f5 R! ]& Y( ]/ j

6 E" w& y/ {# `1 J 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。+ L. r4 r& y% e5 Y; u

& g k& j! G) [

/ P8 }4 ?+ P2 A. t   ; x% {4 F8 w+ M K1 V8 \; ^1 Z

z5 G% v* a4 r/ N

9 b5 W u, P, Y( C, }1 B 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid( Q* X. Z z, `2 e s5 l5 U- o

9 Z) c$ b5 y& ^; V

! y) n2 ~4 g7 _, r 不过实际利用上会有一定的限制。6 \' b( G; i4 D' O

/ d* M* H3 f' f9 m5 y7 {- B

2 V' p4 o" n; O' ]/ D7 ?5 T/ G# D 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 : `# d: C* j, e L: v7 [, ?* A

( S4 z2 q v- [" H

6 b# N7 J; O' I) o: g7 L  ! u" |& F4 {0 d# B# M: }* n& B8 p

5 o# ^' X6 s' d' o% Q

$ Z: ~' c; F7 I0 F Y- ?1 R 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:. a9 ~4 K( V c2 ?

0 F) ~8 m' [, Z( C7 b9 e g

~ N4 E6 U5 j5 A, A1 c 省略...$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 y* T* d: s& e/ x/ ?

5 q$ ~4 B. |! u$ f, |) S

) s8 {, \! g/ ^0 _; ? 因此要利用成功就需要条件竞争了。 8 n/ L y, `0 ]. ?, C

8 X. @, f+ v( |+ z, T9 c+ K

: F* X4 |$ q3 [& j& L 补丁分析 6 f+ }3 T6 z5 @* U* l

9 r' S* g' Y2 {( r% E6 X

4 l- ~" @: x3 a& X3 O! G  & ~- O2 K" F. v$ v7 ?+ I* k

1 h# g/ i% r6 N* e/ f$ j

% e8 C6 v- V( k4 u" A) E0 V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 6 B! A+ K' O0 ?9 b! [0 q' x- z7 r

0 {; F8 \2 u% n

7 s$ Y% m- @* H- G. a$ D function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}, z0 e- x4 i& q6 k# i" R' N$ T

1 n$ b* v1 w; s$ U: N

* O" [1 g" c% ]- ]. q$ O& }; B1 V4 X: i  ! D) Z4 |) l) D5 T2 t% i& n

3 V) J# u' v* U A3 F) v I

. K h# ?: a9 |; `! I" S0 W 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 / O# m8 B4 g/ a5 v1 G0 N

) u% s" t8 R* U" d+ j* L

. Q# F! F& \6 H 在is_allow()中增加对$this->savename的二次检查。 . J5 E0 D1 c7 b

* ?7 f+ B7 d) f+ X% ^

$ D* \ T4 I5 U' Z- F, _ 最后5 v: \( v" V; W: _; @! c* u

9 R: B) I4 K- P5 h( d H( `

0 G! M* D2 x/ F5 o 嘛,祝各位大师傅中秋快乐!# G- ^; u( ]2 X! K1 c7 c8 W1 U

, n; K/ v- C, Q* V8 `1 R& Y

! l) ?' H, K+ Y" k; U0 @. n6 [   , a$ q g5 K! P# U$ H

O* X% v# B1 b) `
回复

使用道具 举报

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

本版积分规则

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