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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
& x' w, m* ]& ?8 r% A5 q3 j

5 J {& z& ?" W3 V

' }3 |. _) N; ]% @1 a. X

: q, I9 M' L6 T1 F9 |, h) r 前言 7 W( @8 D0 c" ?

: w: c- K/ X$ X6 G2 k3 X

m( f! F8 C$ W' Q& @8 Y9 u: [7 g4 m 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。$ ^& }, b/ D7 y! O6 ]2 T! v

. S) i. k" @' L5 \+ G4 J

6 C# m6 P6 C* E# [+ T( a. Z2 k   * ~; h$ @- J/ i2 C. I7 z5 H

3 a- o# W+ y3 O1 J

, ~% G# e: @% a3 A 漏洞分析 + D: K( ]6 G2 g) h6 a" f

+ `3 {) x \# F+ ~9 K$ x6 H0 L

' }! [# e" {* \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:" V# D- k' ~* |9 {$ n* ?( ~

" H' r% j1 Q4 T* C9 N/ f

" g5 {, ^: s% M- [" }4 [: c$ R$ A   ' X9 z- h7 G1 L6 i6 {. N

% z6 C6 z. h* G& n; O# m3 I* y

8 |# G6 k3 C7 n% ?+ x( |0 u 对应着avatar.inc.php代码如下: & ~6 g. y I" B) ?+ p/ b8 `

4 }0 V9 k1 N/ B) h5 z- m

9 ], c* ~# ?- A7 _ <?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) {, Y( r/ o+ M- i" h' y" ~3 A" f6 R

- N; D" H: x/ r! O

+ K; p, _$ k+ Q9 d: m y     case 'upload': ! S5 F1 |8 d% F' [; J2 v2 y

: U" G( ]1 U8 `- a

