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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
: T" N, p* s6 W1 f! v

5 b; L4 F& G k0 F$ } n5 d9 R

' i( a4 P% `) T! _$ P

; O% a/ W) ~3 N, b) x9 B 前言- k1 S, i& P) }! P

8 u) ^. b% y, t0 l% z+ q: w

! ^" `0 Q% X6 B; D( Q. p 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ( e% f8 z( @! O/ R; |

" N% u8 J! Y. @1 L: H

% {4 l$ z" i, ^8 Q   ' @. X5 D- \/ u( P# h7 u

" J: S9 u$ r; m9 z+ |9 h. I5 R) y& }

5 j% g6 w- E6 ]0 m0 ~ 漏洞分析7 Z( ?9 z7 a! P9 q% x. u1 p, }- G

# \ z! Z& h' O- a9 r

( J" h6 C) p1 t# C, } 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:! ?4 {4 `# ?. p* n! h: I5 p

- Z7 k% `2 e" o5 b3 a% q

) }; M4 l& E2 X  2 ]2 ~# y5 `4 |4 l% @

2 Z6 J- j! k# Q" ^# e

z$ f; H c$ h 对应着avatar.inc.php代码如下: 2 q+ E0 Q- J1 }- |4 V9 h7 {6 o

! `4 G- H& x& e0 a3 H

! X! m7 E9 Y- X; V7 Z; D/ I <?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) {7 u4 P4 l) R- r! H5 Z

! E4 p# e' D. P3 U B4 z/ ?

A% C0 {3 G5 y     case 'upload':7 W, S! Q: [! L7 w

* i9 k! V3 |6 r4 q% f4 X

! F) h& H9 @6 }( s9 x: O8 V         if(!$_FILES['file']['size']) { ~. |; q3 x0 O ` V1 R

g. N0 Y) h7 C+ F% Y9 H$ r

: }: H3 y/ }8 K1 m, u) |# l             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 1 H/ t( f5 q7 K* ~, g1 F0 B+ u: ?: u5 b

9 u3 _* [/ _9 V

8 t6 r) ? I( \/ j( u             exit('{"error":1,"message":"Error FILE"}');6 D2 i$ H/ w* }, h

`2 d! M O) G4 i- F

6 B+ j- }6 t! X         } ; g5 t! U' s! P( ]. T/ U( j

0 {" a8 p( G5 [9 f

7 g; i4 |" V% b, `+ s         require DT_ROOT.'/include/upload.class.php';8 m7 l" s! p' q$ Q( m9 L( F. i

9 z- g7 }: I* A$ w( f

: u' j/ j Z5 \0 Q8 t1 D0 g# y  4 W" h ^8 I2 U. V9 u

/ c/ j9 U* y, R5 S7 q

8 L1 j" X% S* U! J. }         $ext = file_ext($_FILES['file']['name']); , h$ j( l, d6 o+ a" f8 ~9 I0 h+ H5 Z _' q

/ @3 V) r0 Q+ V' t, {( V: M

- \5 m+ }8 ]: t! z9 u         $name = 'avatar'.$_userid.'.'.$ext; ( O5 ~) v& z o

! L6 f* e8 s4 F) y3 n

! q* f- v2 A, x9 O8 X" x5 e         $file = DT_ROOT.'/file/temp/'.$name; m0 `6 ]* S( F

* Y9 g5 _& K! t

1 P% l) V7 Z" ]  / G }& e& L+ z5 z2 S( x2 Q" q7 `+ ~, X

1 z* b' S5 I0 ]! ?

+ F5 }6 j3 r8 G1 j8 P8 s         if(is_file($file)) file_del($file); - ], o0 o* ], R8 v" n3 E

' _4 y9 z7 |' D, ^! c

' q7 T `- y+ D% A1 L2 W" |% M/ Y5 h         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 7 j/ ^& ~% {7 B3 L' C

- S5 r- R; g; ?8 W* E, F" @

