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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
* [0 B0 v) r$ e! X& E$ Z( ?! Q

2 B/ O' V8 ]! Q1 _- R4 R

- x+ B" g4 t( A) d1 x

8 h# X" N/ r$ Q' Y3 t F) l; O 前言; R. K* o2 f8 t

2 f- ?$ P4 y- W5 A5 w3 D2 w8 K

, ~4 m- j+ @4 J0 u M5 X3 T 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。2 l8 q* {3 Y7 ]/ A

* k, l, v. P8 V" E: l& p( z

) ?% q- h0 d D& m: z   3 o# E6 _7 C6 F( h. ^$ r2 N

6 M' m S: K& N! m1 f0 D: G

) C& E& F3 `5 g, T/ f3 J. l9 \ 漏洞分析 5 i! o7 e; D+ X% _; V0 H

" l2 [$ B1 ^+ t2 W- k* g

, t4 R k4 o5 t9 T+ a 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: % Q( h2 m' k4 c: j6 h

2 v- n' h7 f: O" q4 G8 w

2 b, X! ^+ r4 _! A& X   0 }' D+ n( O- Q8 x; m( ?6 S. J

0 n+ T$ ]; \& O( M

# E8 i' p& I, @9 c& J' z 对应着avatar.inc.php代码如下:' ] @% J/ U9 }' S5 y$ `' c. M

0 C- |. f$ E* H6 Q

: l( F2 v0 _0 I' P) S, ]. s <?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) { ' t" V, z7 N# K* p9 ]3 J- O. \

$ u$ u6 V+ F2 S! B+ z

0 H, z, p. w5 m E/ s" _+ t     case 'upload': 3 `& u# e, Q1 E/ S$ ?* v

6 q" x6 J9 n. p; b* m! O, B6 `

* \; h X/ Q+ z) \! J1 B' j# `         if(!$_FILES['file']['size']) {6 B7 E+ ^# d# H' d$ d8 Y

) f) ] f8 p3 o+ X5 s

- W2 P* U) g+ {6 e+ Q9 u. }             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 8 a4 ?, V/ Z2 d& G+ d- `) b# A

9 N/ m5 s6 f( K/ \% i( C

6 x8 t+ w# U3 S5 \6 v3 E$ \0 o             exit('{"error":1,"message":"Error FILE"}');; G8 M r- E" z

+ r9 B+ s. h6 J4 B

$ w; {! M( n3 F0 O% F6 n/ n5 X" h         } 8 r2 T; i6 X, q$ s) ^

7 F+ n, I* z, W8 ]. J

0 V r7 y4 M9 Y' O         require DT_ROOT.'/include/upload.class.php';7 l6 M/ w( D: p& Z" [8 A1 o

% z6 e, t( a! @; i8 l

) R- b5 F& q# s% `  * ~; ~# H; C+ K9 D% D

