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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
* d0 I% Q& F/ ]( l( V/ l2 l2 j8 l

7 o1 p: C0 {: ]2 l) ?: `( M; m

' H) g5 h8 @6 q4 B A

5 t' m" ~+ H* X! i { 前言 $ W% r; E& T2 R& R Z7 f7 V

) p% y8 n- g E0 E3 @: o) ^

1 d7 I4 I" i% L/ U 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。- k: }2 U, w1 b; K* F

) L; D2 z% y7 u8 h1 G/ ^# b

# Q! m+ R" \- k3 v; P9 ~5 `  & H: u# u. b: t9 W; ^# y

) D- L0 f2 r+ Z9 c' t/ \5 \

0 q' i4 X% ^ L) I 漏洞分析5 ~+ b) |, n- X/ c2 J/ I, j

: a' _( j: Z2 M9 ~

0 ~" J7 N1 u3 W- I* e; {/ o) { 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: * X! e. [0 D# M* v3 j

: t* t8 E- {3 |

x# T7 s% w2 c+ _  % F: c% C- Y* \3 |8 Q4 Y) E6 l

; J4 }0 {, K2 Y. v- N* q4 q% l7 q3 T

" L& s2 z$ ]! k0 W, d* c 对应着avatar.inc.php代码如下:" H9 y F) i' V2 D$ y+ Q9 r2 q

; r2 e: i/ w0 m8 e

' k+ X4 O: K* _. P& N* w4 W <?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) {; F7 r- G1 L1 ?, |

0 C5 y& a, w7 }6 v- M+ |( Q# \2 I# k: [

0 d) e: t- U( i! V. T1 R( S3 C U: L     case 'upload': " b3 ^" ~4 ~4 V+ |/ ]

& H& D i( S) D7 q% {

9 `* K) f$ r+ Z- [         if(!$_FILES['file']['size']) { f$ R& L* P# o/ ~

: ^8 v) X/ x" k0 z$ }5 H

% M1 v8 H% s5 D$ e! w+ V8 {* b, Z             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);$ U% O3 X$ f5 t* E

9 o6 q( \& [* D7 Y4 w8 ~

i2 z7 |* a4 ^+ a$ f2 D             exit('{"error":1,"message":"Error FILE"}'); , z8 u, S; `5 m T+ G$ F I

2 q5 r8 ]) S, u* o! `

+ A- a/ y; o/ f7 r+ O" A         }! f/ C7 @' a% x" S0 T4 W

2 n3 g# c$ A( A( F2 l

2 M- B2 D% [6 E" w4 S/ F         require DT_ROOT.'/include/upload.class.php'; 1 V/ v* ^4 g. T+ c+ j! ]3 S) l

9 @ Z# D. c, s, L

& C! L$ J, t4 I$ Z  + L; I4 W6 w% J) Z: n

+ L/ P( Q$ O% p4 e

5 z8 R' U& l0 G         $ext = file_ext($_FILES['file']['name']); ( i4 ]$ X. g( ~. d! T

* O1 O1 z1 E% h

8 l7 r* ^) O8 J/ r         $name = 'avatar'.$_userid.'.'.$ext; + s0 a9 L* J$ D4 f

7 D) a% v# y O1 d7 Q* Q

* d- s$ S* |0 w8 C, B) E7 y; M         $file = DT_ROOT.'/file/temp/'.$name; $ }: _# L, P6 _/ q

0 g/ h5 T. L- `' q+ K- U

2 i0 I( z1 l+ s   0 U# k" c( [6 ?

8 D$ Q3 f' V7 g

5 P3 d U+ m1 U0 M8 g1 R9 Q         if(is_file($file)) file_del($file);8 w& D0 V9 r4 o) V