+ d8 {3 T1 g1 `9 r1 G  - {( h( ?- e% \+ X9 a9 P$ r+ w

% [/ d0 q$ [4 J- l

/ \- I2 j! s, ?4 \         $upload->adduserid = false;0 m1 o9 U6 ~2 L! D

2 U2 @6 m3 C6 B( T

5 e" U2 p& o4 f8 u$ I   7 {, G" s$ P/ d- y8 X! r( h

) x$ M" e, O9 [1 f) p+ P

; ^" K5 o; m j2 e% x         if($upload->save()) { & |; d5 E: Z- c0 S

- B6 U+ N! ^6 }9 P S' ?

! Q* F: Z# N& _8 X/ r             ... , c5 A6 k' W* a8 k/ n- m

1 J: u8 F2 O% k0 ~2 j

e# S- H$ E3 z0 {         } else {% \8 E" t8 l, ~0 ^1 G5 P0 H! g

$ \( D% a& m' _5 q5 Z8 q

' a- ^6 D, E2 ?$ p2 h             ...0 C% y' l( z& }! [

. x. v% C4 ~1 H" f

' @1 h' i9 _ s4 F* |6 a) F         }6 ?$ X8 F, f; M

) Z- @8 ]( D; ^ D% B

. \" g- `3 A8 z# D     break;3 L, l! y4 w# Y& i# c1 ~

- t5 U) w* G L: @# \! o

8 q# f; K1 I/ h8 X( W8 k 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。9 C- c+ N6 y& h* b6 s

2 Y: w+ J" g- y+ w1 Y5 E

8 r z1 P s; a0 ~ P upload对象构造函数如下,include/upload.class.php:25: $ k! q Y. r9 q7 x

# S9 B8 t3 Z2 |" c# T# @+ Z, T

0 s2 _7 h$ j' w+ l3 Q <?phpclass upload { 5 d2 ~# }# _* O4 @) ?

- R! H5 @& L& K+ \

: k- Q h" v6 t& h6 P3 m     function __construct($_file, $savepath, $savename = '', $fileformat = '') { + w# O5 Z# B+ h$ L) X! B

3 H" b) w6 y1 Q- e% i( T) f

9 U) X! W C- Y8 H         global $DT, $_userid;5 X7 l$ z* _) v1 t4 r! V- N

2 X q% C6 I; |. l3 F K& e7 a

; f( s$ q/ m a3 a& _         foreach($_file as $file) { Q5 W! x3 ]) I; M% H% |

6 Y+ x6 q4 F$ c. m