; @5 b! j$ |5 T* j0 e( h9 J

9 x! o4 j8 X4 x         $ext = file_ext($_FILES['file']['name']); : ^" ~. |, G" Y' h" O; t

' Z8 B' G! n: h+ U% C

0 [+ M( t( ?4 }: i0 ^: @         $name = 'avatar'.$_userid.'.'.$ext; 5 |) y/ H Y- Z8 E4 m

) D0 U: Y( Q1 Z7 r+ d2 l7 y5 @

6 W% g" @3 r# L+ ^2 g' G( p         $file = DT_ROOT.'/file/temp/'.$name;. C$ i1 `- L4 d2 {% l/ w

3 y+ b5 M5 h4 v/ _" G) _0 |

7 f& ^. {6 G( E9 g   % q+ L% x+ `1 Z* C# y. ~5 k7 ?$ H" V

- I- i0 Q! ^: r& U) d

i0 n8 P6 n. ~* t         if(is_file($file)) file_del($file); ' [+ Q6 z/ H$ z- N( e X& b

/ S9 ]: G. X2 j

- c \2 }1 M+ d6 _         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); " s5 H2 C: g7 H L Z( e

+ W, G4 {" q6 }+ ]; m

* D, Y0 f) {2 w' d, N, r3 k. c  0 ^* G. _4 \1 H' Q- g

: w8 c1 ~- Z- w, A8 }

8 Q3 f/ v6 O! y/ e         $upload->adduserid = false; 1 x' @- n2 _# {3 a

5 X w: y+ T- H" G f

, ~- B" g+ Z d. w7 n( U1 M1 h   $ Z: G* H" \. \ R

8 |. o7 {% P8 @2 @) t

. Y- b% Z9 K! [9 U3 P4 I5 `# S- T         if($upload->save()) { X; T) O7 |/ R& t. r w! N- Q

3 {' L% F1 u) y% |/ M6 ]( }1 i

9 z; I" v; |3 H H3 n             ...% O. m' a4 I }; k+ t

# G5 I$ f4 i- Z) o$ [- U6 J, s

) Y" T. B, x/ H% N         } else { 0 L. B9 s. _& Z5 O- v1 ?* C8 r3 H8 e

9 @* T# y- A5 w

' x1 d* l) b5 d, [" W/ F& h             ... K. O. h4 o$ R) f/ _

* V; Z2 C& o6 a I

1 S7 K8 ~* R6 B2 Z         }/ L7 R! i1 N, Q0 }) H0 H

: E$ Z f& t- h/ q; m

G1 c& K8 O: [& _- r/ l; w# g     break; 4 g' t, E" y. w7 P) I! v: L

" s2 S, X6 r( C

' u) e7 L( B! H 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 6 {& H% @- @+ A8 Y1 b9 J" S4 i' R

; Z" N( e5 ~; W6 p' q- k

- C9 F9 Z/ V( S: `2 B upload对象构造函数如下,include/upload.class.php:25: . }0 D" A& J$ ^5 x* D, \

! D! q7 M% e3 _1 Z- t# a$ U

; r& A% N \, A) X+ [- U$ L <?phpclass upload { ) ]+ E N- i& X8 A

# K& B4 T4 J6 R1 F. V

! I' u6 l& {( M) R) }' E* B# Z' s     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ; B. D4 z' B( s; z

0 G; D/ o$ d4 e! e$ N! P

* H8 E& m9 ]7 H0 N) J         global $DT, $_userid;7 I* @8 G3 t2 `* N' N: X. f

& ~/ Z" ~3 X# \, J f# {

' b) c$ U9 Z2 t2 o5 t3 \+ k         foreach($_file as $file) { 1 f% N* P8 [1 v* P5 g: V

3 @$ K$ ]+ x q2 K. j$ ^8 G

* D" T1 h) V$ I) V: T( ^# x             $this->file = $file['tmp_name'];$ f: \- L* A1 U& N- n. o

) J9 b% K) E" o5 E2 p

' n W; P, F* m5 s& u             $this->file_name = $file['name'];* N1 B* k$ \- `

, O2 _: Q2 x) ?, y j9 e

) F; M$ \+ [! n i+ w             $this->file_size = $file['size'];, R3 {1 y" j: T& k* g

4 z9 |0 \- C3 b4 G3 Y/ a( A1 ~

T5 d: _5 J6 O2 l8 a             $this->file_type = $file['type']; ! J5 x7 h( _7 t: L' l' F

+ \1 d9 q3 ~* h& G, V

/ N v! w* K& o- p2 @# }& t, J0 z             $this->file_error = $file['error']; K; B( k N% @' |( s3 r- ^* Q

4 C" r+ {7 g/ P

( { N9 U: t0 y5 T7 v; ~& ^   2 D+ g) G- W/ A! X. Y7 l

% Z6 S( b2 a: \7 o9 t( j* E: |) A

0 c( t) g. t# m' A1 I/ J2 S8 k         } / v5 S8 o8 z& c0 V2 ~

" X7 K/ c9 |+ e9 i: j8 R) j

2 }& m# `) b$ _         $this->userid = $_userid;% f/ s7 }" N: E% r+ D3 l' e3 y& a

# O ~, Y1 h- T% ]2 R' H

* v+ v1 R$ o" M! U; G         $this->ext = file_ext($this->file_name); 9 t, H3 B' ]) s3 A. I( k7 U

; j5 H" Q# h0 w4 p

# x4 h) W8 \- |/ ^" v) t g         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];4 @8 f* N Y7 Z9 z. a% E

. T+ c5 C& H* W

6 V* W: P9 @1 h5 ]% d; K4 B% m         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; + c/ S- u: P5 k# ^/ {

1 j- d/ [+ }0 M1 I6 y5 P5 ]

3 h9 c% v; A7 k1 ?/ ^- E* {         $this->savepath = $savepath; % x6 |2 _3 @" Q% K

& D8 c8 n5 ]9 ?( c

5 f& Y2 U0 @1 V5 ]/ h8 Z, g         $this->savename = $savename; " i( ]# _4 X( @/ ` J

7 H7 q7 n: D) q4 i

* w, k, c2 P, i$ U6 L0 Z& Y) Z     }} 2 a5 Z6 F/ H8 \

4 w; I! F, ]" z! n1 B! [& z8 ]$ o

7 g, a( j8 ?; M O. i/ n% R 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 . N. s d0 Z' L; D+ x

6 U: @' f/ N) ^2 W: [8 A" k

% }& W7 e5 j" K( d. } 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 B! V" l* ^: L$ C6 o

