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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
) q$ S9 S) M! j! ~, L4 a

, U, R: e1 s0 Q9 X5 q2 \3 T5 T; T

+ A6 N0 I. I9 j

+ o6 O A% Z8 t 前言9 x+ U+ D- E& \4 l3 ?$ w

/ q5 M. L& e( j/ p7 m- \

. b$ u/ w3 E# ` 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ) k6 f( Q1 |- z* I# a

" \5 `/ f( S h1 _: t" E3 u8 g! M# u

$ F" s; T; {! }* C/ i# i. s( s! M   $ _$ ~3 _, g! V3 I1 T7 o. B

- D `# V& ^0 M) _# o( Q

$ i1 M8 E9 c. o1 _. }1 E X 漏洞分析5 r$ Z. e" g5 x, i, B6 M

8 w1 d- S5 g" t) E0 G

) d q0 t* \: m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: . s/ h5 v; u# U% T

9 H8 r9 W$ O' A# P" P3 b

- C s) O0 C. n# Y* q  ; v; L: p1 y, r% x5 `# L1 U

( z4 F R1 V& P& G9 E

1 D' [9 u5 Y4 I; N$ [3 \6 S2 e; Z/ I4 k 对应着avatar.inc.php代码如下: " `6 i+ ^# x9 H. O* K

0 D2 E5 t' s2 Q$ B6 g% T9 D# o

- q% k, G8 Y1 @" 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) { 3 g- [9 M! G4 `: W, R5 f: r

: Z: K/ A7 q9 I+ H

6 L, a0 j9 L/ K0 ~+ ?; ?     case 'upload':- W4 ?: j; g, w. D

1 X9 M s! @" t3 X( ]

: m* u) x+ M8 ?! w d) j/ K         if(!$_FILES['file']['size']) { & w$ u3 a; v: u8 V, Z

+ N: p, J' {* b8 K [$ H7 V

! V4 I% O" B) I1 t             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); ( I) `2 H u" _# r0 ^

9 v) |# I( z9 A, S6 o

) w1 G% Y p/ |4 Q9 u. i- @             exit('{"error":1,"message":"Error FILE"}'); & i. [8 C$ e4 i& x

) q3 @3 A$ G' w: l! p1 G, P

, q' M: N: R- }, S0 P" X4 z         }; ]9 i8 {: A M1 }9 s' q

: O* B, P& H8 ]! \1 D, p* p9 Q' w

8 I. F' ]0 r0 m- f. P         require DT_ROOT.'/include/upload.class.php';9 R( p3 v; l& C) s' Q; @- F$ W5 k

6 U7 D; ]/ v, y5 z: j

3 W4 `2 j4 z" O: x5 x6 A  8 g. W1 D% J( {' s2 ^; K

, d! j4 s; X4 [0 e

3 [: G" H; O8 v3 ^8 d% B6 w( [3 |         $ext = file_ext($_FILES['file']['name']); ! U2 }7 L' @( q6 q

5 f1 Z* Q! Z2 u; P) D

+ O8 |% t. \/ J. M% E E: e         $name = 'avatar'.$_userid.'.'.$ext; ; X6 A( t6 }# o* f: T

$ Q' O8 ~) P2 p1 G

% z% g1 Z# M2 z8 H& s7 R* w3 ?0 Y* M         $file = DT_ROOT.'/file/temp/'.$name;5 Z" D9 p% C! a

4 F/ A: g. X+ O/ ]( V5 r

; A0 @# H ^7 y3 Z! Y, u   : M7 ~+ B/ d) Y5 i3 L

7 T; C7 \; I8 i7 o0 f7 n! n8 a8 U* a

5 ^9 ?: c6 O' a6 Z( f: E         if(is_file($file)) file_del($file);0 q/ Q+ X" R/ s5 @' i

9 V6 U4 @3 q9 R" z% w1 Z- _' W

6 G3 I+ j9 s/ F- F. V1 @" o/ z! \         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');0 F. C" x( ~% G1 ^$ N7 ?

7 l2 ?. q4 j" {5 C

& x* t2 |. E- j. c4 K   8 |6 p, I8 \& u O7 Z% ]

& M$ i7 n0 G( O+ e! Z4 X

: n. {5 L x( l1 c" d2 x1 ]         $upload->adduserid = false;4 R1 j' j& V+ L5 L1 `) V; @

+ G+ w& B4 t; K' }! o

+ r2 }: C3 Z6 x3 m: K l6 J- ?   * b" k( [7 |, M" o