6 u+ M1 [: t3 a; i, G

/ w# c$ Y- `3 s& R: {         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); # R7 e( {/ d3 _% f) o

& I d5 ~& B( O: @# H( I: q

1 s7 A7 f$ ~, {' ^   / \, i3 }0 I9 |& u7 d! q- S) j

1 ^' ^: b5 `# C, q! W! d) Z

: \: u5 U0 l2 ^         $upload->adduserid = false; $ i& A9 y! Q2 j0 ?+ N0 @9 i! ?8 ^

7 x, Y) J% V, R6 }; j: Z

0 b5 n! W9 m0 k0 S: Y" [) h   ! z) d9 k8 r. |6 U- u

" j- |6 p5 \, u! [% Q

8 m' ~" h- _7 |0 r/ d+ ]5 g         if($upload->save()) {! l# h1 g' j! Z4 _' `

Z* T- q6 M ?/ V8 D1 a8 A

' G1 t8 @7 \4 R2 y, q" _) T! ~ o& a; \             ... 9 [" u1 _- U& X( w. ]9 ?6 q

0 x0 [# N# k, {# j/ S5 |8 H

& T, W% b* S3 q/ i9 X         } else { 1 z! D2 c. u8 ` E7 A: Y- c8 e

; ?* ]( L4 L1 M8 s; ]

. R0 {) H/ n( e% Q4 s1 d7 C6 x7 g             ...+ E q+ r% G7 S S- d) ~+ c" L

. I6 j7 K2 m6 b5 F T9 }( W2 l2 m

7 X+ H- j+ B# H% g6 L' E         }, q& K' J" Q6 s, H3 j/ m. X6 ?

3 q( i; Z# _8 F o# u+ g" R

* H! _6 p7 _5 A% a3 A9 P     break; 8 e& ?! ^/ {" |) o; J5 p8 r

8 r+ Z$ D6 _# d5 O+ C" l. ]+ a( [+ F

9 h) s h+ R% T) J6 X2 g6 C 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。0 I+ G& e3 D: A, a

( n: X: s: R% o9 V1 Z* B0 g

: q9 p4 M d7 ^+ f) ] upload对象构造函数如下,include/upload.class.php:25: / w4 p4 q$ t8 B* }$ M c% P4 u, Z

% x$ s7 w0 p- ^6 P2 k

' M `7 t4 T/ m5 h( u1 X <?phpclass upload {4 V1 h2 {: a5 r- m& F6 A7 F! I

4 v! C) o# I3 L/ c% a4 H- b

$ A( R2 @" N: U+ x% w/ C1 H     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 3 ^( R* O% n) w' [" L3 H

/ z8 h/ Z& e" r/ f b. r! _6 Z% X G* y

! k( u2 D7 Q% f. n         global $DT, $_userid; ( ]- A- M/ V; c1 F

( r v4 U5 [5 e- C7 q

& O) A) u( s, H6 n9 I: h         foreach($_file as $file) { 9 M+ c" |. ~/ G2 S6 A6 I, c) a% v

* q1 g" h, s& D) Q. O; v7 k+ {

* o5 } X4 E& X& }5 `! [2 ^             $this->file = $file['tmp_name']; $ G& h' B( M. R Z/ u7 U5 }

4 a" }! M3 G/ |" P$ J- I

2 m" b- \5 t8 @ p5 Y2 _: B             $this->file_name = $file['name']; $ D, J1 p& ]% |$ L4 O2 `4 Y- S" K5 @

: G7 V) W9 s! X

; f. k# A! r- l             $this->file_size = $file['size']; 4 X! z+ i3 K; o/ w! q

; j- N- V* c; Q. i1 T: `

* G0 t8 k) i- B- j+ i) f             $this->file_type = $file['type']; " i; T5 o. l* j9 D f7 {: V

! I7 D; r8 m% O

9 B8 ~% i5 d; Z5 B: |' v             $this->file_error = $file['error']; + I7 g+ q5 z# M. c

9 Z7 G; d. r' r6 g% Y U8 l

, h6 f7 g" h4 q# D' V' G5 W7 `   6 m6 @" k2 ^& h5 q m9 ^4 ~

4 }% m1 z d! w' t' a

$ U {, ?; K- \; d( X         } ' C; x4 l" U( a/ K8 B7 `/ j, k

% W/ n% _( L K( O$ ]/ G

, F) q) V4 f7 y% \* n; `+ R& i         $this->userid = $_userid; 9 w7 s4 S! B# c; N

: T* P }, |$ H: ]& I$ {1 n

{, P- Z. {! s( W" [2 J         $this->ext = file_ext($this->file_name); W* N3 a% i+ c( R

: u- E" Y% x& D+ E% v6 {* p# ]4 Y

& o- ]& p( n$ q: _. [: p: H( Q         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; - W0 n, q) j" T: A+ [

8 ~5 _3 V8 y- h) _- R2 f9 k& m% M

% i Q5 |# O3 D2 A0 R, \         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; * G. M9 z1 `3 w5 Y! Z6 m

. z: U$ P: t' [' y" e, N

0 u9 b1 |) h; w5 Y; f. L         $this->savepath = $savepath; . R% G* W7 A. ~2 B; Y- x

2 S, i* G' {* q

, @, K& {( ^ T% q. C& {         $this->savename = $savename;! f8 O& j) r/ M8 y$ y/ z3 w% f

: I( Y* e! w; V! O+ q

- y- i( k7 ~) c, E: p# o     }} & u3 Z8 y3 @# a

3 I7 T* P7 y9 S) }" ?

# c0 ^! I/ H' c/ \8 u 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 # V V' \% F; g0 X/ f( @6 l9 E

( w" P! q0 X0 Z& h

; M6 a/ e+ v0 F2 e% t5 M 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 7 F. n, Q, F7 d+ Y

* W6 B- t4 c/ k* m1 z$ F5 s$ m

3 ]2 k( X) q/ @$ G* o0 _( g $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/ Z& z" A6 ?" p: J0 u d' K

$ W+ g6 m* c! z- p, @+ w

9 H& a8 ?8 }9 U# u- u& Z 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:& F$ p7 g( u( x, t v# \0 Y

( `+ C. M; G2 v8 q

2 E7 w8 m& z- {( f: c7 y/ f6 N1 C( |   " _. T# T$ n X

2 s1 x. f" r7 \( O% R

# H5 b% ], u( s0 G% @# \ 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: . R( i: e9 F6 B4 v( {2 a: E

3 l/ k7 i9 I4 m

' }* z0 v7 F2 i* c* a3 a$ T: T <?phpclass upload { ! T0 O. O- K y' ^* n4 S

4 a" [0 U _: t* r! p6 Z

. K* j6 z7 Q3 r+ D% e) q     function save() {$ b" R5 R) _ v& u8 d9 _

- G; y* S, C d5 w3 T1 \8 f

% e+ R; Q( Y+ E6 ^7 T- Q         include load('include.lang'); - ^& i- t$ {2 ]4 ~# Q

9 Q: u1 n) B4 ]) B

5 H2 T5 O9 {+ P2 ]; l! P         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');6 y8 q$ l9 f/ V' w# Y' a7 K+ J

4 n% {- t" v( F& Y

: o0 _# ] X8 e1 h4 z   ( q: S8 [$ l* `/ u9 p1 I

7 G- V* }5 _1 N6 K1 K7 y6 L* Q

9 H! L S L# @; L6 `         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');1 `8 {- |+ r' N1 {& H, y: o! U6 ?

: g; ?& N+ Q4 m3 C4 g! y5 s, T

" z" n7 m m- D0 ^  8 e q+ p, d, j' u5 \

* m; a( Q( A P, [* y- ]) ^9 J% N

4 q0 Q1 _# G! X, i# U( l) ^         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);; X. o# z& H R" Q5 D& @

1 H$ c4 J/ t7 \

7 I: Y2 `3 l9 e' Z; ~! O   / ]) } b7 e( h0 V/ P ?& b7 {

- Q' Z6 v- L% u* P* b9 |+ B

$ i+ i) g0 y- h z+ Y         $this->set_savepath($this->savepath); 2 ]7 X0 G* H: E' [$ p

1 v* u6 l% r( Y# f9 w+ N- }: u

8 V7 {7 F2 Y$ u8 c         $this->set_savename($this->savename); " R3 E% M t2 ?2 s. s' S. H: R

2 x4 V5 N7 \8 `

- a+ H8 L" c2 h4 p2 o& C* q  1 x2 G1 X) ~, x' {8 }# {) ^' c

2 K& d2 g U$ ]" x' b( v

T/ @3 K3 v/ Z" _         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); p$ }2 o( V5 K7 h+ d* m8 a

, b8 b1 U, \+ a2 J

8 }* A: V4 |' u) B0 e2 d         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);* ]) u3 }1 E% l2 K7 N) T/ R

) l) D$ b6 w+ s: w

: }' q# Y# V8 ~. d: F: X) T- Q         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);) y0 U9 j* B6 p9 ~