, X% V2 _7 X( l) z9 F* R

) X1 t5 E8 u- T3 A+ W% t! K $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 1 H$ V: K! r; a/ n7 d5 J) g. ]" q

3 _9 j2 S0 h" f9 l: F2 g

$ A8 J @# U- I p3 ? 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 3 q1 J h% i2 ? g: S" C

f1 X: g D( `, O4 E

4 \$ y( P% z9 S- e# h  % M E8 J2 y+ _

8 o6 @0 A" F8 e- [" O

7 [7 j( x- D. m% }0 v* ?# h 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:0 w9 Z7 I4 p& Z5 @: I Y) \

! g( D7 r# }1 _! C# `

# @/ V/ o/ r3 E( f: ?1 b <?phpclass upload { 2 h1 B2 p# c6 V

5 @" F4 H1 m3 a

( }0 j: p. v. U% A5 E6 o0 z8 a: e% h     function save() {, y4 C, f9 O3 I4 g' S/ F% l9 u6 N, m

3 D4 v. f4 F; t6 P+ O0 f

- m" ^7 _6 u$ G$ E3 V6 r/ |         include load('include.lang'); 9 m [0 Z. w4 h7 l- A

* O1 R1 s- A* x2 W) \5 ^1 `8 J; N

- J6 r* m$ U4 @! o8 t         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');9 e9 S1 x( w' s& J

! l j5 \, G5 S: j4 |7 e4 m! n

7 f: K$ s7 w1 O4 ?& b9 m; J  ; d9 `2 Z/ A' m- U

* P: s2 g- U8 s8 `

8 B, V$ T& J/ s& N+ T& {' `         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); " u& A. T, J, e3 Z

3 ~9 n. m1 q0 d6 R5 ]% \" Y

+ r6 a/ a1 n$ S( P: b   3 y( W" `; I0 J( A+ ?

+ ^1 O) G& ?+ N

3 p9 d/ |& |4 o7 J         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); * s, R2 k' A6 I2 \; g

+ P/ c8 [( N( `- G$ j2 U. z

( W5 M3 g% y5 d/ b   ; {. E3 f( S9 \6 {3 D" B5 `/ r

! m* b1 ?9 i. g- n$ O

0 ^; }. Z8 i( e" _( [# _0 p         $this->set_savepath($this->savepath);2 `4 m( r* z, J) E; O

6 o) i$ j9 i& G9 h9 ^1 D" x1 P

9 h9 I9 q# s! o) F C2 |         $this->set_savename($this->savename);* p3 k6 |2 C- P" j' H# z9 a' S: v7 L

" h% o5 h, u) m3 C: d% H) n

! p7 Q0 y7 v* W8 c0 }6 R0 |  ! p, H, h' _5 [9 c6 ]

8 X( C; N1 o6 L& B0 o' R5 y: Y7 t

! r% }! Q8 z+ c5 E- I! {( j2 x         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 9 d9 c5 h8 H7 \0 c+ Q; a, a

- z% j8 O) o2 O ?

l! U) N1 \6 P" s! B. C         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);4 |% g+ X p+ I: o5 B* V

7 Z5 ^+ ~2 `# p7 b8 x6 C

; C0 b6 \% m7 N- w& z+ |0 Q         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);, x, U9 s# R+ h7 y

9 S# H4 `' X+ y

x" A* o; m0 c( b   ( w2 M" l6 Y" ]

, v: H9 k) }/ F: [

& d: t6 V9 u- O8 _4 L         $this->image = $this->is_image(); 6 G8 }% t x* H9 l$ L4 i9 P

; D( t1 ]$ Y; |' e& r! B

# t1 t+ d) o$ ]         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);: F/ [6 U2 Y/ K" j

% `4 `* V4 S: y$ S- S7 c' h

