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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
8 ?4 i) A- k: J0 g9 \

7 R7 `% [: l# N0 q2 x; C, a

9 U# \8 N# V& Y8 @* q, J4 a6 X

; `: I5 w. Z; e9 r* t1 V 前言 2 Y0 L+ r% \, O! r

! D% c# D4 r- {1 p% t# m

( s% {0 a2 h+ r- z 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。0 S& n' F2 ?0 J5 X

9 U v( p1 [4 i' ~- O o' K

+ `0 D% {# K: S9 _% V: f* u3 }   ; H) ~5 M( V) @' ]' M

0 a, c! C# B" x& N/ Y$ k

- i+ |3 o( z5 O 漏洞分析 ) U+ C! R& J) |1 k3 u U: f& y

" x, ?2 j. k( A1 s1 g

" [& g ~$ A' B 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:, ?! I8 U4 U( `' i; m

- i; {# L8 I+ M" X8 w2 g0 g, _

, B9 p' F' v' f* F: Y# i* Y  4 a& D0 v) W) S- S

$ ~; p! ?! E$ B- X5 r

E8 U2 n$ t/ @ ?6 P3 T7 E% k 对应着avatar.inc.php代码如下: 7 p5 y' g) T9 F

( w- z1 H& F/ \

$ {- D% p, v Z% M* a- B. \& L5 D) L <?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) { . F0 }$ n" r5 q; T& J

/ L0 W/ _( B( `3 o1 z0 k- j

# z4 ~, \* c# N: e( n6 A     case 'upload':4 J) V6 K' o$ k/ n/ O

6 k0 y" |! E L( ?( \( E

$ \- V9 h; K* t& L- ^! i5 W& p         if(!$_FILES['file']['size']) { " E; |4 O) R- X# ~8 ~/ z% Y7 I

4 G7 G2 l* X$ u- F

) o0 k- M ?* C! W0 W5 o             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);" Z9 |6 `+ x" M! ^

9 U9 i5 P' F) E# _2 O7 K9 K: E

5 C e' M; a6 T& s8 O& e             exit('{"error":1,"message":"Error FILE"}');: B2 Y; b# x& `, p$ J& T- k

* j- D% D9 c, }; o1 S4 A) e* V

( G6 I) z" J( f- }         } 8 u5 x. F. |5 o8 H% X9 e

* D% c' o; U: C6 f# P+ x" t$ C+ l

8 f! W: L9 B0 O& F9 x         require DT_ROOT.'/include/upload.class.php'; 9 g8 y- j, L1 u# ^/ f

; O/ [/ v/ S9 ?7 B7 X

/ b) q* c2 |9 d4 V  ( d* l0 m* Z+ U- i+ q: O+ E( W

' d" d! K/ b( S) R2 `% M

9 O( U- O3 s% X! a/ h( h         $ext = file_ext($_FILES['file']['name']);# u' R/ w: Q& p6 O( p; M/ {, w

$ S& V9 [( a: A

" W) V% |) N: }/ n3 u: ~         $name = 'avatar'.$_userid.'.'.$ext; - Y8 i3 ]; j; ?' B( l

- C6 I5 N4 t; e* p5 `# [. ^& d' n1 x

8 L5 ^) V% T* R* f, j! w         $file = DT_ROOT.'/file/temp/'.$name; & J( h; O$ S: j. t* b$ g

$ Y f; v4 S& f( B" a- a

% h$ k1 k" f4 S6 d   / x3 o) t/ v' f, z

- f: W" ]+ K( x' @' u

9 u# I: k& {7 O$ \         if(is_file($file)) file_del($file); 6 {( }+ |- y! W y' d& X

0 n6 s3 v/ G+ M- o+ `. {

# ]6 p) W1 ]2 K) K         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');6 E: F9 Y' ^1 u. z: G

+ y$ n1 \0 }7 }0 o. X% U. P

2 y0 Q2 w& Q. @+ v- U& P5 I   : `% P: c5 ?8 g* G$ g

8 T0 j8 ^1 g9 x) w

" y2 R- J7 [& }: I4 B/ f# f Y. s         $upload->adduserid = false;% m' }, \; U: s

3 _/ t |7 n6 d6 l+ G6 d$ u& A4 z0 `

8 p- b2 A; i4 o" P  / Q& U( b! C0 ?" h; L6 h

# Z2 i" G% s' {+ N. _

# J# q7 Z( q1 o+ R4 @) C5 t# w         if($upload->save()) {! x* _& _0 l4 h; i- X9 Z* b7 s3 A& [

" [/ W9 n/ h" a5 Y: E% D. a

, ]) f) p# d9 o% r5 V+ d             ... ! h; c# G1 o* A0 {, d6 u* q

$ H% h3 j S5 |' v! B" ~ m8 N3 h

# w" x4 E% U1 a         } else { g, c. L- S4 o0 U' |3 y! O

4 U0 O. A& g9 N+ i

- p% o6 ^. e) R$ \8 p: G             ...! G: ~; U2 @/ {' r. x4 Y' d" L. @

9 e( _ J4 r9 }. q( G' {% V# Z3 N

$ g7 ?' A2 q; x) e5 X6 @         } ( c# e* X3 ?# N, Q( e5 J

7 `+ U8 k( i0 R; R8 z, S

8 ]- x& i% ]3 m E- a$ Z% W) Q* e     break;6 V9 h) ?7 ~* D% I( x: S8 u' D6 U

1 X) K% ^, w, m3 ^# O$ z( m6 C0 @# b

6 Y7 v, T& z2 w" p/ T 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 o* Y4 B' i+ y5 m; `( D

5 R1 S7 @) ]! t0 H( V' ^

3 q7 b. O: `4 n+ q* I! e5 A upload对象构造函数如下,include/upload.class.php:25:% H5 S/ y/ a& r

6 B% t: [- |; L! G& U. D' L" a

; \' q+ J9 |. n9 ^8 N- ~ <?phpclass upload { + p$ g, g/ P1 E1 f K4 k' E

: c# e! a) e. [: n* [; g

/ W1 l/ L( M7 z4 y/ d% v. [, Q     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 4 ~' a6 M, H' C- P. {0 [

; \+ ]4 J. H' y( M1 p% P3 |

- m1 e- T; |3 l N/ J" a         global $DT, $_userid;/ ~2 w. [5 B. [9 G% G

0 m% N Z/ P- J. w! y- y N+ o* s

4 N1 e- V) O# C- g m         foreach($_file as $file) {: z8 U: f% d6 \: J$ c! N

# V6 ~$ G# G% e/ [: _- A+ z0 d

9 m7 D+ n* h5 A& T5 b8 k& S/ q' [             $this->file = $file['tmp_name']; ( y4 S" F9 ]3 L3 |( F& d2 m

) D- f1 m* `+ v. g7 w; Y! o4 s% E

3 g' P( b. Q( V4 x# ]/ w' ^             $this->file_name = $file['name']; 7 ~7 H& a- M, W1 l2 n, ]

) G! A; I6 _7 A6 ~$ c6 g

3 e, J8 m) B: s y             $this->file_size = $file['size']; # F* ?7 e7 `6 f2 J: z. Z7 v

' p6 f3 x% d0 x7 ^

! a. W. N+ q9 q             $this->file_type = $file['type'];9 j* i T8 \6 Q& ~4 y8 R

2 X3 b$ j: G2 i3 b' d j- P

# c; Y2 V5 A& F5 s2 s Q! V             $this->file_error = $file['error']; 0 `* S7 S( P! ?7 J4 P4 a# c

: a3 d! q _* f# }

! }, @1 k# }% G3 ?+ B' `  ' k3 z0 }2 M4 J5 p

2 \ s% P! J& {: M8 d3 i

x: `$ m) s' Q2 a         } 9 v* g' `' ?7 c% I H+ n2 w: T7 c

% ]( }+ l. ?% ~: ?1 |

% ]2 ]9 k5 _# Y+ p         $this->userid = $_userid; : E7 ~- V3 n! h

0 g, {; Q0 ]* W* O

" A, x) w0 |0 x/ c7 L. @         $this->ext = file_ext($this->file_name); 4 R! s% z1 p5 u9 S. l8 j

# s& l1 D0 I$ a* K* S7 L7 G+ Z3 o

, m6 G4 ^ u" e7 r2 \& }- L         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 3 K5 _ d6 M6 x7 W y

+ [1 B/ y" ]5 x* @# L. \4 p

$ T7 @9 K/ J! |# v" g! G         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; : a3 r6 n4 q! d. R5 ]" e P

) _. q9 u m' _+ }6 w, M ^ Y

^2 U9 C4 i$ `% D6 M         $this->savepath = $savepath;9 J. Z2 W( o9 k$ ?" `

3 t" j2 b. y# X# w |

1 G" u' S, g8 E! @0 N         $this->savename = $savename;0 f8 Q7 l! A: z C2 L

& w2 o% r* g3 _/ q1 m/ W8 {1 e

: t7 Q0 ]4 Y( l/ @- u9 w     }} 1 N: \$ Q, a) t9 s. O! k6 {

+ u) Y9 S3 e$ W: F