1 X4 j7 t# c6 x# T: h7 A& ^6 _' V

! h3 c! r$ V. \! c& J9 w7 B% C/ A   , c& h! x0 }& l; A

1 {2 w, ~+ [# R- ]3 h9 Y

) m; ~# x5 \ X         $this->image = $this->is_image(); ( L5 ~, u, `: x+ ~ L% T

' @# i4 ]3 a5 s# x% j% v

; f# ]' j/ z4 g+ Z# d8 N4 s- Z         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 5 |4 }7 P& F$ F4 a& e3 D- I

6 _# ]9 N# H6 L; G! y

$ i& F* U) `5 {& i$ P         return true;# A, W- v+ k. I) v# @7 j2 @

( P4 a7 {3 l; N7 n1 B3 n

8 N5 A' F6 Q: `2 t# U2 o8 B, w     }} ! G7 L9 W- p- T$ h

2 p7 s; R$ T* c# ~, f! q2 \' d

. p2 J% R3 X! B$ X/ p2 H7 \ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:; `/ |; g& ^% J

0 z3 T+ d, j3 t- h

1 }2 U$ T9 T4 H6 g, }- q <?php + n3 m; ?0 S) g2 Q

1 q( i7 H. o" f; \( `, G* \

# q; |9 F: s3 Q* o% w     function is_allow() { 6 d2 K4 G5 u( ^& | m' o1 K U

