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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
, l/ b# s8 |& L# l- B) Q2 a

% N. Z9 i. x2 b t" C3 z K

9 i; {; ]+ b7 d& j3 i

* Y) P8 y6 Y$ E7 @5 | 前言, O0 E* @3 o" X' k1 W

/ m# `/ e2 ^0 y6 M" I: I1 D% d, h

% t$ |9 n2 ~$ Z/ E3 M 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 . d$ Z$ d) }4 m/ X

8 ? B' E4 V. j( x

( j: M% C W3 ?4 s   0 v3 ]$ w) e( P9 @

7 i- R5 S7 g$ a7 {- V3 h0 D

$ ^* i, n! |/ N' k1 v1 p 漏洞分析) ^# f c$ r! D- l- k# }7 g' i

. E: t, G F' p H. g" H

c8 u3 k5 b6 i, S+ q0 D 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: * Y% S: e- t& | C

5 T" S0 P9 P& y

2 e& a: i* t0 ]# ^& B" N5 M  ; c8 o# V- D p* P1 l1 t

0 S7 H' q( N/ J7 s

) Y2 J5 F, Q" G' D 对应着avatar.inc.php代码如下:5 ?( B$ n' q& A, D8 G

; T! A% h% \' s& W

1 ~- B( h# _: T$ B. n* _) | <?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) { 2 T* j0 {7 y, `

3 u5 I) U( F/ O7 u

- [1 {: \- {( ]& `! D     case 'upload':3 t7 J) R; }" B0 `0 ?7 w! z

, X4 V- w9 `- a) q0 i

; N; S+ I F: c1 T         if(!$_FILES['file']['size']) {( c' _7 [3 h9 C# p

, l# b2 h4 [2 p* U- e

- o" g0 [& ~& V" e' w2 R2 U             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);9 P) O+ p0 t2 C- ^1 L$ y

5 d5 p! w e0 q9 ?, L; p

* v/ J( a$ A0 O             exit('{"error":1,"message":"Error FILE"}');, U. G( P- z6 j+ D0 N, W6 n# R& \/ G

6 q7 a# h! @9 o5 U

, U4 z, m6 _' i7 A5 P         }2 `) T3 Z3 ~( ?$ S# e; v

" l9 N! M% [/ ~: u

2 g3 T* @: S5 L L' V: s         require DT_ROOT.'/include/upload.class.php'; , e3 T& ^, a' c: U

7 j# {8 W9 }8 s3 G: u, F

5 ?* L. Y- _- ?  & o; r! P+ Q. H; |4 T7 h

8 O m Q/ }5 L( W. k2 W

% [, F9 n+ s8 @5 Z; l* \( C% e         $ext = file_ext($_FILES['file']['name']);2 F% s# I6 [( B/ f4 C! M- `3 c

8 X; p* G* Z0 U' H

9 K0 u6 ~, r6 P/ b7 E# h         $name = 'avatar'.$_userid.'.'.$ext;- Q# m. c) v+ Z# t1 R8 _+ n( S7 r

) b" j P m# Y2 l9 l

+ X! [9 U+ P" h# R1 X         $file = DT_ROOT.'/file/temp/'.$name; 7 v) V/ a( o5 @

. b& |1 [" f/ y* q+ F3 `" h" R

' N: W3 Z( P) Y. ~1 m  / V8 `' K! u+ d- @* Z8 w

. |3 P, T' z- q! q. Z) F

# h% x/ V) M7 ^+ w1 ?         if(is_file($file)) file_del($file); ; j6 z) w+ ?+ _ g+ Y1 L

4 t# S# d- Q, U& I; `8 t

" m- G- r* ~( b, M7 n         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');/ w/ x& w2 A6 G6 H2 [3 ^

0 i6 q& G& e. t z8 s! O

+ `0 t8 I9 |: [   ) }, {) J# J% X

+ I' q+ u ^5 ~# |% C9 s0 b

* D9 T% n3 e- Z1 K0 i         $upload->adduserid = false; 1 h+ d- m+ E5 G( X! G- B1 K

2 d; t( g8 j# n$ E9 u% |& a

% J4 u$ y' l8 p$ ?  " T, i9 ~: O+ ?& _0 [

K+ {! s, O2 K; u1 G( ^( \

/ i* o) F- f7 }( [         if($upload->save()) {; K8 j$ o3 E1 m9 y& ` J

6 C9 B# _7 c+ t1 ?; ?6 [

/ R K& I$ E/ i- U4 z+ K             ... ) d" G2 R6 J# T/ i# A

, ^3 e9 x0 w/ n1 Y& i

$ U: L" X& D: `2 }/ j9 R         } else { : s, A! H6 F% S/ v1 W7 L$ ]: v: z

8 b) C) n: V( _4 |2 O6 d3 @( w

3 [$ |9 t8 N. ]+ Q! z* V             ... / C& ?0 O6 G, W$ O- ?

P( @: v: C# ]$ h! E, @7 E5 L

# c9 m5 L; z" A0 v& K/ ]         } # l9 C/ h" O, }6 l0 l' r; m; m

, N3 O: z |* `7 Q! u% ~* h' B) m% F

g: V' }: J) K6 T( X3 ?7 I& i     break;( k8 [$ L, \& u2 F+ N2 A" [' R

( h! t' n+ t- _& _% U/ V

0 X+ G3 x1 J- X! `+ ]2 ~" e6 E: c 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。& |5 L3 B9 e5 z1 g0 }

0 Y0 m, v6 P' f

l- q" \1 `# D( y, S+ s/ D upload对象构造函数如下,include/upload.class.php:25:' J2 l% m) D# H; }& X

Q( f7 q/ `1 ?% s+ Q F

5 V" }! }; _5 k5 a7 ? <?phpclass upload { 7 `6 v. X. O9 s5 E/ v5 `% A$ k4 X

- a' K5 x$ X. B0 v3 B

- U# Y( o8 J" D6 b) `1 {     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 4 t: N, |) ~. a* \( _# F

/ e! A j0 L) i: [2 @ {

$ P2 e' m% h9 \         global $DT, $_userid;, z7 g: c; I$ p3 t

, R1 v7 Q& x, _6 C" r% y% ~

. k5 U7 P+ L0 v$ L' D% Z         foreach($_file as $file) { + D7 E# }; L; }+ N# }3 d9 t

2 a+ ~* R8 Z5 f1 m+ C; G. W1 i! M

6 h( z7 E: c/ ~* s             $this->file = $file['tmp_name']; 8 L, R% P2 k# n2 M" N

/ G" u8 Q L- H( y

' B! M) d& }! x" V5 [7 R/ y             $this->file_name = $file['name']; ) r) @; a8 c; O; v- K9 `, }

: I' G& s$ w4 o0 p+ v1 W

2 k0 W! o9 H5 z4 K. Q+ i             $this->file_size = $file['size'];; `; W9 k1 y; {0 d Y; |) G

6 v9 X+ l9 _) a, [" @

' ~4 l& X, X6 c- N0 B* P4 H             $this->file_type = $file['type']; ; s0 `: V* Q; |5 e, E

9 x* B4 l3 p8 c% ?

8 K9 q9 c) \$ |6 X             $this->file_error = $file['error']; , O! l7 J8 O2 X: V; |

" W. ]+ e9 y. u3 }# e: G" I

1 A% q0 N' G2 K5 ^5 z9 h  $ }5 m- N' F: n: A; z2 V

( @: Y4 B! Z' N6 {0 M$ F( K, v

- ?, y4 `' ]% g, `, u' l6 a! d         } * t8 l6 |: ]$ y1 s9 a' e c

5 W) P# s+ |5 v5 i* ?3 q' b

/ t2 G$ v1 x3 Q& W' S/ M2 _         $this->userid = $_userid; X& t* M; J+ [- v+ J( m; R

1 k) ?4 Y- l; g2 ~) A

' [5 S# F, p7 s9 E! W" X         $this->ext = file_ext($this->file_name); ! i0 O$ z+ g! [( Q

, E7 }8 f5 Z* {( f% L* v( n

( C1 C* z1 U( d. e' T         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; # d0 x7 @' J" s' e' W& p" o

" Q1 a5 F, |8 G, M

* G& r7 k! U" ?- j0 k, J         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;' ?7 v0 p" e" Y; c) N7 u% {

( |9 U1 D: e8 _( k- w* o9 @: E# \

1 Z% i, c/ h4 y9 N         $this->savepath = $savepath; 5 L. @2 [% v$ G2 O/ @

/ D3 G6 d' Y p: o

) P- ~" i8 r, w# p4 q         $this->savename = $savename; * D5 y C& \2 ?4 T

; b- I% \! s7 U6 W# l

. \% j* u: V/ M6 k J     }}" d6 u% T7 g* p4 A! I# _

, R8 ~; V$ b4 F2 ?! S

8 M; b$ d# V* u 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 + B4 L9 T. ~- @5 s0 R2 P4 U0 W

; q* J o% Q' G) O. P. u- w7 N

' ]3 N4 G- K9 x2 U" M0 J5 o 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 " w( z1 n) B" ^- |4 L

3 G% h# T( M4 P! Y; j, [

* d8 |; W& u& i6 ^ $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- W! a9 X* R$ c- g: B2 m% k

" E' t3 E& u" C) C! Q

) X! D. q1 V( \" b 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 6 {1 U$ W7 z3 x$ e8 H

' c6 _" A8 A9 C2 q

& O4 w. R8 o0 F4 }  ! f. F' K3 Y0 i4 T; G

: i4 D7 f7 J; X3 y# X; ?

8 d, k9 C" N% R. t7 I7 N 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: . ^( j7 P8 s& r+ u# V; g- y; I

. j% x$ R9 ^ g4 @3 O/ M

, g0 O+ B) n( t4 K0 r7 e5 z) d <?phpclass upload {6 e4 ~4 E+ i; {! ]: a

8 r* L4 s% `2 P9 C# z0 n6 _

7 I: F0 L& Z5 j5 C1 y     function save() { 1 j$ B' y! I* t9 x

$ r) s0 B6 }: I1 J& `; t

# c, \. ^0 Y2 Q! r. D         include load('include.lang');/ I3 [( e' F9 {* ?- o

0 b6 C" f3 H- G5 N3 n/ j; M+ n

2 s& Y! J4 ^- c         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); . ?3 p( f' @+ I, F8 q) O9 W% t: r

+ e0 ~0 E% r& x

E$ }, p8 s+ l5 @: `+ o5 ?  # r0 I5 a" ?8 C z0 G

5 f6 Z" o: V n* S

2 w6 q& R0 D- Y }+ }         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); - b5 x( Z' f- j6 e* a! }* ^

" N6 X/ ~7 b- p1 M% M1 c$ i* q

- B6 h6 e# v# Z9 U& ]( I   5 b* v3 |& m( ^& J- ~1 O, a) j

( i; g" `6 l# q; N

9 s% U0 `9 O( J0 q. _" E* N. I: a- Q         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); , X( x& h( [! ?) b1 `; Z5 Z

9 T5 E7 ^( a2 g

' I5 W' d) Q( W# ~8 n3 F" |   8 h) [8 x) s, @! t o

' u- m7 m$ e9 Y, r; T: c2 V6 M

6 M9 H$ g2 A ^: D( r& `" j j! v         $this->set_savepath($this->savepath);% s$ O+ _$ t g M; ^# I

+ V q' X0 `7 ^' b1 ?& Z( P6 H

: `( B; j# D. C4 n# v$ ^         $this->set_savename($this->savename); . L% D" c( o" t0 h$ L* G

$ u- G$ B6 l# Q8 N: D

9 x+ U' n. |7 C0 }0 T$ R' B, K  ( Z0 W( {6 Q4 O& U \- j5 q, l; r

W# G* R, x, @5 s( N0 w5 I3 b

+ {+ T, Z3 [4 a2 G         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); & @: X$ }* @6 t. X1 Z% m* j

, D3 t+ N5 B# S% t0 ^" T; g, y- c3 @

2 e4 r1 H4 m* Y, c' N2 X, { Y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);, k; E- w4 u3 h4 ]* b: R

0 V7 v, K, Q& g' k( i* g

1 L! U" ?' {/ k( g) S5 I1 \+ @         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); & }( v; a$ d0 t* [% F2 A

' Z' ~" m! \' n5 F8 V9 }& z

/ r! Y6 q: @$ f3 ]0 h4 j   9 m, j0 {4 y% @( \$ d

# A4 j* `" r$ ]

8 m+ [9 G' s) f/ Z0 G! P         $this->image = $this->is_image(); - t2 o4 q- e0 l1 H( y6 S

1 e, \/ i# B) O3 g% P

4 L" S$ L' f+ g' ^9 l         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);0 t" m5 M! P. `9 ?2 s

3 q$ _3 f& w$ b( j

- \% y# y: C9 c. Z( N! D) f; [         return true;! w+ q6 c% Z! q+ Z+ n2 y( N

; O$ h$ k, n# w4 i/ l, v3 b3 U

3 r2 [) d; U# f     }}+ N+ U* b* W" ^# K7 Y g

9 s6 A1 n' J8 S% ^

# Y, ^1 k9 M F' i; O) ` 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 7 ^+ S1 B* S* \& P5 O5 n% U# u

) F) ^4 @9 z+ b6 I( w( U

9 {( ~8 M) T, n1 D3 [ k. g <?php% }# i b! [; m& Q$ s" w

$ {7 L" T, G2 ^# u- i m5 x

6 Y6 C9 A& L* t: c& x. E     function is_allow() {0 c7 t) `4 _9 O9 M& O: D+ _

7 n5 c: b. s" S N8 Q

& h$ j. t; v; R# p( S5 k9 u         if(!$this->fileformat) return false;1 \" l; \- a, i, D. g& s. T

8 o( i, V) E( g' u+ G+ d" C8 O

5 E4 g: w+ |# w, p         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 8 X4 Z3 J% c2 \0 C

- _8 S& V$ p( `! S, o8 b% d ~! {

5 w6 C# q3 \ ?/ ]7 N$ n         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;3 j( y7 I' o4 A% f- j

. G. p) q+ _+ w7 z

7 z( Q1 h. f) J. k& i W. D         return true; . U6 {0 K2 q, t( j9 d; j

5 n6 v! ]" F f3 v* d

5 ^5 t- A+ l# U$ [) m9 e& J3 s     }8 Q# K" }, s' N! z0 P

, S0 h% ~ z5 j0 p8 m& x% X0 A; l* k; w

- J, ]3 V: e) ?9 k0 k8 u 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。* C6 e6 h* S I' F" u

q: h0 @! d1 z/ j. C

8 H# p6 I! D: n3 r$ g" u 接着会进行真正的保存。通过$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文件。0 J2 [* f: ~7 t, J) m6 E

" d1 f3 b# F/ k* X8 ]/ w0 D3 q

+ Y+ ]3 x$ K8 n. b 漏洞利用$ h; n( q; T3 f

0 v6 v2 l8 i9 J) D7 i6 f

/ T& Y/ T! G/ T/ E/ D, x( j, t 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 2 K* f3 d( m* f# u

. W6 e9 d. \4 }+ e, D% y! |

, ^6 z" e7 S, q4 m f/ O9 Z   " r8 h! _. J6 R/ x& ]0 I

; Q& j% z! s6 {

, p+ m+ K8 f0 h- \ 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid " ? V' b' M w' @

, ?2 C X5 H! ]) V

7 H) ~6 D. k6 w 不过实际利用上会有一定的限制。. [0 q$ Q8 V- m- Y# i6 J3 \- ~

/ c' |# |, }& Q+ R

% ]% c `9 r$ E1 p4 G; d 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。$ Q+ I5 ~$ \# J

' Z5 `9 ^; i* C$ x

8 z- f7 {/ M* i; N& a8 j2 ?% B  5 h) h2 T; X" |; }8 }

* j; t1 o7 K7 A, M* V

( b# O1 m1 I+ F' }& F. E) I 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! {5 V' c2 z. k- y. r+ P

3 w. t! D0 S2 O$ Y8 l

, r: R6 p: N- y3 O$ Y3 P 省略...$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]);省略... 7 p( l j( x: R+ C- p% R

, ~( q# ?. h" {4 {$ x

0 d+ n9 m$ s& l 因此要利用成功就需要条件竞争了。" J- q; V' {, y

; D' H. P/ f; p

9 }1 \2 i* [% d8 |' g 补丁分析 # a' M9 ]1 W& u5 }* u1 K2 V

' V9 x8 L& f/ D

' D6 k6 p4 n) ^' u( j   5 q+ c5 x3 c' Z

! u' A* V4 h8 p" q0 N" G, q

6 _' r) d* R! Q+ @5 @$ F% V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: * ~ T! v- y0 e4 v5 j

! v1 o7 t* {$ \* w. {% h) [ Z8 p

8 a8 d7 V+ I( L3 Z- p8 C7 ], K6 r function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}% _" h9 u( z" Y( M% y

- L* `; b' d3 ~6 ]5 G K

$ n4 a( X) ~ o1 R$ c: [/ r   6 w, ?5 f3 |: ?

P) F3 p* R1 z5 z9 a

! Q) O' @1 e( Y. V: w" e; l 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。( g0 s% Y8 K% |; I' S

! i, a* w: c& k

p% O. F% c4 N8 Z. M 在is_allow()中增加对$this->savename的二次检查。 ( E" A' a9 I$ }7 [# u! T

# {0 }+ W# I6 q" H

7 @ f- T1 S# W1 C3 ~ 最后 2 ~& _& L* f1 j0 Z

4 V1 u7 V" Z+ T

' c# |: R/ ~0 D# K7 @ 嘛,祝各位大师傅中秋快乐!: r: H# R' p' |; u

1 o" E( r R3 V$ `! p

$ R9 M3 B6 \. \6 S$ g; b% ~8 `   * F- F1 C( A$ u( T0 `

3 p: Z2 z3 g. p3 k2 L' f
回复

使用道具 举报

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

本版积分规则

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