4 I- O4 [2 z f2 f; d- y

; {8 I' q- a4 L/ Q# _2 B2 ?         if($upload->save()) { . A+ F1 v5 ~8 T7 a

7 p- X& l/ @$ o0 }

6 h) A$ ?) {$ t4 k' t: Q5 n             ...0 K* |# T. J1 K) f& J

q1 y( A0 T+ r. Q

3 c8 ?3 b" x5 y- H$ H) T         } else {6 U; S: n. y9 I7 v* v

6 } u5 F: @% E0 T3 j8 g

4 W0 O) l0 o8 @" b: G             ... 0 o! n( X- C7 Y" J; I3 R. K0 m* K8 p! t

C9 ]( `# I! H7 j( P

' w/ j8 d' R) t5 L! A' T: i         } / L9 _7 f M( a# B" S

% Q5 T7 Q% r0 G) O# ?

7 B* N; F4 {8 F0 r5 d     break; ' h7 @9 {! S8 Z

5 o$ O' a( s% }; F3 |8 d. u

& c; ?: J( }8 ]8 l X 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ( }$ t' }* `8 P b& d

% A# W3 O2 n. n( k

% {. Q7 n& Z& d( _7 W& F upload对象构造函数如下,include/upload.class.php:25:* F3 X2 W' Y( I

7 N$ Z: N/ t2 \9 S

( Y3 [' v1 w, n7 i) @0 M# e+ c3 e <?phpclass upload {3 B9 `% m2 P- k) a; s1 ~& H

4 Y# v* M2 W$ h# p7 O, J

4 D9 ]1 w0 }2 W$ g     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ; i5 f6 G' ~- B- ~1 D+ K+ i

1 c7 n8 X$ e' _

% I. y+ |/ l% P         global $DT, $_userid;, |. @% I5 v" ?, t+ c

* Y6 B7 k. b% J3 U2 D4 d, [. k1 \

" @1 m$ x0 d# [; }         foreach($_file as $file) { t4 P6 X' F& N+ D

{- s' [. ]# O: F/ V" a

- F) t! B$ o8 x/ b8 e6 k% W7 M: t% H             $this->file = $file['tmp_name'];0 l3 p* w* \" x, @

% ^: \. V3 d0 Q! g6 k0 ]( I

; C$ i1 M3 a Z" k3 E9 m             $this->file_name = $file['name']; 7 b! v# C" r g1 [

! H- G) c! c" r

: `" r& B j" H. [$ I; S2 a& B2 j- V             $this->file_size = $file['size']; D Q# a1 F6 G7 e! q# v! N3 R

& ^# ~& V- ]& {% d6 B

2 F5 S/ |* j8 A x+ v/ Q             $this->file_type = $file['type']; ; d* V4 O+ r9 n2 ^* w1 L1 O9 |- {

8 p# _" e- P1 N d7 F n6 Q, b# V a3 x

$ O* |5 _/ ?5 k* F- i1 n             $this->file_error = $file['error'];+ y7 \1 q0 ?% s9 C

8 m! N3 J. B u; \! A3 T" y* X5 K

! |9 ^4 O+ I& @7 s, L0 h& s+ R4 e  & u' ^' x, O' g! _* {

& l. }' B8 W. z% g: R

) t- C0 C2 Q' i2 ~( y         } " w$ ~) g6 Y- W2 E% G

7 [, W6 k0 u! _* ` y4 P/ B( ~

0 m8 T. O) ~) R0 C V( A: H7 |+ E         $this->userid = $_userid; 6 Z2 |: ~8 x, W6 n; W

/ ^8 e X3 c9 T9 e# n( G( J8 t, B

% J! m1 I" J1 `$ X" Z         $this->ext = file_ext($this->file_name);% y3 M3 C5 c+ c. z C; L

1 l! f: R: U! Q5 I' E3 Z

, h, c5 O1 ~ \         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; # y% J' H- y! m* B

2 Q3 t5 u" M! L& `' d; e7 _

5 R& @' z5 D, F" o; s1 k         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;, P- u' `( E& l4 J: ~: L+ T/ N

/ i5 Y/ G7 n \# ?& H* s+ W3 t6 x4 o9 x

/ W9 B- O! ]5 y" @7 p: p         $this->savepath = $savepath; 0 C+ t }$ n7 \8 z( j; w

. h$ B' ~& C6 p" l& e# @; ~& [

2 P6 X% D2 g1 C         $this->savename = $savename;8 L; l+ C5 v U7 X2 w% W- u

; o6 [, Q3 [7 y4 `0 S' m

& X; c# S. w# g+ @6 b     }} ) Z# \; @6 ]# r1 j

$ Y9 ]/ l: W$ O( x

; S- t- l7 s1 a m 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。3 n: S! t* z* F+ F3 E O& Z" n

: x! c0 k$ d+ n/ {( Z

7 A5 F2 T. W8 o, I, j" ]2 O. W3 H 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 & E4 l Z! K S9 t# T2 H7 P

Q* j6 Y; y& [" {' t1 U& q) l

1 V) ]1 c9 U+ }' n' J6 ?% T9 | $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.php6 e& f1 b) p; P; }( }$ I

. y0 l4 ~. L4 B6 h' H. \0 f

M. b9 c+ R4 G( h5 H 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 7 l$ |3 F% t# v& o: c5 W" `

' q. ~2 E8 p8 K; t

; g9 G1 m$ B5 k6 B# [   0 Q! v3 j/ Q: U) X1 L

+ N8 M6 z% r& A

) i- Y) k2 M, P6 ] 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:0 N5 |* l9 ?% V9 @/ V$ w- E! |