q5 M3 t' ]! i; G: F2 g

) v4 I4 ?5 H' z) D5 ~; H' M         if(!$this->fileformat) return false; , R; H( W6 ?+ u1 E( s; u+ a

- |& b S- Z$ A, ~$ D$ x

# K# u( j" r9 B3 ~ f9 ?2 Z# a         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;3 d* {' N6 m* `' J+ y' U! i& _. r

3 R) t5 J# B* T9 t: w0 t

" u6 u( i0 V+ \4 ]         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;. |' q, d) B) _+ ]% U

) t% K$ }' y& f5 C; b9 D

) @' }8 m. ?8 Z- D( v5 y         return true;# _7 ]# G4 X5 p1 e* ~

1 _' W h1 B% f$ m( e

- M3 C3 ], O" K: x( Y, S     } / E/ |& ~5 |7 X( g: m/ @" X& A

* T2 T3 H2 r% ?' L$ Y7 F

0 x+ F- L# d7 E- a8 n 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。# d% o, M& o% U/ x4 ~: r

1 d' [# _1 T0 e, w! P/ R7 u: N

- ~, q* R7 K6 L4 Y$ O 接着会进行真正的保存。通过$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文件。3 H, V9 r% G* S. W) B

' b7 F! @0 \- B( M9 w8 M2 p

) x/ y2 F2 I1 i2 i 漏洞利用! N* q5 _9 G" ^* }: i! A6 m

6 S' L- j1 I/ V- C

6 W9 j W4 _, I+ s3 S( t/ X 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。- p- Z+ c7 _5 H5 ~" @# D

: E. |* }: P. }! f1 b

! t; [. H, b/ v& o E! ^   ' {( K1 t7 l8 n

. c+ ~2 x* u$ E6 D) r# d

& {# b, C& f' s8 p 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid" t, b, n' |9 U

) ]: N( z/ k" j [

5 i7 N% ^0 F: j: u! O9 ?1 o 不过实际利用上会有一定的限制。4 B' V: D( Z! p2 p+ v

$ N( c0 R. v! F( j- w3 g

) ~6 U3 r* z7 Z# J% H. q7 x6 n 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 : ^" @9 k" w, v F6 D0 d& Z; ?1 X

! @+ \0 A$ V. M, [- O( W

- a9 k1 N/ A. h  & a$ E8 g3 N6 G

3 v( A5 K; i3 l: A' ~" z- S

; l& Y8 b8 v% i! F5 L$ [; B 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ( B* t/ u: m5 s9 ?* N* }

9 l s& E" P3 P

9 o( H& ^7 [# o, Y 省略...$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]);省略...+ c' ~9 |; g2 N$ r G0 T. L

$ M. y, c- H9 F. x3 P% X& p

4 x k6 O; }% q" y0 M5 _ 因此要利用成功就需要条件竞争了。, J/ W7 B' f' [3 C+ ^; g

; r! | N& x% ?0 B/ z

8 a" Q; R' C# `" m7 }8 T 补丁分析 % m) `- G9 O8 k* q$ S( B* ~

5 b" i, {# E' f( f% F

, \2 o, i# H+ X6 J& n1 p4 G1 k  7 q* W7 i: Z9 s

[9 Q4 _& q1 U) `& ~

8 Y' G4 o9 j8 f& o& s4 m- v, s 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:9 _8 \8 k+ g5 h/ O- e$ L

) V8 y. i4 j0 |0 G

' d- `, W' c( Z; R$ y" C6 \4 o+ ~ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}6 F* k4 g: e8 A! \

6 }1 p; M1 l+ K" I

6 }* m. T; L1 ` u; V   : h% r u3 ?- h5 v/ P

/ D) k- |9 I1 L

/ V( r; R& `1 m3 h9 M 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。' L/ k+ d3 A; f# g

( c0 Q) U% s3 K6 h7 {

, B# R. j$ W" z3 Q 在is_allow()中增加对$this->savename的二次检查。) G# c) x. L1 V

* \, M# w: G+ n e. ^5 w6 B L3 ]

2 X: v& V) p7 Y4 c# @1 i 最后 8 I' R; h. G0 W8 G' m5 q; {% O

4 U/ x' v- m' y, {4 j6 E

& \3 W" M! s4 Z5 W 嘛,祝各位大师傅中秋快乐!; A* o1 b5 I4 e, H* i

2 V% V n) P0 S- B+ B

& J7 X# [% Y% M7 T- Q: M- Y   7 I9 V$ u: ]0 `

: c, O9 R* Q4 g8 k+ I. e- R. G
回复

使用道具 举报

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

本版积分规则

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