1 A; [5 d4 E( O4 S6 C0 x" m$ _ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 * b: P8 x& h/ A- M% s. T

& q% X4 D, M: h. Q( V

) \0 g- `. ~% _! S1 N" J" @ 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ; Y- i- u6 x; B5 t0 a

$ Y( E2 X0 R9 o' W+ H" U% |8 L

& c0 Q7 n6 c! W, ?4 `. v+ \ $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 D4 q* f- u- R- b, N8 u% ~" j

9 J! j% O' q' n+ S/ O, u4 w( c: i+ p

( Y0 X3 @1 ~4 Q) k4 E. Q 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: ; F) s, h% Q/ C9 \( }( N- S' H

& ?+ T2 d6 B7 G% k- W! r

& n9 o: Q5 Y* T5 W! I! \  5 F: V. S& l6 c9 s i a5 K2 z

% Z8 T& m4 Y" n

8 C( g) h& j7 L! ]7 Z 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:% Q: U7 \1 e( \( F) z

8 j) b, d) l2 z% P2 a9 M+ S. H

" V. X, Z; h9 v4 O7 Q/ ` <?phpclass upload {( g9 }% t/ u, M+ K" d2 `% I

! Q0 A0 Z' i/ z& ^

0 ?/ V* B- Z4 Y0 S6 f     function save() { - ?! r) l( f2 ^1 N# P0 I) e0 G/ H8 v+ ^

7 I0 _% G9 A! l7 T" Q

+ H" [6 Y% U* @4 n: K% c. k         include load('include.lang'); 8 v6 t$ Z2 x8 M. X8 E

# r, t$ |' ~3 c2 F' h

/ N* M3 K$ P% [& w& O3 `         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 6 V+ B& I5 z2 O4 Z

" N7 u* F* r* ~& I6 @0 ~

9 `' V3 H! B+ G7 j- u   " a5 m9 @+ E: i' E6 u: d5 U

: u8 {# i- R; a: L* W, R; k

- N4 K7 K$ y, p         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');' a" I) Y1 G- h8 e2 M

( G0 z, Q f5 ?( _- s

. e! ]2 J! l6 f7 a9 w; t  ' O$ v( z9 E# u0 L. a3 ~( @7 }

. n) q* E& n3 Z" A$ P

" a3 f( K+ T3 x( G/ X) j         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); * q% N6 R, J& x

1 b6 J8 s+ r" {5 u7 ?; m

$ B" s/ z4 M' i( j# p  " e4 B+ ^0 n6 e |

2 H8 J* S, h. k* e9 b; f

$ L" |$ P7 i% x6 J7 H9 K1 Q) N         $this->set_savepath($this->savepath); # n. D+ x9 ?& L$ v5 {& m# w0 g3 O

. U5 X6 S/ ]8 X9 W

: u0 j( W% ]% E         $this->set_savename($this->savename); 1 y) \- _6 `# B Q

* t$ p! v4 g; H! j1 o0 j

" W+ }% M2 s% C1 a1 y   ( T# `4 A5 Q7 g, ~" U- `

. P2 @- I) s% @ e% k

4 x& f! Q! o) Q0 W- A+ m7 T         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 8 }: ]3 z6 g4 @

$ K2 D s* C- l' b! h

# \/ y; F* M8 X3 ?( Z+ C         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ) d _9 N% ?8 c7 X

9 l2 x% D+ ` i- R' v# I& c0 O* [4 k

* a* y3 E# k+ u         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 2 I- ^$ N7 E2 y

% i2 O6 h8 D( R

* E& [# J* s5 o) ~# H* x, h   4 M1 I. u3 C6 I, i! B

) g" g; O1 e Q& c8 Q

4 k& y) B- w' B2 H. u         $this->image = $this->is_image(); 9 g" ?1 D9 J5 k) K5 o

6 O& \$ O( A9 B' H) N# d

1 q( T. B; T4 v3 }* k+ N& `9 P         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);9 M. G& T0 F- ^* Y0 \5 R. V- ]

' B' p" G7 g* e: L; x

2 W( V1 {- i) D0 ~, W8 R7 |         return true; 2 B3 \2 y; Y& O

1 {: o- C" P# c! O% o

) S$ U' z3 [" X% Q2 Y1 K     }} 3 h! L' A9 S: x# f# a( J! `' m

6 U( l9 @/ ^. n1 z" r

! A1 [& `8 i, A 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:* o: z8 f% i1 V) d' {1 p9 s

' l3 [% A) G1 d8 t- V+ b% k# a

2 G! j8 L, g% q: o <?php2 k4 ^ x' l- b/ T

/ C7 W0 S; N" ~/ ~

( h- r, [" b/ U; X# I     function is_allow() {& v- s5 j% r. `3 I4 Q

% p2 I' y# n3 {) I& O' J) ^

4 R4 D% R5 t* Y8 Q8 Z0 Z" Q         if(!$this->fileformat) return false;2 k1 M* H4 d) F2 c. B( ?' I

3 j/ c* l5 L P: d$ t& m9 c

: L, K' P" e/ }# g- k         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; # R' \% w5 L: K2 E4 D3 r: G

1 ?: p; A' R+ ^/ u E( T

8 I) y! {0 Z0 `5 x/ j4 k$ ?         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; / ?, c. p) X u- @

" {8 b, r. e& n( F6 g1 C0 r

2 u6 d. p- y, q0 ^2 F9 f; o4 k2 L         return true;( F0 _* Z( E4 n2 h

: b# ^0 D* F' ^) i

; h2 ?+ Z M3 W9 g! H4 k. u* N     } / f- f4 K/ ^& i+ P0 |1 f& g

' T, x7 ]6 P& D- `4 K& ]9 ]9 A

+ Z' w, O$ P! j- k6 N 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。+ }3 {/ e9 h( e6 K ^

4 z9 Q' a' P( N2 I! r

/ N' s4 o8 ]7 C# H 接着会进行真正的保存。通过$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文件。8 R9 n4 d$ l6 k) a! o2 P% E

5 m4 X' ?; u$ |9 Z n$ |

7 S. L0 O; k v: N( \" G& X) R 漏洞利用( ~6 i; e& s1 H- r. o# B' d; Y

: e; l, {8 O; \3 y5 K) ]2 E

; C* o6 Q# F& N. D 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 D3 y/ E8 c( K: y

, O: Y0 d: O+ M

2 q, X9 b* g$ } |% G. E: @   6 a) c0 ?0 M- E: M( r/ b/ `

% `5 |4 M+ X3 v* K A% O& h

" r4 L( z" m1 p* Z" V 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 5 w* J5 [4 h9 V! ~- Z

4 @- u( }/ C2 G

1 e, ?; L1 u2 o9 k, I 不过实际利用上会有一定的限制。/ ?+ h, F0 t. [7 X1 }9 @' y

6 S% Y# o, C) Z5 v0 e1 ^* f

5 a* z3 U# {0 ^, p' {5 ] 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 / h6 N4 @( X1 `+ n2 y- U

* Y& q/ s" K; Y8 L6 S0 \

6 X' M; a9 \/ q* ?5 K' p   ; y; O3 D2 C9 R

: u! B) n0 \( Y

/ Y3 S4 S2 K0 d) \% ~# M 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:) G6 i5 t" y- q2 m9 y! I! x+ b8 J