s& w8 h8 k* Q, O+ J, t5 G

. @ q% \ ?( u# r <?phpclass upload { 7 @, T, C6 F+ e

4 I* j/ c% z: l

5 p& L, a9 K! q- b6 s$ l" z     function save() {$ V; K8 X6 F3 z c

4 n5 ~) R4 }! ]: ^: s

2 I3 ?; a- n( ~% \, U* n9 R         include load('include.lang');: b( B& j6 n- \8 \, ^2 J

$ F) r: q. K2 W j

; `) t" s/ ]% W k% w         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 1 s8 q* G( E* H0 K# {8 w% y

# [) H3 F6 l" C9 l# N9 k L/ c

$ o# S1 ^. F0 v' r( s; U  3 h: B) g6 ^$ K+ u

9 n6 `3 K0 \( s+ ^ t5 _& H

7 ~' W# t1 x' H- |% k( l# u         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); ( d. I; Q* Q' Y* z

' i, f: k8 a1 h/ f5 m2 t# J

9 E9 n/ P* s6 ~6 O1 R  - \/ T; M; e3 f, G! l5 A

" B1 G$ ]" @2 a- i Q4 J

, {; e* i" b+ V         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ; _6 J% D7 z+ R* Y& [$ K2 M& F

3 m5 h: Y' B! X2 h- \) S

* [* v: n! L% I   * c: E' k$ h- y( f2 w/ B- _

9 d0 S2 O' a7 c1 P" @

8 S& k. j: V8 y( k) y         $this->set_savepath($this->savepath);7 f1 O4 z3 T; u; n$ k1 [

7 {5 [/ w, `- ?2 [6 T

+ c7 |8 w$ W3 h3 u6 {$ N         $this->set_savename($this->savename); * ^3 L- z( l, J4 S7 z+ e' L

! R7 o P: h s3 m, w/ N7 k1 m

- Q/ A" m7 d* f& U+ e# k2 w2 ^   4 k, |( k+ q/ X4 R& i" \( W

7 U4 z$ ]1 R7 q$ n! p* N

2 T6 b6 ]- V6 q+ [/ A+ Y         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); # ^+ g2 @7 {; `& e

+ A9 X/ S% }5 Y

4 \$ }8 p; W* O' e* j         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ( ?$ z8 X: ^7 V3 r" N# J

, w9 V4 V' d3 V w4 r' U. w

3 t1 M0 D2 E K+ e) q3 T# J- f         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); / S0 x6 e( }5 o: {6 j" }

1 J% D* R" h) a/ d1 z; D

6 C% {8 d4 y- k4 k/ A) |, F5 g2 w   ! ~* b$ w2 h5 d" [2 q1 `+ T

6 ^8 Q/ t& ?( M- ~6 R. j

# B d% g4 O d) V; B5 d         $this->image = $this->is_image(); " e% v. Z+ n2 N* r9 t

/ s; p% V) {/ F. Z! i. F# _( v

: Z% a& J `/ T4 U) _# @) }         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); , g- m* i8 r# j

% r. v6 a' o% l9 K0 H9 M- u g: {

4 p I" i& l2 X' i" O) H" `. _" M         return true; 4 H1 G4 x; r6 f8 P; e

1 x7 r$ s: w, g5 o

, T( A% h1 E7 }* t/ o     }} 5 v7 p X2 M4 A7 |& t0 J

: n8 k) z/ {6 r+ i

/ } s* l$ M% q 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:5 L1 g* H5 a. T @6 w

- f: O: x' }; P4 r% G: D2 ~

- J& Z6 I3 h: l# Z' R9 h <?php $ b8 m5 U# P/ v/ [ g4 J t

4 r0 a2 l8 q' ^' {5 J0 _7 ~

- D5 [) z U5 Y/ u+ l) S     function is_allow() {( \' Q3 w- z; T( V

, p2 n% p+ g( C2 n" |

6 H1 c8 |2 c0 A$ B3 `% n. B* r         if(!$this->fileformat) return false; 9 n. s+ i7 S. K; a

8 u/ H B/ L. B8 C, ^- D+ ~$ n: A

6 m1 V1 [8 z# P, K+ _$ X# I) B5 ^         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;' J* T0 M" Q6 F* N2 }

2 l# e; I8 a' J7 q3 u; i2 u4 Z

4 y, \$ g/ ~) ]. f9 p/ g+ c( h         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 J1 ^3 \7 v/ a

* r6 `$ K% l. b) o6 F, j- D

! i9 G O2 P! y1 O J0 W: a         return true; , N6 ~' V1 k) h5 P! f

' G3 u F& _! Y5 Y9 T9 O! M

. u4 N+ t. x! Y     } 8 a1 x7 C2 A' r3 r% q; L! t

1 |7 P) C5 F# z% q6 @; i6 K

0 u+ i7 u. `) o/ ~ \0 q* v3 X 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。. k w2 s! N$ d! H5 S( z+ `7 D

' ~. E$ c1 q2 v8 Q. ?4 v

2 t( _' {; R2 y( E) X0 K 接着会进行真正的保存。通过$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文件。 q( M6 r T I. L

3 J; U8 x$ |6 n, Q8 m1 i, b! |

# W) J" `( |3 _' T* ] 漏洞利用4 T3 b( T+ l. o+ U- }+ V6 R3 {8 t

# O" d8 ]. ~% r* z! W

# H& Q x. i) u+ Y3 r# o 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。4 P# l) O. J2 R T

' F( u* }: F" \

6 J. I( ]( }0 V7 {8 ?   $ S7 e# H) I. Y5 u; ]( b

; H3 o# {9 i3 f' t

, ~. n1 b- X H4 [: e u6 p 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid3 y7 i$ o# p& Y5 h8 _

" h+ r w7 Z' j v' b& J' O

1 ^" t% }' K- a 不过实际利用上会有一定的限制。& W& L7 C D2 @* l! j6 N& D) n

5 ^# J6 l) q s" J! v4 F! h

0 b( M Q3 ~9 X) l. p' ~! `3 Z! i 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 % |: Y$ C4 w9 B8 D6 S

# v; b8 @' u0 D( N* Z

' k+ N" M( B5 p, C0 ~   1 q$ t8 Y$ w; m& m" \

3 i4 h2 M. C; [. x% y! R; G+ |

* e3 q: [( [) v5 K6 F 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:- Q, Z$ B1 F% s$ s) D0 E

" f4 u2 T7 H6 a% W v/ L" P

C6 n$ n. K8 }6 V 省略...$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]);省略...( z9 g% C/ R% u" X" x2 X

8 Q0 i0 g- @0 l$ @ T8 }- r7 Z

3 q$ W ?# n: A' Z 因此要利用成功就需要条件竞争了。 # J& y+ m9 ~- o- v. O; T( b

/ ^8 z2 E: w( k9 e

( t6 H% h8 [) O9 ~+ C 补丁分析 7 P" J" a" S; q3 B

# ] H( ~8 }; Z

5 i; A; k! e& ~: V' W0 Y2 P# i   ) W3 U0 w) b: |, u1 J8 B

2 l6 m9 V$ F9 y- r) b/ M9 f& W

, P* M: j, @& j& q8 r! \ 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: + H' M9 x Y0 U4 U

4 w9 f4 q0 @7 i2 @! c" G

( s3 G# V6 @; R6 o3 t8 b C! i function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ! V6 |" b+ M' P7 D

) [ p0 R& T# |# X; @, K3 ^

. ]# U P% J+ |3 T5 U' K; c6 @  3 _% G, m/ ]# t+ [4 x

3 q) r4 k! l5 e

% u. ^0 W3 c3 l; B: g 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 - s5 u6 o8 N% z0 B" `6 X9 _

! f7 k- _6 F- N0 A

[. u9 B5 \& }5 e X 在is_allow()中增加对$this->savename的二次检查。2 s2 G9 b$ o- `+ E4 ]% ^

( _2 e/ T# E9 U4 k4 T3 ~- n

1 t0 c: R% S/ H7 H4 R 最后 1 c) L. @, _- H1 k9 Z

. g9 } ?* c: W9 E, H' _

( o$ B4 h# P g* x* s 嘛,祝各位大师傅中秋快乐! ( {9 z9 i' N: y; _. A

: j3 q2 q" M$ S4 [, F

$ Z6 ^( W& T/ J; m" _! c+ K  5 [; O' t7 U% l$ \5 g) [; w

6 r' M) ~0 Q* l6 R D
回复

使用道具 举报

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

本版积分规则

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