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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
8 Y& C8 F- G* d7 T7 Q

& ^8 q8 A! i7 E0 }& \- f% E2 L* l

5 j, u5 e% I, f1 l( c$ `

0 ]% r4 X. }7 m: U. }. k# [7 u# R7 E 前言: P" O4 ~4 M+ y) H$ G1 P

0 j; U6 r" |) E* ~! f% d8 B

( k& b7 [. ^% w$ z: L 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ) K O) ?" E# n9 ~/ H* ], I

; x3 @. y/ z5 o

* M9 c% P L: D3 T8 `5 w  # j( a4 c8 w2 _: e/ v- H

% A1 W: p2 v. Z- n% u; f

$ l4 p3 t, N: b, J. L8 V( V+ I( b5 R 漏洞分析 ]5 t+ {) x4 c) U; D. ~/ J# [1 e

+ V! Z" t# q' U& z

+ M* t5 c- ^7 [3 b5 S2 G 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:" I4 c3 v, O: X2 @6 ^' s, c, s

9 m7 h1 z0 m: g# N- A

) {# Q- F8 q1 Q- W: k. L- j9 G  0 N8 e( K2 O4 E* @7 B \/ H

# V- ~/ q* E9 t. g- d

. ?# U' N# y+ \" S6 B$ n+ L2 ` 对应着avatar.inc.php代码如下: ! x8 y( P% y% n/ h+ B

' s& j0 o- [) c* _

- Q; D1 B' m5 E' 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) {# Z r$ J' j {) B2 i7 k6 t

# a' ]6 H( T! J

# j- }8 H& ] [3 o9 v     case 'upload': . ^7 q* ~5 n; i7 t* ]

5 w, s a) I/ n0 z$ ~7 `! d# z L& Q

9 v3 H6 O {" X v8 T: p         if(!$_FILES['file']['size']) { 9 n) @+ D8 L' C7 Y! d% h

* w" e: \9 H8 h4 Q

. c$ D' ]% M! f3 _* F             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);/ J: Y6 Y* x5 j% S5 s

, B0 L4 ^* Z2 f) h$ s4 j E

5 D N2 {: j; s; p7 g8 M             exit('{"error":1,"message":"Error FILE"}');/ ?. n3 |5 g, S# Z6 N6 Q J9 [, [

. e1 S6 p! g7 B5 b7 H" D6 k7 U

$ A6 r, b+ B6 C7 @2 h$ E& f         } 2 Y0 E$ u6 j2 M/ p, P4 Z" C7 z7 V

8 h( \$ N& ?: A8 t

1 m# S5 T; R* p) L         require DT_ROOT.'/include/upload.class.php';0 O0 J/ |+ u5 M% K+ J; w$ J9 _/ \, H

1 r# B% P1 F5 d, i# B( v

( k3 ^: K ~5 h# o  4 N6 w; w8 s( L5 E1 U

. ^) V# C( A/ ]+ V' w7 R

( [. h, C( Y) Q1 F$ n; v0 |         $ext = file_ext($_FILES['file']['name']); 4 A3 o1 D) p. Z$ N3 ]: R

/ u z7 C! b: n. A V

- Y" S$ J2 \, G- a2 H" X8 v         $name = 'avatar'.$_userid.'.'.$ext; 9 n ~. B8 L- \* W8 V+ n5 t. J+ S8 H! }

! t/ U" {( J: ?/ B% R$ [% r, X

$ v7 k7 I- _# O- y. S3 F         $file = DT_ROOT.'/file/temp/'.$name;) G& e# z6 c( p' ^( M! a

; M5 L& W0 H/ U, y

+ o, Z/ F- x3 {% T) U. ?' }   ' ~% e l& h! A- F/ Z2 r+ T: Z

/ n% y( \, a5 g6 U8 ~( Z

# O7 m# E g8 }, z         if(is_file($file)) file_del($file);/ L1 [3 Q8 p$ u& A/ [$ s6 L

. l/ H. _- U( B

2 O* L5 ~. F, q& ?+ Z         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');, H; t4 @4 S/ f, R

& L8 M+ `( i ~& F

( C5 M& g$ X" u: x* `2 s( \" m   & l/ }* @: W: [/ f+ Z/ S

) g+ {5 m \( K4 k L$ P2 C9 v

- a$ h+ E% g6 ?5 M( Q7 ]. E         $upload->adduserid = false;: T) q9 y1 l. s a, Y; F

+ S" n+ z$ y: t" v2 ~/ }" M7 @- m

1 {# o: u9 u1 h" L1 u5 w5 A   ' l W' d* @# z( D4 j' O+ o

8 v. P# g4 Z; |" p5 }5 Q! Q4 O* W6 j

* A5 m5 g% |2 Q4 v         if($upload->save()) { 0 j2 V$ N6 o7 C( X

2 h, }+ D# i. y g+ K" I; ~

j* m+ i" v: ~/ Z( c4 M. {+ Y9 T" E             ... f) s( W0 [* E

+ K3 F" N8 B2 z& R1 d5 T

/ a) ]# W& {# B         } else { 8 T/ D- I8 P( }3 b7 K: g7 G" O

* ?" t8 y# ^8 E" R9 X2 ^0 D! O

G( q- M, x' v4 C6 m4 K3 Y6 a             ...8 Y. }- Y% i* U' Z4 W; J: y. V+ C1 H

: @. }1 p! F8 y2 ]

4 d3 @! R- a3 n         }4 B) B7 u9 i% S) A) C7 }# V

