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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
% V6 m7 B D! G2 ^: u

- \; g) G# l% h" ^0 Q- h

/ S( {, j. u( _# n1 T; G7 |

/ O# E6 ?- `5 |, W; b H8 f 前言 ) O2 a. o; K+ E) v: p4 S

; g8 S& Z) T. S5 t

* I. I9 a" k" V3 A2 K9 A& A 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ( T! d9 M2 U9 J/ e

( m4 H* K" Q' C6 Q

+ d( ]' b. @& ^( c7 H7 M) G5 b! Y2 A   1 f3 L6 X3 }8 a! K! u

8 z1 o& h7 [7 p

z1 {) u3 I4 |1 C9 ? 漏洞分析 - S6 ~* l- v* X5 _8 C) Q2 t

9 z8 L; W, q9 u/ Q3 @* }6 l) Z6 r1 Z

$ G- u+ S; _0 [% m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: ) h6 w* {" Z. j8 L3 k0 x

, j$ e- {8 Q, v5 e% h

" g/ |% t2 t. y3 M( x$ e H   8 V1 J+ ^0 Q0 Z! t+ X" F* L" E

$ h2 K2 R8 A: D! s# V; q

5 g8 w8 J& q7 J* l2 l 对应着avatar.inc.php代码如下: ! R* K( A2 |, F& [+ u; J' J$ r% B

& j) h2 b+ B0 i$ ]' x3 Z, X- F

. m9 [) ?/ u5 @" L- u' ] <?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 | b3 ?% ^7 U1 E S5 x( Y# Z* v

$ e. \( [: F, S9 ]" Z

5 i& h1 A% F) G. ?$ P+ o     case 'upload':% K$ ]$ U% U' H

- U* D0 y+ M- }% {1 l5 Q+ z( [( u

6 D- E5 B; n6 a# @- X6 q. e) E         if(!$_FILES['file']['size']) {$ p( s8 D4 c; [

" |- P: U3 R% v% f9 D; o* Z

- ]! C( R" @3 n3 y$ V             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); ( M6 }( T! ~- f

; k8 K4 P' V; w' Y- e* B2 Y& g8 n

- _. d$ u# {1 y+ }6 t             exit('{"error":1,"message":"Error FILE"}');7 y% J% g8 ]: x: e' x

/ r( j" V( A. ~! ^

; p2 m( V5 D3 \         }+ K7 X* c: I: r- s S- U8 F

1 g9 C9 y6 `, q5 S% D, P8 z6 [

# F! O5 W/ W* w: {. j" [         require DT_ROOT.'/include/upload.class.php';6 \( W( U# {% P/ |6 L

$ [ c( R# Q5 g, Q% I# o

; G5 G d' k" ?7 N6 j' l   7 E: a" Z1 h2 u7 t

4 d/ ]: B& }5 |' j8 ^+ J

2 t; Y( P' {. I5 A         $ext = file_ext($_FILES['file']['name']);7 i0 m0 S/ x3 b0 c

+ w' S/ u8 L9 V7 A$ C3 n3 Y( e

( t- s1 q3 L) h7 C5 S6 z/ K         $name = 'avatar'.$_userid.'.'.$ext;) ? p, _( I( m+ Y- s* O

& u3 } H4 Y& N3 k2 a( L( h

- k7 t+ z- R" Y) G6 a         $file = DT_ROOT.'/file/temp/'.$name;0 H. l% b7 j" H; D2 z

0 K1 a5 X" v, \8 R; \& z

2 M8 I F6 Q0 y9 y' @; D1 ~ G& ]! d  " z2 J) Y- W# K' n2 q

; |9 M W" T0 f% n5 T

3 A) T7 P* z) E0 e. p* E         if(is_file($file)) file_del($file);* W, U7 [. W; h! @- a

5 ^: n0 D* W: a8 ^1 B* j

7 Q" `% x( C5 L" h& o( k         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');, o, G% Q7 B1 D0 Y+ [2 B$ }

H. i: s7 c1 d$ ~( z' F( \$ w

7 C+ z: s. P4 T+ X& O3 ~0 f  % O2 m+ B0 C z- N P, _8 g; |

7 P ?, y5 z6 @: _

) X) b; q( l5 R+ i6 ?* @         $upload->adduserid = false; 5 u7 O9 t: e5 f+ U

2 Y- g' h, v9 U) ?

6 P/ h$ }7 j. Z0 p) m% r8 V   # U7 m& c% S8 F5 L

( ]3 j' ^# O# d' R4 P

. U) V) H; T: h6 D0 e3 I         if($upload->save()) { R6 m% U! o! m' x" q7 Q

; ?4 G" s+ V4 v; z) @: s

1 u% E6 D- n1 J( d6 T. _             ...! S8 e: n# \: T9 X5 ?

2 q p- ^) z# L% ^

7 L* H3 C# f; i         } else { $ g4 @) y7 k" Q4 s

6 b1 `4 ]# E; P' B! v6 l( ~1 w6 i3 y

7 ~- l1 T" X4 l, @' A3 \* G n* U; Y             ...! L. x( @# P' `" @) V

# L' K3 z9 n% @- C

0 _1 P6 O7 E; Q; [& Y8 E" ~         }0 m9 ?, R( M1 _) c

; s: [" t% j7 U

& ?! {6 {4 o: q, ]# P$ ~# l$ _2 q     break; 5 Y2 s6 s4 q. F7 w, Q& i8 g* ?

# k1 P3 Q% |, a4 |+ h% u; i6 [5 |

' ?. b! ^3 o# e0 Y( O0 z- o 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。/ ^* Q, f, G) K8 _$ D+ Z0 r5 s

1 l0 Y2 h9 a: j6 A B

6 X& m9 O1 O9 S- ^+ f upload对象构造函数如下,include/upload.class.php:25: % ]- X0 q+ b7 R7 n8 D9 I

! o1 i! @. C( }' V E

9 r3 J; s# z0 M C. {# r i, X- o1 B' i <?phpclass upload {& [) K+ T) C: N9 w

3 g8 h; E- W4 o* L0 X; D% B! Y! _

0 r% L+ U( s, q4 g, A7 C     function __construct($_file, $savepath, $savename = '', $fileformat = '') { , H) @9 y# d' A

# o3 Q) ]+ |9 e0 I+ ]

4 e6 E8 m0 P0 M1 a- t' T; R         global $DT, $_userid; 3 G4 X h+ D6 Z& |, |& E& Z, n

0 o: P% U( C7 q% D' M

; v4 [: @ ~" _5 k \7 g" g         foreach($_file as $file) { * I. D' u- N; X3 S- Y- N" } b" A) f* S( ?

6 V7 f& D/ @: X, D9 P: }' k2 x

* S1 l* E6 F( H, K/ Q& H& ?             $this->file = $file['tmp_name'];9 Z+ _( ~5 i/ W+ ^( y% d

% L6 ]2 ~5 D% R% H2 q

1 x \4 W1 |* h6 q" s             $this->file_name = $file['name'];$ I' T3 f8 O) L& h) u3 u; b

1 N1 i9 i. f, u) x# `: X0 Y/ M

3 ^! e! D' m+ I8 ^! ?. d) _# y             $this->file_size = $file['size'];4 X; {& o6 r9 [- M

* S; Y# v" W7 y' E3 D

( H7 Z- Z4 ~+ i             $this->file_type = $file['type']; 9 j) W& x) h1 o/ L: V( b' T& u+ {

6 r9 ~: N' r4 n/ K3 O$ u' j

6 }( D5 U) a( j# o% Y% F             $this->file_error = $file['error']; 9 I# L& ]6 `. j V6 r

! Q @. l; W, }- a- |, @2 R

f# P5 K7 _1 X* L+ M. U: o  8 W3 {, k' T+ U( I" W. V

: J+ o7 p# b8 H* i+ C

8 c* X+ ~1 g6 z0 J9 m         } % R' z2 N* G5 c$ m$ l

$ Z+ _" Q% B% ]- f

" l7 P, T Z! B. h" M0 V         $this->userid = $_userid;- }9 m: J: Q/ [+ y- o

0 w& s$ _7 D" y

" x- i: a4 r8 C" a4 M9 E$ j         $this->ext = file_ext($this->file_name);8 z( }8 e; e7 r' Z- ?

" M8 \# k) L/ d5 m

* n& }8 r9 J+ K         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; " Y Z/ E8 Y s8 A" c" Q. Y" B4 W& P

a& q% `/ [. E$ M% p. a1 u9 o+ S

' ]( s; X' A# P' F         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;3 w) o7 w& _* _+ w( m1 J

+ L V& v! ^- B/ i1 C2 D6 N! |

8 {; a; h0 n1 U8 _9 \! u% ^         $this->savepath = $savepath; 2 ?' Q8 `* S5 O/ h; L

/ o2 ]: ^5 p( z! ]9 N1 s% g5 y9 z

1 D: L/ Q. ]( p7 I: S+ u         $this->savename = $savename; ! ?: k' Y6 G" X9 r! Z

$ `. t3 M" H( s) G( R, g* C2 h

9 v' ?/ k/ o- x- H) }     }} ( A: R$ w) M# K1 i& G

- L! ?9 D4 G0 W# s, ]+ F' L& n0 t/ @

- o' {- J4 _ X' @) D+ l 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。7 `4 ~. H4 [" f' U) r2 x4 I

0 c6 R- i+ t/ q% ?2 H0 `* }

4 h& y# Z; {; h3 ?! N! C5 t 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 8 P% U8 g' [/ z# k

4 v7 _8 a+ _9 S: a) W% k

3 J# N, @. }7 ? l- ^9 [ $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.php3 [2 P; Z3 m& Z" c- f. P0 b& d

* Z% X- e3 M) t. _

+ P5 d" F' v0 f+ x; x4 { 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:) O4 t8 o9 o8 m6 P* w7 X

3 @6 Q( h7 s D, }2 @+ ~: d2 i

5 m \+ w' P/ D. s. I5 G   " P2 S+ ^# w' r2 _3 u

- V# Z- I8 _8 W# l

& F9 @) w/ Z$ K' F4 d( Q* P 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:# t9 F9 n5 K+ J4 ^: Z7 ]

( v i6 ` k. `* o! {; U; a( r

) ^( e0 m5 k/ w6 {; H- s& o1 a <?phpclass upload {$ \) v+ t3 n( B

# @( l. b( a8 E% }

6 u, o Z4 Q" S. T1 |0 q( T1 \8 B7 ~     function save() {. `8 q( X3 ?) Q3 b( l- g

5 ]+ y3 b( H( B" X/ F1 z9 Z

1 b+ |* C+ l7 |/ a         include load('include.lang'); % _; m8 ?# L/ g& @+ R! e

3 C4 d% Z* \5 W, l

+ N2 ~7 `1 ^, C4 O9 D% f         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); / K v0 B, k' w. c

; g1 K) z s0 v" `& n' `$ ?

$ S- o) ]% U; O( n E" [' H0 N   8 d2 h; b; x; n; z" X

5 E8 g; y9 ^$ a5 ?& m( p% R: X

# q. t0 B+ K: [4 e. D S" z; R& A         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');- z0 B2 R1 N S$ [: L7 k

; S9 r5 F6 o! Z7 l9 N" ^. c

, q/ [, N- I' ]# O, K   : r) b3 l, J" `5 v& f9 S* j+ @

6 `/ d, H" _- B# S! r

% i5 d% q1 ~7 W' ]6 o' `! N         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ( \! S0 E/ d* j

3 x: G! V4 g. Z' ^9 h4 z, p2 `

( A1 k1 g' G$ {  , [3 Y3 ^0 \# F9 F

. K5 ^; C( T4 m- m& l( H

- x: J% r4 r& ]9 @         $this->set_savepath($this->savepath);, S# Z3 ]* R) N$ ?7 f

3 a$ }6 d8 R4 [

+ }( x+ D4 x* P0 V         $this->set_savename($this->savename);! q' H0 w# L, ^4 \1 j0 c- f& l2 E

# j* w- j/ g% z

8 H7 N3 V1 x( [! X4 ~- {   - |) @8 ?3 s- r! Z: I) Q/ w

5 J! I- \& s( I+ C. j

: h1 A K- o/ W: s* z         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);) }: z+ Q$ _2 Q o( g% R3 X

9 q7 ]' _: v6 r9 b

: ~5 c9 s( _1 K- g! ~8 t         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ) F# G# R+ s# V0 |# [/ m( r; g: Y

; G1 _! |+ ]" x+ q. ]1 s

5 f( L) K) g3 k* N* K: J7 [         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); ' g% y* K9 k0 y' T1 F# w; _" K9 L

2 L6 d4 e4 n# _. F

O1 m2 Y# \7 f& ]8 _" |* {  $ S! b7 X$ I0 m7 N0 I- ?* g

/ ]2 z. f5 ~) O4 x

, F7 }! m8 h3 L/ l9 L         $this->image = $this->is_image();. G' N. H# h1 K9 J& |3 Y

$ z4 B3 s5 i: q' H) e6 t

$ R$ N9 r$ R. y% l E' g( c" K' Y+ Y         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);: \8 s+ O9 Z" V' O) H# U

9 `3 `" ]4 o8 p$ m1 D3 c- s

3 j, Q$ a. ^1 b2 i& C9 p5 f( S T         return true;4 {5 D/ H9 w- g: R" u

2 q2 I9 C/ n* G* a8 l0 f* r

1 c7 M2 t' [/ m) w2 i     }} 0 h7 B# ?# E4 q# k# ]% H* @6 ]

( o. d# t3 ~" n

% |) o; i1 Y9 [) S w, ` 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:0 ]+ W+ K H5 x, F" k

) @0 z7 m' B- k# e

) ~* w6 P' Y5 q# Q <?php , N: g( h! Q1 m7 U% E# |/ w( `

7 j* O" H, x7 q# J

- l1 G" p/ Z# ]1 k' E3 N     function is_allow() { $ a# P2 e1 k' b1 z3 r- N( [! a

- K* m6 u* t8 A; j, d! A5 ]

; I8 |# V8 ^# B8 I5 i         if(!$this->fileformat) return false; 7 o! F, _# y# g' k: c5 u

: h" F; e1 n+ T. N* d' T0 D7 L

$ D2 U* M, ]5 \         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;2 l: ?1 \; |% J ?, D% I

: T2 Y, M! [& b% j; x4 U

1 v! S% t0 z# ]; R' u% a F         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; 6 i" n; w: p" b

) N: ^2 S" G1 t( ?5 l

1 h7 R! y2 W% Q% d7 \7 I& l& @         return true; & K) u) t& E2 b, w% x4 u& |

; f! u1 p o; L% g

* }: a" `! b1 x+ H# ]/ D7 ?) e     }* e$ v( z# {; I* U. b! g4 ^

1 M2 @& u ~2 q, j' W' p

( E3 S$ w0 {8 O% ?) X. ?1 ]3 f 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 6 b" i) K d" s

) A" x1 a4 p) O+ s

/ m2 } C: f( K7 D2 h" H3 ?" ^: F 接着会进行真正的保存。通过$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文件。 ; o. b4 ?# x: O* Z- s

7 _3 i: P4 W2 j r" }- N

# l' D$ [3 j, P9 w 漏洞利用: ^9 B7 M- c/ O( C2 @/ X

* Z4 O) L8 G5 P t& Q* _

8 V0 \$ ~, n) t7 i2 ^+ f 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 6 H5 \3 ]7 G; k8 Y! M% P l5 Z

) O- r \* C& } H! w. |8 d