# Q7 ^. z* ~# Y* ~; r

4 D2 X. G+ V6 C. ^/ M) Z+ F5 } 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]);省略... 0 G7 R+ g* b( B, e) i

6 s% W$ k. w4 e7 N! e3 j* s( Z# |

0 O: o7 y* ?4 O. T2 T 因此要利用成功就需要条件竞争了。. f( G8 M0 |& U5 y5 U( ~ Y

, i4 m) O, K" s% B0 }! B W

( g% ?' \% S8 o$ b 补丁分析 ! H# b. w* Q8 d3 B/ ~- h8 H

: n- b5 W! ~" u) }0 S3 c

* f. y+ G' a1 [. u; L1 @4 l  3 }- J7 m! U! P0 C7 z

C z5 k6 k8 s* }

! o6 F- Y! R; C) V, Y) D 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: ; o2 a% p& _8 _1 N; j( C0 [* }6 N

' y6 y6 Z+ y3 h" [1 p% [% d

; @* X* U" a8 P- b% n function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}) n. J1 C% ^( ]0 [# D

9 m! Z: w4 U1 |) h& [6 `- ?4 D

5 \8 D& n% i. R: d7 R1 D  : n J f9 z4 H3 X6 r

" H q0 l- I8 R0 l6 U% ^7 A

8 H9 |2 h8 V) ]: A2 m0 ? 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。% t; o; y' s( {$ `

. n1 [6 s" s; e9 T7 C" M2 ~* v1 l( Z

7 f! Y+ r+ S1 r; Y7 A5 U 在is_allow()中增加对$this->savename的二次检查。' _1 Z% `6 T) }& I. U* s$ ?2 ]

4 c i+ A7 |% P& c: W

. S: b8 H( ?) I! ]$ [, h' N 最后 ! M1 M! d# z" W" r7 F1 }4 c, m

0 h N6 i4 ]! x1 g

5 l6 {8 q% u" [8 v8 q) F& ] 嘛,祝各位大师傅中秋快乐!# e. ~: |" \3 O0 z6 G+ o

* }6 U- {* H! n

3 M6 k8 \5 ^2 A   / D0 W0 s' m( {" X+ P% y

) S6 @6 |" ]; `, ]
回复

使用道具 举报

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

本版积分规则

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