, h9 k; X; b* y             $this->file = $file['tmp_name'];1 P- ^ ^9 s7 E( i8 R

: ^1 a: w0 q- F; c' u& ]( F- f

5 P `" L: A$ ]* ]- u             $this->file_name = $file['name'];: n/ O: u! O: ? G2 l6 m

. m0 X( k8 [$ @1 P

$ l: ~( g Z9 E5 x( s1 D             $this->file_size = $file['size']; 9 j |/ v" k! Y) |, _& I! M. R

2 K, H/ O3 Q4 l4 o

3 R6 ]4 z; c! `( r8 F! {3 S; F             $this->file_type = $file['type']; . ^3 p2 h) f; M$ m5 D

9 I6 [; N+ _( {

: m) g! r# X2 X! |, R5 {# x6 u2 ~% J( y             $this->file_error = $file['error'];8 E; u0 H+ P X5 Z

. C" d6 t/ ?* l/ P7 n& ~3 F& O

1 k& K8 g9 M% @  8 E# E" S6 P) O5 S- m+ N. G

& d8 V7 T7 F- h( a1 t/ K/ w, N

, N& A2 O$ j6 t1 r         } {; _; ^2 n+ F" H c$ |

& U7 e( Y+ ]) C& c; R( {6 y: `

, ^$ K) N6 m; D q         $this->userid = $_userid;" J% [* v% ~0 j9 Y# B$ c

* d9 P. G& j* b2 [6 `( m, f

1 q' w6 x; U' V         $this->ext = file_ext($this->file_name);" z% K7 F, R/ V. ] ]9 L/ S8 A

J6 w+ \2 F. L0 k s+ r

9 M' x7 M" w4 N1 o1 _         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; y5 d4 t8 e. K& q3 K' q/ b/ \

6 d1 a8 p3 ?+ F9 k) R

[$ C1 D& D2 K# U9 V! G         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; - c2 ]7 X# H7 z# o/ l

1 q9 ]' M' a8 P

1 g" V, b; U! H( A/ Q* c! f         $this->savepath = $savepath; 8 L# c8 z# @- L) ]7 h X

0 H4 W! H) |8 w0 P

/ H$ w' w" v q9 k4 h; F         $this->savename = $savename; ( A* L+ u7 H( @2 F. ^8 g3 Z5 G

- `) L" r# ]8 I4 W2 L4 b# }8 z

7 w, {) A( x: F) S+ v     }} ; P- G% [7 V3 u; j

# C: c2 _4 }1 m. u/ [+ h

- t7 s6 b# i& a 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。0 J8 h: d5 [$ _( i2 s

+ S' ^" f' U% e7 u

|1 m1 _/ x- x$ ]4 p+ G 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ' b/ R# X. D5 U; ^

* L, A- B+ Z8 B* q

; f% t T* C! }2 X $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# z* o+ Y+ P

' O3 J) [6 z' O" J/ `0 C% S

: i/ m! s S$ y) F4 q 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:+ g H! u5 R1 a

6 `5 V2 P5 K5 H* A3 n

1 J t0 b E3 J; q8 ~  $ J* O* J. T B) R7 P* Z: |" V4 n

: u3 l5 A/ {: A- E' P K* k+ J7 R

/ p1 {5 z2 P1 [+ @ 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:# }# y5 u2 J# d1 z2 j+ F h1 O

$ E& B5 |* }. ?) o; A* }$ E$ f9 ^3 k

- m) K# Q+ D e1 T/ F0 M2 l <?phpclass upload {, q u! y! b# Y# k0 L. a9 w

" D5 a7 J) q% t* c

" ~( c4 ?% a, T! U* ^     function save() { - `7 U: _0 i+ a) v9 K' Y

# u* @* l+ S% T6 u

7 e Y8 O3 e9 |9 f; B         include load('include.lang'); # `2 v4 }" m: g3 S

6 s( x; ~+ I4 \1 N8 W

6 A$ Z8 A$ x' f& D         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ) Y2 N% p& G' @# k: R

7 _' |5 {8 d$ E) U

% |. r8 [7 x0 R# d$ N  ) Q; `% `* K. g# @- a0 V4 N

! V; m H$ k1 F6 c

+ Q' n) _4 ?$ X2 M# t$ s5 N' |         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');+ ?* M, C1 S( {; L1 V. j2 b

% t1 a9 W0 Y9 @) |6 f: C

( O, M$ y! f: p; l6 I7 g% W  1 Z- m- d$ I4 P* ]

+ B$ U" x$ k" ?. N* i. ~% q4 z

1 _4 y1 b4 l" C' y6 H5 I         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); + E: K' ^" T0 V. G( ]

( C2 u @$ e! e6 ]

' e3 V7 g6 a, Z- S6 {   X% L0 b7 J8 z9 G4 a1 h, l4 f* Y

$ ?' F- k7 I- g- y7 G1 i

5 v4 |. ]$ W" O; P* g+ _1 `         $this->set_savepath($this->savepath); \ a8 ^* |2 l( ` j

2 G# V, [# E4 ^9 L6 f: g

5 `8 X- l! ]- g4 g         $this->set_savename($this->savename);2 `9 [/ i# g3 q6 s1 N- i0 I

- s: _5 c; S7 F& w' U+ U7 o

$ `$ {7 D4 d" y1 \ R  ; O! ?& R" f/ D$ L: {7 z

0 f8 \; X: }4 h

+ E$ b" F2 [6 t/ t8 N9 m         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); . P0 ^; P( H0 C5 [; w' u/ f& D

$ b4 n0 n* }7 v3 c

& ^. Y4 U/ W4 m! P         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); * K- m0 r2 Q& f3 h* o& k

9 _& R% P$ ~3 g

2 G1 F- Z y1 R4 V         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);9 U3 b( q2 i7 k5 K7 }

- W8 g" V) s6 v! J

/ [6 }$ s0 B0 I) o! c1 @   " S$ X- c) r2 y1 f6 f+ `' k+ U

9 d# Z9 ?& W: E; @& Q

D+ R4 V8 h" e0 E         $this->image = $this->is_image();# d% E, f/ R8 S8 {

. a: F) z5 e9 k" J4 p+ Q: `/ G

8 m7 ^8 f$ k. O/ n; C; ]3 H9 F( E* X" I         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);5 k& T5 N( p3 x7 C; D. l4 f

5 O0 I. W: M/ X

7 c3 S3 Q J+ C: i) ~         return true; 6 ]- ~6 a% w& c7 m

1 B; b' Y; y& v6 ~- x# R

; E, |% K" r) n     }}. A' p6 b+ z/ K8 Q

& Z. C7 Q2 o( V# @5 D

, C. N' c W& k9 k2 V2 V 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: / u+ h/ U. z, s6 X' l- e: D

! _8 I- q9 ]! {) ?+ J1 @

1 z, V0 N/ l6 } <?php - h5 H% Y! H3 B3 G, v" ?

! V, g) G& f3 D3 N; K7 D- Y) f) Z

# @7 r) `* l* W$ I     function is_allow() {( @: G* i4 s; C9 E ^3 h" I# _' w