- i9 ~1 O" \6 e \& ~7 g% F( F# U8 \ k   " P A% E; r* g* Z* S: y

3 r; ^( m2 I% i

. j" c' w" B+ A; E; {' j' j* O 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid& Y8 k, W u( d4 ?4 V

, t' X4 E/ ^7 M2 @1 G

X0 U) z3 s8 O! Q; k% [( C. P. b: U 不过实际利用上会有一定的限制。 ; e, p* X% R- O; u( Z4 B7 z( y

! I) B" ~9 R# B( }

9 Q$ v0 k: Q$ R- [ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 4 |3 e: v8 E. H: c

: }/ g/ h' V' g$ |

2 p* O5 [ K( R; F4 _9 T9 B   3 ?& Q! {$ R3 m/ v3 e( s

* V$ j; k# ~8 p9 Z* z

% ?6 m$ ^- @" _; S0 ? 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ( ^3 |$ j1 v8 f5 d3 @, L4 ?- @) m9 C

( h! V0 \5 L4 U$ f

% l Z* x2 u% I4 i1 i$ r4 S) E 省略...$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]);省略...+ L( W8 u% Q$ X! L/ h

! b& T! Y0 q1 p* `5 Z8 |( ?

! |3 w9 i6 C M; P( J2 G 因此要利用成功就需要条件竞争了。 ( ?, k, z7 _! l6 ^

# w/ k4 D7 f0 I

5 r: ?& k. \0 G. I 补丁分析3 a5 J. I# j% `, a

! h& D" X4 B9 V9 o# t

' b; j/ G3 f! R' F) x8 j  : j3 {" j; W6 n0 @3 s

5 y2 x N9 J5 c; m, r; V

$ q. v! A5 [ q7 d, S4 t# c 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: H' w8 ]5 n: R3 V! {. W

6 R( B) [. E. A. p

" {3 a ~/ I! a2 n: O3 ~9 @ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ! A( N. H9 u* \4 T8 U( }0 _

! |% ?, M' H7 R/ {1 w0 l. T4 b, C; q

) Y( V$ |, A' G$ @  - V2 u2 y& a$ k" k$ {& K# e3 ^8 L

- v) t" O' M. U4 t

& J4 ^# ~" Y+ U5 M 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 2 @* {+ G& q5 `) h. q( b3 {1 ?

) g1 f+ O, j% K |; }4 ]8 U6 k

; k% w. \& j9 l7 O: d/ n 在is_allow()中增加对$this->savename的二次检查。6 l6 ]1 D( [( P( M$ V

0 ~ a! P! G) A% y

6 }; m1 t/ Q/ \2 w% } 最后 $ c) S5 \- Q9 R

) e* p& I( b* \9 V. h

0 [( g2 |, [9 N, P5 T( ` 嘛,祝各位大师傅中秋快乐! 9 m8 K( X* c7 q' `/ h0 A

' F5 ?) y# Q2 d t2 b' z$ m

% Z* @: D+ A3 v: J+ p4 e/ s   $ o0 g6 Y/ }( F# \/ b# Z# W

5 g: M* P0 r' D/ C; D4 g1 X- U9 p
回复

使用道具 举报

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

本版积分规则

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