% m+ [; u$ a, n         return true; . Z5 {& r8 _- D6 b( C

4 x& b3 s; _9 `" M& V

L4 ~( W4 c; l/ f: z     }} : K# H. L+ P! ]3 b4 F* V5 v6 G

% ] s* R" h, s$ k

* I! R- T W" f8 H$ [) c3 c2 p# t 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: * G/ X/ r* O; ^; j+ V

6 A/ \& q0 ]8 x7 L9 r4 p) }

( `, s( p) F7 c% `9 I <?php % F3 p8 Y: i# V V5 E

0 K7 i9 c' _5 O) Y4 [5 B! Z

" Z' k; w; G) A# O# T     function is_allow() {6 I* V& T! I3 s) B+ ^; _& E

. p8 ~* d; } r2 A

u9 k, H0 E- y6 {) Z( ?/ o7 x         if(!$this->fileformat) return false;( c# @5 I7 x5 D8 k" m* s! J( ~8 ?

d& Q1 j4 I& A' C& `! j) Q, B

, ^. P. \5 I# _) B! @         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;# A+ r2 \+ i1 q% [0 ]* Z# r

; G* o, L0 i7 y, q4 L5 Q

) y/ d- A1 u1 p6 I' [- P         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 c. `/ X' U: V+ M

" v# D# f! Z! c: ~6 r) Q0 m$ ?

) \2 F7 L* w8 M0 N4 y& [8 H1 \         return true;# S3 a. m6 h: _; t3 Q; q6 y

5 g. R! p" m1 q

' r; M( U# S% C     }' y" q Y5 P. m+ B

8 V, U( ~' `8 r) N1 i1 X1 c0 D

" E* D( e6 o: r% S+ |$ Y 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 & K" Y! m. A# Q! F) h

5 {! r1 r: T8 e \. T/ K) N

Z+ ]. K6 \4 T5 y H0 X2 b. C1 z& j5 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文件。$ S! H: ~: r4 |, b6 `! r( v

/ P( U" |+ S. O5 i( t6 X

( r" P9 C0 q% Q# p 漏洞利用/ n# v8 R/ k6 |7 Y+ z+ P) y

& d8 h7 ]4 w: B4 a$ J3 m# B$ {8 z

# B1 J' r- P4 s/ R8 Y3 S5 W) S 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 , d! c D! S8 e# ~0 w5 S

4 v, k0 }- c3 Z4 ?

8 k* }7 h& |$ N$ `" g# a+ g   * i9 O* b3 @9 s" G7 k1 L' b

$ J! }9 X; v% T! J* Q

+ u3 h) n/ b0 A9 i5 R4 C1 i 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid) `' k5 N9 h' ^: Q8 A8 M- H

3 Y" }. {/ T2 {: {8 l* F. \, `

4 Y; j4 Q* |: H6 } 不过实际利用上会有一定的限制。) d! A+ k; k# b. e1 R) E0 H3 C

/ w+ I+ u9 ^* v

6 r; l( Q4 [8 C) P1 P 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。5 q0 V3 J. u# s9 T) R7 p) u$ Z

7 v. p1 h! F, _+ P7 s

- P& K! \) H0 \  2 T0 |7 \, ^4 \; w4 T

- @8 w/ l: J8 Z& |' \

! D5 r8 G* }& I* `: t0 L8 D# ] 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:: _, ^$ P: Q/ W) U( D6 x2 E

9 a# w. ]- R; A( @" {: K

6 e5 |5 _ v) j, t. d( ^! Q; e* E0 W# ] 省略...$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]);省略..." R8 x& N2 ^' z1 w9 r

' o9 `/ ?( G2 H7 L

! P5 B; i0 X% O# P! d 因此要利用成功就需要条件竞争了。 8 K) }, k9 ]. Z; W, h

1 w: y* e* g% b3 G" {

- w" }' d1 X* Q. ~8 O. z, N8 O4 E+ M4 ]/ ~ 补丁分析 5 ~' Q7 C2 v, ^1 J2 B8 m& a

! e: B0 P$ \; o$ ^

8 [- u" W- X- q; z7 a: \; `- e1 V  ) E, q) o% s% t. b2 F

% K; K* z3 M. w Z3 G

# G6 X9 P6 K) i 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:/ |+ f- u$ v: S

! m6 r% q9 Y8 t% T' U

# ~9 `+ t# A1 ]: s1 L8 k function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} / v6 z; k+ G1 y# }# b

! {& R6 T! A0 ~+ S9 n

2 T/ o$ U! v# f' L# _   . [1 j2 L, o' `

# d1 \9 K2 a3 w" n: _

. d5 H/ z7 u: P+ A/ S- U1 ~ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 r0 Y/ w7 w' }: ]: m4 G: t4 `

, c% m8 S c, ^& I& U6 p

, h, c# |4 R$ |. W1 T) [ 在is_allow()中增加对$this->savename的二次检查。 4 D' v1 x% J/ h# S

! _& C0 x. J* h

9 r7 n# f) i: z3 ]* L2 f% n4 [, j# E/ w 最后) }4 `$ y6 f3 h. g

3 `$ f& K0 G2 ]; x3 h6 \& f

5 H. F( T* H1 ]: i* k3 U B 嘛,祝各位大师傅中秋快乐!! |% M7 J! m, k7 m! ], U

6 ^5 \. Z# n! s5 Z* g

- L6 T0 v* V0 E2 J; G   ) h% G# R, F- P- Q- B* P

( c! ]( C. V y! q
回复

使用道具 举报

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

本版积分规则

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