4 m# ^2 T+ }' E$ I2 l, H2 J

7 M8 U% Y3 C4 O# s" E( E     break;8 X7 X8 r# _! T4 z

% `6 F( s, a+ n2 a

, R; h/ K: [$ O! A 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ; w) K/ {4 _/ d4 r0 c! {

# b; f E. Q/ h' J2 I

& J, A) O7 w8 J( W. M8 v7 k* n; B upload对象构造函数如下,include/upload.class.php:25:- N. H, V8 r9 M: W& k' F

3 @, V% ]& w/ Y- o, D3 B2 Z

! n( H( U: W6 G4 U& Q0 h$ ~8 M) N1 W <?phpclass upload {6 J1 L: v+ y- ^9 P

$ X1 }; N5 {' G4 ?3 l+ o' g

) D& l3 {' ]+ j1 K- J5 C0 e# E     function __construct($_file, $savepath, $savename = '', $fileformat = '') {# }/ H. R! k' {2 N% ^1 ^$ k

( P, u1 B$ h. r9 X O

- F9 M8 b4 p5 J2 `; S U( E         global $DT, $_userid; 5 s+ \& H4 n) i: U5 S6 o

) g- J a- I8 |) p

. A, x; S, f) F- w: v+ w         foreach($_file as $file) { 1 c$ H7 d5 g7 P$ q, m- g" S

9 a7 D6 Y3 k3 w! m- x/ e

' u' s9 G: h; M/ C- u0 J3 N% l             $this->file = $file['tmp_name'];; L& ]& X; \" W7 Y' Y9 }

3 G) b Q2 [" P. B1 k5 a

; _* s, K% i2 d+ _! f# M             $this->file_name = $file['name']; . y1 _; d( T% P B7 A0 x2 S

* z4 n$ ]- i4 P. `7 c [8 G* l8 V

$ y2 u: \" [( v5 R, W" Z+ M             $this->file_size = $file['size'];& J: ^7 N$ y- d+ a

# F) o* k- f! j

) c7 ?! \( V/ e$ b0 o8 w             $this->file_type = $file['type'];$ h& E% `# U0 B- P* j2 Z

: h7 x+ J% F, G% \

4 @6 y4 H9 h( d             $this->file_error = $file['error'];1 p( f3 O; ^* k2 c/ R5 h) c2 o4 s9 h

. E8 r5 D4 q m+ S

! A/ z7 p V6 D c7 g2 ]9 j1 k   + O+ a2 V/ L% a. j- D

4 m! ^/ G R* S4 ]% E' m# r! l

! w: t8 o( C/ n, C         }7 P0 Y) ~5 s9 T' I

, G! H7 k) W' c( c: r1 y6 a8 L& V

( g+ L5 l0 O' b7 j; C+ g         $this->userid = $_userid; 0 y0 S6 L7 x* a6 F+ t' \- R- N

8 z& A, x9 z: |( P2 i; r: N3 F

( p' N% ?8 e e7 L& R2 F! w         $this->ext = file_ext($this->file_name);6 \1 O* v5 K/ f1 C! [* V$ O" ]

4 Q& Y1 Q( h& R: k2 H% m

! w( j1 I% v8 {% p% j% r         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];* c5 p/ S, y ~

/ F. R' [0 ?& Z$ `/ b

) \# e% ^3 {3 ?0 f# m6 x# v         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 3 ^" w: j' D5 ?

* S6 s5 o4 u0 Q6 n" Y+ d

$ J2 N4 \' _0 ~: @( I         $this->savepath = $savepath; & M: ]3 i2 t' d3 o! H% a# {. k# Y

' }2 H$ }: {# B, S

, t% y) E" Z. W! x6 k4 Y: |         $this->savename = $savename; 1 B0 U2 y5 W' j/ i( Y

, K* [' D, @( ~, C: l) \

1 u, C/ j2 }9 j( M$ F- F2 E/ ^     }}! b8 M% j9 A3 I# ]' M0 |! J

, X0 q& } b! Y8 @# z

: ]* V8 w2 U/ D 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。. d, [3 L2 }8 ?/ Z