( Z) M! t [9 @( C+ V* e8 s! z         if(!$_FILES['file']['size']) {8 m8 f$ n0 f& D2 n$ W$ Y

. a/ y% a% ^+ r& o6 h

8 w/ \# l# }% r7 q% L8 ], n! ], \             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); + @# X: W0 l% \; K( \' `7 E) H( W

1 ^: P4 {: I! q4 Q4 [

+ ^/ r/ t# N0 d/ ]. p- E% K5 H& G             exit('{"error":1,"message":"Error FILE"}'); . q3 F/ b, h! Q' a7 Z4 A

4 M* S9 P( C! [; d

. Z, d& b/ \* D+ Z! A; O1 @         }- f! C) R7 Z1 Y& |* f

0 Q9 D$ M' X; \' j& \

# O' O) N) J: M: L* i n% Z4 j+ b         require DT_ROOT.'/include/upload.class.php';" m5 Q* Y6 A) H+ x

+ C0 g: [: e9 t0 \& U

! s$ {% f4 j% d0 F2 B s+ m0 f   : W& D1 ` c; Q6 j

- x E, E- q+ l, {' a3 S1 X

& k( f* v8 C8 L* ]- ~7 b         $ext = file_ext($_FILES['file']['name']);7 ]8 o2 `, t! Q) h9 t+ L7 t6 O$ I

( |: q4 \. a. s! L' Z0 Q% ~( @

( j7 O3 r9 V3 l$ K. ^         $name = 'avatar'.$_userid.'.'.$ext; $ E* {7 a u$ Z0 o% p' S

/ r7 P# Z7 M8 L

" C! s; m7 S9 p' L         $file = DT_ROOT.'/file/temp/'.$name;8 @) a0 ?8 c6 l4 U: A6 Q

9 U0 ^5 J9 \# C( U; }

2 D& z' Z. X# Q& B1 x& E6 h   , b+ ^4 N4 H: s0 n1 W8 ]2 C4 h

( q! S9 Q" j. |1 N o

) O! y2 ^& v% n7 |7 b         if(is_file($file)) file_del($file); * l6 l2 z4 u) R; p6 I j

% E5 }* e2 a! E* q% y

- R# T' i0 _3 n2 M" D/ }4 h R         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');0 z$ c! ]/ B( {0 c5 }) P

; h" P0 W/ `! L" i' y

# D+ ^* l1 [3 v9 m/ e4 i$ m9 p  & Q O; z8 U7 d8 A/ h/ Q- ]+ R

8 K" ]3 R1 _6 a2 j! i& G" p

! `/ g* G1 l+ u/ d: o         $upload->adduserid = false;* ^ l* A' |! f8 i) v7 t4 t

+ b( y# ` {1 ~& Z! T% N0 T

- {" F" m6 U% \! g! `# q8 q% m  8 V( N5 ?! @( W6 P

5 s8 B+ O. z$ V3 ^; T, y

. o6 ?6 Q' y* n         if($upload->save()) { ! x- H+ J% _6 U! T5 c8 H4 u( `, M

+ n1 W. |7 y- e* Q

A( Q, @9 n% A7 `( K& I; f             ...! i. r$ s+ B W3 c3 g7 N

2 w7 l* V; U0 Q6 r/ C9 ]0 h% E

! x/ \- b, _1 ~3 r         } else { . |1 {5 l. T4 b( j- G$ c

( t# m& M V5 t8 j/ K7 M/ }

5 V" c8 l4 m( o* Z/ ^             ... ' h$ q6 T+ j$ k6 B+ R( Y: ^

b% K+ h( V; g& J$ P7 d8 |

$ o8 }! Y- k5 |         } ' t; [- `% R% z

' v f. C- o7 f8 v! s

4 L! l m8 L" J5 l: o% h     break; * r/ Y( y# {/ ]: f3 n+ c

$ t4 m6 S* ]; Y9 U1 q

; U; {) y" U8 m7 b# a$ E+ E* ^ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。" [( W r( L) V

9 U# T' Y: @4 T

2 M5 D! c/ N) k' ^% \, d upload对象构造函数如下,include/upload.class.php:25: 9 j" s) S' S& g* O6 T# j# D( R

0 x5 m7 H! Z$ Y! I. ?( u

5 w0 K4 z- N6 ^1 X6 y1 @ <?phpclass upload { ?2 L+ n! T9 }0 h- f& x5 ~ Y+ I

$ _/ C p& F5 q; I7 h- j6 C

! L0 j0 I' V& j* f5 @7 |     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 2 |9 q: R( I( M1 A

6 `* a ^0 E1 X7 k, P

) z6 ^. C' `: W, c         global $DT, $_userid; " l3 f- L7 x9 X+ N

0 C- N3 ^5 \& k" y! L( a3 z

" u( b0 I$ A8 s7 M& _# b         foreach($_file as $file) { 4 |: S$ M& r; J+ I4 H( W8 I1 m

- Y6 t# @2 v0 l8 y. Y. Q4 L

( R0 ?! z- G! F             $this->file = $file['tmp_name'];7 {; ^/ ]7 J- b' M" g! Z# e" @

0 I- J* R0 Q/ {0 K5 p

2 G' a' A3 e1 }             $this->file_name = $file['name'];3 d$ k7 Z% A0 u( ~

1 m. r X- u# a/ k1 \# W7 v2 L# ?9 Q

& [6 L: a/ p% G1 j             $this->file_size = $file['size']; # T" s9 W. a" J; j

3 F% w! k" N+ K- u

( V- a. a4 v: a# @3 P0 ]/ ~             $this->file_type = $file['type'];* o) f9 e8 @! Q: Q/ m! A- H1 U0 {

! [8 r; h( d' I

; K+ |! i9 E9 v' _, E             $this->file_error = $file['error'];; v$ I4 U7 L6 J

7 g( }! `4 b( N0 n, N* C: w

6 n; w" `- V5 k( N! s( D3 E+ l   ( ~0 I9 U/ _( v r5 ?

: k( A. s5 m$ N8 y" s

- j( `+ o( p2 H( G \; G         } # Z2 S' r5 p9 e3 J

) b) A2 F3 a! v9 e( n, u+ O

1 F# J- S3 H, \         $this->userid = $_userid;) c5 o6 D2 S% R' z! A, U

+ Z+ c" S$ d# ~$ @+ {( ]

k9 J& j; P% K+ k+ ^- o         $this->ext = file_ext($this->file_name); 8 J; |, I# u- s/ l& v5 S; a

* F4 b2 i0 S0 S

& D. a" V- j6 _7 N0 U9 h3 _         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];' ^% g2 U3 z- Q8 R+ k

4 a; i/ A5 G% E0 o9 B% S

& M. n+ z' d# H( y( f6 R         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; ( F2 b3 g9 X1 ~0 p6 s7 V8 R

3 z) A! A, S3 \" `9 z6 e4 H

- K% V6 L2 _4 Q( x& [         $this->savepath = $savepath; n* U1 H( J2 R) L4 O* u }* [

& C: Y1 [3 z% [+ U

. F3 t: w7 ]% | w0 h0 u         $this->savename = $savename; 4 ]; s# D! {+ b0 T1 v

! y3 z& x( y0 ]4 h: r, B: U

2 ?, I+ ] w+ q! v     }}) }: m/ h3 M# @# H& I7 F a$ q

* {' L3 I$ v! G% h

! J( |0 T( R! O" a: U j* j 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 . e- S8 u" P) m7 b: @: n1 \2 W! }+ i

5 P% w& A9 s7 v3 J1 `6 s: _, b

0 w+ l. X3 A7 S2 B 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 & E {+ V: f* r3 @# k

3 g1 I! `6 R+ I- C

) i+ f7 I+ u5 { $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.php 2 j2 Y; P1 u2 p _

3 [. e+ G0 q" E

! D; v: O! p/ s. i* P 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:, c6 k; n. i# A* [0 k

* |2 C% R$ R9 m# p x- k: {

8 U6 w5 {& a$ v" U, n5 Z+ _2 J   ; h+ y6 D( i- \; N3 c

+ p. ~! e: x' z

0 l, _6 ^: x# M 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: ; G+ Q# B. N: B6 H: I# x! K

$ ^7 d: |; J7 H

9 T6 G7 I" r0 \3 k <?phpclass upload {, G' ?2 E% [3 n

: F8 w- F, G) S4 w7 W

+ R. c8 G+ ?; Y     function save() { 8 t+ a2 N4 a' N/ \

4 H5 w. f, r, T8 q. A7 z

; P w' C5 L4 h7 H3 o- `6 \         include load('include.lang'); 7 x% q) x- c1 Z5 ]! `$ M& }

; V- D& N. J( d- N

8 a% ^) m; O) X! r4 H         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 5 X2 ], f- w+ w9 O/ X. h5 M

4 A6 c" d# V+ i

& e$ k9 i3 R+ t2 s) W   ' f' e% ?/ s. X& [! B

2 O( m( [% L2 V3 Q0 T! ^

# V; E; p3 e0 |, z: i/ q- `         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); ! g0 X _% S# _+ k

4 L+ H/ L+ V' `# G' W5 C* S$ z

# s9 |/ M. P$ C& H( c8 D2 z/ v: i. \% ?  . P% s' p3 @" ]8 f: B: ]- S

" J; K9 ?8 F0 s9 T$ o, ?

- n+ B* ?* Y' J" \         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);8 v6 L2 W) C F& {: q- n1 A" i

+ m# F7 v& x- `" n+ `

) D! L. D( J! d" B9 ^) q  ( k8 W) s \: t" R+ q; I: A

5 l+ y; |) e. N. W+ O# M

6 r+ Z5 P! |- e3 c* i! P" K4 H         $this->set_savepath($this->savepath);3 P+ G! j4 o* C, l( d

; J& \6 ~3 p- ?1 [& j5 @

6 n8 u9 E' ]' a: O         $this->set_savename($this->savename);' K# C" w1 g" `+ U1 n) Q x

3 q( c! S0 P' t$ C2 M, V

6 Q0 ~1 E0 b! Y3 ~. X, [  # {. M3 l5 ]& Q* |! a; I: P& Q

0 E2 ?- A5 Q; b3 Z

' {2 ~( P3 m$ ~5 w4 f! V- Z         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);7 S F3 k1 D, G( w. Y

4 c8 Q+ @* [$ v4 u- D

. A+ q: X4 a2 ~/ o9 r         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);4 |3 `$ b7 j k$ i! \

$ C- G8 F2 B0 X8 n) K* {& Y1 M

! E- e! W7 F. [ f; U         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);8 q6 V& R4 @4 w5 A. |/ z) r. w

3 P7 Z# U4 q% B1 [9 t

e' k# i o- n0 I, \  6 _$ i3 c* w+ T1 l9 K' C

4 I2 E- X& y7 H/ n% I" ~* j

b: I0 @. ? [# b# C         $this->image = $this->is_image();4 t/ o% ?+ W# x6 H/ [: ?& {

, ]! k+ M. Q' @$ _( f7 V9 J

2 D) H; V) s- M( B! S         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);8 P) a+ e" d* R1 E, k. ^

! J9 k7 K! J" @7 h

* f8 q& R$ a8 l) F         return true;: l5 G( |! P+ t

( A, z4 R0 x/ f4 S" s# F/ y g4 v

$ g$ x. g8 w. v4 k8 F     }}/ r5 e# A3 O- ]7 p' U

+ \# [8 |4 V& ~# a& s$ D* ^

/ M8 J& M: k" B( U/ q 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:( H+ Y Y8 M$ _7 `

9 N; |& g/ `2 H2 `' R' ~

5 y6 j; z& O1 j <?php & j1 e6 j0 f1 M; x

+ c \+ N; v4 w3 O4 m

# e7 E4 A4 w1 v+ ~: g8 B3 Q     function is_allow() {, t# {+ }) S: V, J4 p9 ]

+ \' F; Q6 @. Y

1 R; b& {- V, ?0 U& W3 O6 o, P         if(!$this->fileformat) return false; 2 d4 p* X3 `8 B @

) U0 v/ E; B* T$ X, t" c+ \

t0 u0 ~- s2 W7 A4 }, o         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;* P3 q k/ L' y

; S( m+ ~1 Q/ X- U+ V3 O! G

- t/ v: K# ~* l" H& g9 Z7 A         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; 9 Y: U' n j% C g( g3 S

4 A3 k% h& o. D" J; }. E

' F1 o+ M3 a0 [& v         return true;" f2 v/ G/ Q, l5 I5 M5 I

! T% v/ ~% |( Y& ^' w% e: }) y5 b

2 @/ C7 M( K; M+ A( N; z     }9 F1 T' b6 e" v% h8 |

* c+ \' R' H. F" g) p2 C) ]- a3 C

# ?# U, h; f& V0 V 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。; i- [& W: g, l' u% D

' ?1 Z& A: M3 I+ v" W5 N

% Z9 s6 @" b3 Q8 C 接着会进行真正的保存。通过$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文件。 : r" F! P1 w2 z5 V, D$ g

# i- u2 }- Z( h2 F3 T6 V h

: W2 D0 j8 ^' k' c7 w 漏洞利用 ' O8 H2 F v* M% I! D

7 g, _$ p8 ~7 _. u7 L

) r- X- }. W: {2 C 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。) ^* j9 H6 Z' m0 ]( T

& X& ~. i& q% X& E) y' X% R: g

" G2 t2 h4 Q, a- W% A' ~   * [+ m( o; S+ {: t

, J( \$ W/ D. T+ h% {# X3 s8 [

7 e! e. _6 O& S7 K3 d" E& @" n 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid6 R7 m6 f* z) _# b/ }2 c; x

4 G7 L+ H! @6 J& p6 x* R

2 E: T$ G" S$ ` 不过实际利用上会有一定的限制。 + k0 k/ j2 t' c* R- U) s( O

7 d6 G* ~9 P$ a8 K; S4 E

5 Q9 J$ r" W0 C* Z 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 I) e6 l" Y/ v( G* x: A

: p) u2 J' {5 ^3 X

% H' p. f. d! n7 G  % d2 [/ C; _& g- X* f9 I5 x, e

* b% K/ q5 T: X3 b* G. ~

4 p7 F# A/ W9 e6 j- j7 k5 x 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 1 c' L: p+ M0 i. s: o+ u/ F6 D

9 ^ |6 y# z) T1 w

1 i9 c/ g: Q7 u. k" m( c. w8 s( u 省略...$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]);省略...: i* l" T4 x1 D

3 a! z% A, r3 a; `3 ]

$ S" ]- q3 Z" C3 x0 X r 因此要利用成功就需要条件竞争了。' Y& p0 G6 |& Z

1 t3 s1 t5 [ S

( I% _5 e- s# O! I 补丁分析 # L& O$ z) |* [8 J8 Q! o( {

$ J k" ?4 c6 A. H3 O \

# v' y B! w7 d m' A+ z   ' F3 N6 I+ s8 _& @, C) p

7 q) _# E; K( U; Y

% a0 N) t L, U* f. V# b/ R 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:& _/ A6 u y4 E! V4 p* o$ _

9 i: `6 e$ L4 W8 |) j4 l4 X

0 E" N1 C8 t% I" C function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}& ?4 w% H2 K5 p( d0 u

4 u) C/ U% N/ _* P& s6 N

9 \" f/ |0 R& F& f9 O; R  4 Q; i7 n" }( z8 r

" b' ^; t& _1 Y) K: J; C

6 B, q1 M! j5 z6 } 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。7 b8 i8 ~$ r) R3 l

$ n, {+ N; F) E$ {' c

/ X% R( e6 `# H% k: ] 在is_allow()中增加对$this->savename的二次检查。 ( b9 Y) H" ]3 a6 D% ?+ V( }

: m) J4 [- E) \) j

8 [& s" L( \" J: n5 O3 K- y 最后 ; D1 \7 N5 f$ n) H8 X4 y- U

; U7 k, D1 z+ K

+ `) @9 r: i1 p) ]) w 嘛,祝各位大师傅中秋快乐! & D7 L @0 k$ l& }0 @+ t8 k8 A

# F% V3 Z* f( {

6 Q1 i# e2 q5 Y2 P+ w0 i8 |  % Q/ u& Y2 ?! Z' B! k7 G, Y

% @ y& `' G" _7 I
回复

使用道具 举报

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

本版积分规则

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