1 a2 B: [% C( r0 A7 r9 Y: K% Z% x

: u6 C" e" ~% R& |+ l( b         if(!$this->fileformat) return false;& k" s- U* d) E% v2 Q

* J. c( t. {' E- ?1 l4 F- c

+ [$ P: c$ Z; L; t- Y0 g         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 2 d% x6 e" F1 U) a2 ^

, J: ?/ Q- M+ ^" G9 x; z" _

j9 B+ g+ j7 z+ j7 ^! v3 N7 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; 9 {- G; B# j/ S2 L8 b

1 @1 Q* I8 A5 Z% a! I: o8 v! `

Z9 ?9 x3 f7 R" f+ A+ z         return true;- |; n2 U2 h' z9 U# i t! o9 V4 l

0 Q( [0 b: h. D) U5 k; G: m8 k

, o# `$ L& j, M+ Q. E! }     }# F7 C/ g7 o) A" Q0 X2 t7 S

$ i0 }! X5 T' R3 ?/ p* x; V

) f# V" b6 J; q+ |2 E0 J0 O8 v 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。/ C5 F2 `; o! @6 g; [

& ]3 \/ P) I1 S6 ?& j

/ V- w/ q% g6 p 接着会进行真正的保存。通过$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文件。. B# Y1 l1 Z7 x( i, Y

$ C" j- v& [( w$ d- O) _9 Z0 J

$ ~ G: n$ j$ ~1 T 漏洞利用3 s3 X; S4 c5 l9 H* L. m

7 J1 U) a/ Y9 J

- U0 p9 v+ P1 Q, _* A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 + N8 W t" N3 H2 s4 B7 u2 U" n1 R

5 Q- R( |& T/ m

) m1 ?: f: ?/ S" S) L* r6 M   2 `) c9 N% k+ t- O) ]" C0 R2 {; b

0 V( q, m5 `1 e% o

' r% k; C* i. |! a8 B 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 3 _' }1 a( {5 H

5 O, j* Z1 I3 X, j

# f- H( \5 s$ {- J" O8 F- W1 V 不过实际利用上会有一定的限制。 : r4 A- v: T- v1 L. ^& i# D. b: {

. b* M4 c; i3 _9 R

) T9 F; e. K3 X G# W 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 o. d% _2 f$ U" W) \) c

3 D" u0 ?& e3 T% H1 e1 o

' e" p$ j2 d9 F+ ^- A5 c   9 b# j! w, U2 F) n Y+ d

# ~/ Q" l8 m* {. [) ^: t

: F7 {* |3 Z( x* t) B% [ 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: z6 d* x/ P& f

2 c% q( @: S4 B' o4 M; L

& A) K3 A- u4 M6 v% p$ M 省略...$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]);省略... 0 k6 Y: f7 y* E9 x

' s7 p; p$ J4 d: |! c6 K

6 E# v9 S/ I! B' f 因此要利用成功就需要条件竞争了。 % D4 `3 I+ |4 D- M- |2 D. }# b

; E" Z% V$ c7 K, J5 t6 ]& |

0 M3 w. v) E7 k 补丁分析 5 ` z. f/ Z/ X0 |2 R& G

6 d. e" z8 c4 j8 ]

, \: F7 p7 q. ~0 q0 S! S4 F9 U$ F  * U! Z" F. k; O8 J/ \

% i' k Z2 ?7 i, M7 u: F7 S

* P9 ?/ P) I' m, m* s) z0 a+ s 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 8 W, u& {9 u. [. |+ J S

) s# e) k+ V6 v1 ?7 M; B! T

! B) F7 \ R6 e3 ^ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} , l# Q" J' ?5 @+ E; u! G! f

5 z+ j# u4 `( H) B5 F7 r

+ k: x( S+ g0 t5 q+ N  3 S1 @9 I+ B2 N4 R& Z) J2 o. m. J9 h

2 m8 O; Z5 N; W) n$ r% z

; R" v4 g( [3 P7 c8 B 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 * ^0 y5 a) Q! l1 D( ?

, s* H! U. t. B

6 C0 c5 s' [7 q 在is_allow()中增加对$this->savename的二次检查。& k9 u: p( d5 K) ?+ J' o3 l

$ {8 a8 S R. `* J' |6 m

6 e( o q1 [2 {( |; ? 最后 / |" C m% @. j4 j8 ]. h/ a. N1 N$ L

0 k' q4 Q8 |/ R: J: ?

. Q y. G! U; {1 ?1 R4 |& `& L8 G 嘛,祝各位大师傅中秋快乐! 7 r, d' H) Y3 U, c% H9 i

1 K- j4 v' J# K% {# D* b. I" K

2 q* o+ p+ Q' o( |   5 p# Q' ?3 U/ M" `$ _

+ Y0 ` X% s) n, x
回复

使用道具 举报

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

本版积分规则

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