" r' L0 s# G6 u: w2 \! _# I

9 V u: a- {' L6 C. Y 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 . b7 f+ Y0 L# L# H' I) x8 s# g- p

+ u: z# K r( L! a4 Y3 m/ w

5 t& V, d$ y3 D* l. Z $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% }; n0 y5 j; l- e- h

5 I8 j) g. [+ q

; \9 ? k( G% k5 n+ e6 ^$ Y 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:) T* s4 N! q0 i. b: p

. h2 o) i8 r% ]* U1 C! Z. B

, p( n5 e; J. q$ W- z   ( S+ G* s0 l9 A

. U- L; Y+ D8 W) K( a! b' J

1 y5 q% B/ g% A6 G) i8 v 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: + j. c" m) v0 f/ D) i

7 U7 H7 ]) L) u& X/ |, q3 j/ K8 c1 K

4 }# ^! Y3 d/ U& | <?phpclass upload { # ^( Q l g6 x

% F/ }. z/ ]" e& t0 B9 W' P2 ^; n

6 W! E B; Q/ I; B f     function save() {" @1 q+ E* Q" l9 {' w: ?

8 o; Q2 l- Z: H- B5 `" y! @

/ H: Z2 u6 f7 I. [! h         include load('include.lang'); 1 j* x1 V9 T$ o9 r- Z

: m* ], M, R; C! _

! P/ q; u0 B) I n. `         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');. ~- @4 z4 {5 ]0 {* |2 C' b

4 H$ k$ S5 x5 l7 D+ [; k- C7 Y

- U* W" Y; _1 ^0 D; h   / \* B0 Z5 L: b5 d# m; U' _/ ]/ e

6 C+ T1 t% \% t3 s. B3 {, u1 Y

; x/ J5 i7 L5 v* Q( d         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');5 ~8 F. m7 h+ \4 { C+ l3 F

- o9 R, M2 q0 U4 k

$ P$ a+ m* T3 [6 g  " h4 m( O; m o1 i; a

% ]" k) m4 b. D6 n, T; ^& K, u

! d7 x1 w7 S( h! N9 M% ^+ l1 T* y* x         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 3 {: W' k+ _% p7 Z7 U" v) q9 k

+ @( t/ Z1 a7 x/ |, a/ F

- h+ i- g- o, F$ Z6 a* H) ^- Y3 k  : C( o3 |1 K5 N& v" O

5 t' Y. ~- ^9 v7 Z1 G

% u ?0 c2 M; T$ X6 E1 e3 ^         $this->set_savepath($this->savepath); & x9 ]+ k3 B9 i! p9 j% ~/ v/ C) g

7 y1 s- y' W2 c I3 r# h

# M A( a( P; {$ f4 y6 H         $this->set_savename($this->savename); + B3 x+ I& T* I

* M5 h' ?# Y; C+ R+ F* Z

W7 y {0 v# x' o  * p* b1 M8 Y4 y( y* U% l6 K' A5 U9 C% Y

# E0 c5 g4 K7 r1 c% G) ?

8 y! c: C+ ^0 R7 r+ l- c! A9 F         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);( s/ Y& A( u' ^% y# i

' |5 F) g9 S4 O- V: P# X e

1 v" s( B" L+ j2 \! S         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); # `: x! ]1 E+ Y

9 N1 `' k O- G* [/ o0 I8 v6 V5 C

! N# u3 X8 J* ^9 H' x         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 8 s H5 Z' K* a t* p

8 ~3 x: g' A% j

9 ?- B$ n9 Z& T% h  7 m2 j1 Q3 ^! d

7 a! _8 U6 G+ m0 r5 S% W9 r3 S% j

8 h1 e& ?* |. T: w3 ^, p         $this->image = $this->is_image();# ^" X2 r' @ l- f( `' r% l

) k; c' o+ z5 y' y

: _4 ^+ a; c0 f0 t0 `! w         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); , l! N. d1 y9 ^0 c

- O9 h2 N5 h6 W G) q/ z

' G. ^$ ^1 m* t; i* ^. E' A8 K         return true; # U/ S, o; A, w0 P& j

! q/ P& {# I- C+ H8 p) q

2 g: P+ z% @- z: E) K$ t     }}6 o3 [5 B: r. d: j% d

" g" f+ {3 U0 ^. g) U$ \; ^* J" y

" ^) G6 B) t, K6 h1 V- p4 l2 B, ` 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: : E5 n3 G8 G M5 @1 @7 F& t# }

( k u: p5 K5 Z; u) M; s6 O/ M1 |/ A

, u+ M4 l h, G <?php; h, e2 b0 j7 m+ y7 d/ @+ R

0 H2 J: o3 N5 @# c

! K7 R' b2 s- n# }/ u     function is_allow() { Q2 G6 \" ]( y! m! _

8 S* Y& N, e: C$ t, K; }: @

& ]3 _0 D" a) K% S; I, S( D         if(!$this->fileformat) return false;0 W+ `) @6 q6 q+ Z5 {( a$ A

4 S/ g1 M) X) c! w" I& _7 g/ A% b

+ W9 \4 y$ F& T# b         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; ; f) Z+ J, z' W! Z5 D; H4 d

/ b: c- c9 w) [5 k" c+ }8 a, P

2 ^: E4 m& y4 O; K/ }3 o. m( c. t         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;7 `1 ^7 O* s( d4 W

6 h' ]5 l" s* b

% p* t7 `+ {* F; f0 j8 D         return true;, O3 e7 Q+ s& x( c

! z' N* i3 b" I. \) O9 \7 C% H

8 j* \0 y$ i: a; i     } 5 W; l& L1 P/ B2 [

7 I1 D6 f, c- V [

. I/ o, `& s# X7 y$ M 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。- O( X/ ?5 F. F+ h

q$ R( B' k9 q; ~, i

+ ~; ~) X" F, Y }. `) R 接着会进行真正的保存。通过$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文件。 N# K/ `, V2 ?8 l4 U

. ~( P/ L) x. q% O5 D9 e( s

- H0 ~' c! Q- O+ o# U 漏洞利用 9 z2 p! i5 R8 s' p- c

$ f. z5 S; z/ s9 \/ e f

+ E1 c, O0 ~ M 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 + y; B6 p. z2 d3 q

' @ p, z) |' ~/ U# o% a

: D \5 n; N8 ~, `+ Y! t! m   / |, s7 b1 }( n

: \3 o4 p- O/ w7 l* l1 `+ c* I L

; c* ?# X$ j8 T: @( A0 x 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 7 G6 R! t% g# B3 Y! {3 `1 a6 {

$ c2 [ p" a3 T' j- j4 v

. G% j# F' [, V0 a { 不过实际利用上会有一定的限制。4 L. E& u& e! M. r/ c

! _- P5 D! I, |9 `' l! d

0 ?( m! t9 v. u+ M 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。" }& S h- w% l3 A0 L5 h

4 V# H; e) h% n2 X& ~

) ~# _% n" ?. A% D, Y" _' i  ' ?% y0 J) @5 S: S& N" p' n. D

N' H5 p3 U7 f! Q7 I* L. J$ g5 d

* R& P: b! ]5 R' ]2 I 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ! G" v4 a" z1 w1 n) x0 Q

" M9 B/ y0 \; w9 u

9 n1 U) ?6 ]0 A, B) k0 T D* ] 省略...$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]);省略... . f& w- I! s Z# B

0 F H6 l1 A. l

3 N# h' o: e2 x6 n7 _ 因此要利用成功就需要条件竞争了。) m. Z' B/ v* g" k+ V+ z

* Q# R- Q$ O0 }$ i7 K$ \

% I: z& _8 V$ ^) K9 @ 补丁分析. {. W( X$ \/ q1 C6 O; y

! B, P+ S8 F4 q" I+ h5 P; x9 y

9 k+ d- B! E' ~) T5 C1 o4 ?   9 P" ^6 L+ W# F0 ^0 l. H- B7 J

2 U/ r% r$ }. F. l T

( w' [8 E) U; ] 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:- H! |: D' m% \

, _' M/ U" i: x

9 M* D8 ^, C: b' r function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}% v' V; _6 | }7 O% x8 z" p4 }

* R1 s9 q( h) |0 x- A1 |3 t

# X% z( k' h9 n6 W. i  7 d; J( ]! i+ |

p8 \. }9 u, T$ R. m

; h+ ~. a* ]: A Y" }5 N1 K' i 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。/ C/ D: ^4 O: Q- O$ ~

; G6 t$ {* D/ s" N0 L4 M

f) B/ f; }$ e 在is_allow()中增加对$this->savename的二次检查。 & ]8 C6 c$ y$ e; P

9 F3 L% ?5 L l* g. e

0 C% z$ b7 s$ n9 [5 d) K 最后 8 R. D3 V1 I$ U3 X, ?+ S+ u# a* R

9 C3 D; ^7 _) @* i \

% w+ L I9 {1 s4 a 嘛,祝各位大师傅中秋快乐!% @* X8 i" Q9 W. X- `3 a

! k% J% l7 f# `% R- d9 p

3 @1 z) m) x& N* R  1 A6 ?+ f5 m Y& T! k

. R' O% U' |, ~
回复

使用道具 举报

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

本版积分规则

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