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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
. I) o7 I* R9 a, e

( ]8 a& p0 |; \8 t% B

( z6 E1 \0 R- p- X1 n/ b* N* G; l

+ h3 }; U# v6 f/ }: f- v 前言 + W. H* P! K' k0 ]* N6 }7 j3 l

; Q3 }( x9 h& L) N3 m8 G4 x

! ?4 f; e% v5 N. M" i 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 2 F+ N, z0 t1 M; t3 Z# j# n$ U5 B

( K1 L& A# u" _

3 ` D: L4 W* K3 A2 ]# A* J. w: J& l   : _+ L/ D* N# F9 K- P0 [( r

% W& `, d# W; x ^) u" N/ ]

! I3 q) ~& `0 \& s' \ 漏洞分析9 E6 C+ n& n; D% n& W- \

) O& m( r. p# |" ?# J

# J a. U, l0 S( ^% m; m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:1 F* S" z" J7 e, O( L

7 k' `7 p4 W1 M" A* I

8 s8 k# h" Z$ H/ i% Z# x7 P1 y   , y5 v& z8 ^$ ^& M

* y" y ^$ d6 r6 h& k4 o$ V+ q. V# o

1 A% B- z: R$ |7 q/ H9 z 对应着avatar.inc.php代码如下: 9 l- s) l6 x; s6 c

5 i, R, g2 J+ n: }+ G* V2 s4 u

' Q6 S6 l o' {, E! y3 {4 w. T5 F <?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) {) v* q0 A3 g7 S9 O% `

. S7 G% q U; f+ J, `

9 t6 F5 m5 N# Y8 O     case 'upload':( J9 R) t2 b7 q- F

6 o& l: r( ]; p2 \$ S2 o

6 D1 H8 y. {8 j- C- C1 v         if(!$_FILES['file']['size']) { 3 A0 y# e$ Z7 d# ~% k3 `, q7 `

+ |6 v( Z8 e1 Q

/ M7 ? E' A" N; r2 a             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); P# _# T9 I2 g. p9 j& d, X

. S: \ l2 w' [

+ M b; K: b9 G8 C G+ w             exit('{"error":1,"message":"Error FILE"}');2 d( Y5 M& `$ Q W. F

1 O; @# n( Z3 \- i: [3 V: w

) Q0 c% H! Z0 u, ]; b) [( d         }5 J0 w' G* {3 u7 w6 c0 o

3 ~8 \1 K% U: e) `* h0 b7 j

4 }7 x/ s3 q% O: p" m9 `- H         require DT_ROOT.'/include/upload.class.php';8 T+ D7 Q2 h$ s7 ^# q5 @9 }6 [5 O

, z4 c) i1 t, r8 K9 f0 _

$ @( ^: {7 A; N  2 D6 l0 q4 E' g/ J% x

0 k4 c. P% J1 p

" P- ]3 z7 u$ ^$ O& q. d. q         $ext = file_ext($_FILES['file']['name']); ! F5 i" }- T# c( G

9 y. i, ?- D! ~% U" U. x

3 [& N S4 F* `% N8 f         $name = 'avatar'.$_userid.'.'.$ext;' Q# z, W6 r; k9 f( ^1 W9 r* X

( V& U2 C2 E/ @4 v) T y% G9 Z

' N. r8 H. u9 ~) h         $file = DT_ROOT.'/file/temp/'.$name; 6 {9 Q* d1 q) a0 I. E

) {, d) t' ~8 }- i) _" U$ w

. U" w( V5 A; O# |  5 {! x' |6 e! D2 `

- g% V# L, _' K$ s8 j* o3 H; B, s

7 I: M6 \% v6 A k$ C% R9 I         if(is_file($file)) file_del($file);& r& y, R" {8 D0 F+ p) m

% f9 I3 S/ c# r3 e% K7 f

- [8 u7 n @8 P1 |! F$ G( `         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 6 X- V. |0 n: l2 ~; E" }' J

6 Q" w+ H3 U+ U2 S1 I9 a: N( J/ V4 i

0 T: _ U6 q, X6 j# N   5 T( Q4 x9 |: h5 E% ?/ m( I& u1 b

4 U) \& g( N+ z5 I

# u+ Z, O A6 E         $upload->adduserid = false; # F8 Y+ Y2 h( W \% N

+ Y6 |0 i$ R+ o8 A2 F

0 k' ^- X5 @& Q3 A! `* ? l6 z  + v6 ~7 t3 ^8 S& z" P7 G# E" f/ O

8 x- s) \% J9 w

# _- a% T5 K j0 D. O& d         if($upload->save()) {) ? f: b) ?2 t6 G+ b2 G

+ C g3 i$ J& ]# L* y. C

! W3 S( C4 h) }             ... 6 w+ r4 C! v \- f+ h9 k/ T

- I) H* e# g) q

$ R: m& ?6 B' Q( a* n6 h         } else {3 Q1 W* i& c6 }4 l: D

9 C; z, n* M9 ~$ Q! U

D. G' O9 V7 b) O/ e             ...( l2 f, t7 M- L; g- L

% C2 ~0 d9 x+ C h

B; M5 p8 M2 V* e! l- h         } 7 v t' u9 j4 z1 H+ t8 r3 ~. k

8 Z& L, G. R! x" [- P& `) q$ A+ P7 ]

' R5 \' U% w" I( R3 ]0 s     break;: ~4 K+ l% q; i, H; c

+ x9 ?0 {) v, g2 Y; r

; }5 q L, K! J& V; v 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 , v& y6 T, b" v8 U

7 H% X8 Y" e; r: J

( A, v/ r3 Y: C* z/ ^6 c- o upload对象构造函数如下,include/upload.class.php:25:( `" u8 ]$ U' \0 t6 q

) Y& p! |" V3 ~9 @1 J0 U4 U$ e# S

" ?% \( m3 E6 D4 l6 C4 \* d <?phpclass upload { % h Q5 K; {4 w5 e5 d: K

; y: C$ p8 \4 u2 z; [5 e" y$ ^

/ A0 y u% k2 x; H     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 3 k7 E9 a5 u Z6 e \

3 D1 d. N( M4 M5 `" }

3 {* h% j R; y* J3 P) ?% D         global $DT, $_userid; # a& x) z& ~. O2 T) F2 ]

2 B' Q- Z+ ]% o5 n

+ X* t3 |2 F" E5 i: o         foreach($_file as $file) { % q h) s: B6 i# z* `

2 Z& j& ~! X8 ~" f7 D2 f

4 E T R# l& t2 S9 J) _% ]) D             $this->file = $file['tmp_name']; : o, c7 h" C; ^) i' |. l4 d

2 B3 Y1 E; N+ A* n1 z) s- m

3 o1 r7 i" [) y: ]$ v             $this->file_name = $file['name']; / ]$ Y0 @6 T9 i' P" a

* p# k1 ^5 i# D+ _7 T$ l

: y& S5 M g: m8 j             $this->file_size = $file['size']; ' j: d7 _ y( v9 p6 k

' ~9 {( @. K. Z& y

. `& F" G3 V( p8 }- c             $this->file_type = $file['type']; - `/ ~0 x8 }8 i0 C8 p$ d

& L$ |5 n! D7 f8 m" m& o$ a# v

7 |4 d2 V9 C& O& n             $this->file_error = $file['error']; 0 o( @7 f% e6 W' F& }

3 ?/ I+ o2 a: r: R4 A) [& \3 N

/ ~+ t8 U t3 @% A! h8 \/ | R! ^   ) }; V. z$ x& V5 a# Y' V

2 p! h0 r* E; `7 x, a

/ H# H. m% }& N, ?4 |1 M5 t* C         } & h/ b/ C& Y0 z, k! A5 Q9 U

- H2 q9 z) m. |; Q3 Z

( y( z( @% f! @. D4 Q; j         $this->userid = $_userid; - b" Y. o( M+ p- i C

6 ?1 A% |/ k. c" x

9 K P7 z; f$ {5 `+ c         $this->ext = file_ext($this->file_name); % G( w" k# L1 G3 {+ G, ]& R- r

a) x( o+ c+ v* H! q! f8 X8 P

# r' I& E3 S: U8 I0 _+ s         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];5 _, E+ i- h/ U. ?6 V( k& a5 s

3 Q: Q( e6 j: g2 \6 m/ f8 a

: m7 w9 x$ g4 t8 |+ u+ S         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; & M. E1 j S' c

, }# H. V3 a+ K$ O0 _) N$ \

( {; Z5 f" H! ~$ ^/ ]" k& c9 A3 D* s         $this->savepath = $savepath; 5 A7 ?' y" ^1 k( o. a( K8 C

% Q' `# ?9 s8 r$ T; Q

9 f8 w( H7 V- s. c0 i2 Z         $this->savename = $savename;2 b# [# ^* A& }. ^( R: Y% V

3 f) l" I' n8 ]$ A% A

' y* _& S/ y6 I M1 _     }}$ U8 ]* D' r# X* w8 D# Y

- ]- d9 Y; \8 @- `5 F

. G% Q* M9 d5 u( D 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 # X! T) g7 U% ^; H& E. Q

9 U& D# F+ A6 R' N6 a

0 U! g, `: a) r& D/ i- z 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 5 c! F) F5 j9 a3 M+ p

2 N! H3 D* V, ~. B6 G

; c- g# l, A$ N* _# J8 W $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 % e& |: r5 u$ P; F3 w

5 m. S2 F$ D' h, {$ R8 ?

* } `3 x, A! O" J 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 2 n/ S8 Z+ k, J; P Y2 p3 {

, w8 J, H2 e y6 o6 S' x( c: ]8 l

9 D- M2 e" }. X  . v6 C D6 v3 K5 U9 {# l, l$ G" Q+ }

1 p/ f! Y) h( B. d

" v) p8 A v& j6 n' b% ] 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:7 M# T& ^: S; ~2 @

* E9 y) J( _: P& F- ~

% y/ M. U* k/ |2 _5 M <?phpclass upload { / Q' T5 f- ~+ k

! M) J& q( E! |& u8 ?

8 {1 z" ^0 t R& d     function save() {2 s" g; T0 r! Q4 a

' D$ o& E n6 c6 X, g/ [

0 K' I2 f8 _6 H/ V/ P! y6 _: J         include load('include.lang'); . M; v7 E& i3 x L

5 P4 E. O& B5 a# v! [# W

{* g/ ?" P' \& c6 L         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); . Q" H D2 c/ H/ B+ \) Z5 {

8 b1 z2 N. l0 `5 x" ~- d. k

$ R( B$ M4 K; C' M1 M  . C. ^! L6 q9 s5 ~. W8 F% p

|' R( c0 {; b$ x

( h2 z( h% L" Z4 d8 h! s6 c         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');8 `* i0 j4 D$ @5 W$ A

1 i6 _9 o$ Y6 x; ~6 P' X

; }( B' b/ Z1 [, x9 S+ F/ Z   ( m# ~/ j! Q, I2 F

1 ` F! D6 N9 L

" l# P' ~7 q' E) G0 [; d         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);) S. j( N* _1 M

0 y; c# ?5 I h2 E- y I

0 W5 k& {6 H1 H8 d, R   5 I6 |% `7 c; @1 D

# V& h* [9 j A3 t

" K( a" D. G* q/ L8 k6 Z5 S, R: i         $this->set_savepath($this->savepath);! j6 ]4 _3 L7 N$ O5 I. Q% U( P- }

% `5 }) V+ L2 I' T

6 d7 |6 ]: A5 R- S         $this->set_savename($this->savename); ' _- T4 Y, U$ P2 Z' X

4 D- {1 F1 O/ k& }" a% K" C7 H

. k8 i- p* z& [$ E1 |+ u3 V$ f   8 u! R! b+ \, p" S, L( Q

5 B9 r: b: c6 x7 l

5 f+ e$ W3 c0 s) {         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);1 q% _% g: u. s5 Y$ e; Y

1 l, ^2 L8 F, C4 i2 r

+ l4 w. ^. {8 K# K2 k         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);. y0 o& f; h4 U

! F( R3 [, D0 T+ F5 D7 j9 |8 j

' N M* I5 G4 K6 i0 G/ u* O         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 4 ^/ }& J4 c/ k3 n

: O9 e+ t+ d7 l8 X0 u* w$ P

( W8 v7 k# R0 e) ]4 w  / Y' [2 r0 e p4 R* ]8 J

. ?: D J6 z% a+ e4 h( U

9 ~: O) U7 z/ o- @         $this->image = $this->is_image();) \5 X* \0 N0 N7 z/ A

# A0 y! f7 l7 b7 w- k

# K4 q7 O8 @6 P! ~         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);4 ]" }- u8 ^- n# X+ @2 M3 I; u

: V2 t" P: j( n( p: ?0 q

" B+ b- ]* Q7 e0 G$ O Y8 f         return true; O E* D Q- h/ o+ U% R C

4 {' l( [5 ~0 y7 R$ F' N0 ~1 J" X

, N; Q+ W9 {$ ?/ Q7 Z( \, w$ n7 L     }}3 ^9 }0 U# ]! ]

4 ]; x& r0 y& S2 A) W# h4 f: _

2 w! t5 _8 Y$ U+ S# | 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:/ ]* p1 m, t1 ]/ [% j5 [- j

3 K F2 |9 V+ K5 m5 L

! l: | U: | v8 v) Q$ w4 ~ <?php # x3 X- H! \) v

! }( E- l* _; M3 b" ?3 f) g

" Z' a) |4 i2 S. g     function is_allow() { + p* U- A4 b: l5 q* G) I

8 V4 G; |, Y" V4 `* G1 p

4 M4 I$ T8 k5 s2 a" X" B# [         if(!$this->fileformat) return false;8 Q( w6 p0 e" _1 E

1 k$ q) ^! C* q

7 H# F) C0 I1 d1 x- T$ G0 ~         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 2 C9 D( N0 D, [: Z6 m

0 n: C0 Q0 g. Y5 u" J

( ?$ L/ t4 t* S" ~         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;: I: |( ~4 A7 ~. Z6 n

1 m* O% f: f, j! u- L3 I

8 O- }7 H- n- \' C7 D/ p         return true;" \! p( X* T' A6 o

* E+ P K) t: `" I

5 g$ i9 G2 s& @3 a& A/ w     } ' L' [1 i9 A$ @+ l& |* C7 }1 B

S3 `: J3 J' w. b

7 H3 h& Q6 M7 ^) o 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 : s* r- W+ L9 l+ g) ~0 ~

, ~: b, V/ D! d) g

: D; i) d: ~3 [ 接着会进行真正的保存。通过$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文件。5 o8 _ ~8 k4 p6 S: m' l* \5 x

9 W* o" d8 ?* R$ r+ }

6 b5 s0 `# w/ k/ Z 漏洞利用 n8 y* S9 b; S( c/ k

- @7 b9 @3 m* Q/ m4 y4 L( [; X

4 I" v- d( X3 e: x, T 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。3 u9 O, J- ~3 x( E( t. ^

{2 I) |* K4 h, ?: I K

# g$ y# ~# s+ r2 v: [5 m, n4 X3 g   Q* r, P( J+ |# c) w

% n+ p; y2 p$ I6 f7 G

; f/ f' X0 G% @; y 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid : d4 q* ^" I' C4 _: `. w0 }

9 v- _9 R+ L$ J* s

6 k/ z3 c( N3 K6 f q# A 不过实际利用上会有一定的限制。 9 k, |5 g4 J$ ?2 ]5 |

6 X1 ] V) H9 C6 i1 v/ v+ r

0 ~2 o( O6 P0 Z7 y0 S1 R 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。) N5 }% U: E" U! z( y( e

, @$ z3 P$ a4 G

4 J: E# W7 U) P5 s  9 _( R7 j6 J9 v. k" r) U/ V5 ^6 Y

1 S2 ~) b! I+ `) }5 |

% s. Q/ v4 X7 z! Z! G 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:; r! I0 v9 g; f0 @7 L, ~

f7 j8 }' ~. W; p0 b( F

' a# J u: C, z$ M7 r' Q 省略...$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]);省略...' O% e f t* U8 W! V' R

. s8 h' i' D1 `4 O7 D7 e

# W8 I% e7 `( O8 G 因此要利用成功就需要条件竞争了。' W& \. U& h, i8 C% J& [

7 {2 ]$ r* j6 y6 \: @3 |

9 ]; n" X7 H: d1 S. h3 a- a9 w 补丁分析 0 H! V J6 b& t( F( x2 v8 G/ t

3 F6 l& Y, K) Q

" W9 i' a, l+ k9 N  , Y* I( N* }+ U- d1 {! g7 ^. I

' [8 L' ]# n0 K4 l

4 Y/ v/ @& D; h8 F, O S 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: & G) m. l# u+ S. k! w+ T

. M* Y) ~3 r5 [* j

' b7 e3 v: Z) |$ p, y- _ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}# \8 ?( l; y5 J- ?+ F

1 M }8 I! `' k2 p

3 H. g0 c9 q% J# W+ Z, c  : o9 e8 M7 `( t% H$ ^

3 p3 A5 G: N7 n2 c8 j9 C

4 b) [; I! q# m4 f' u2 E 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ; v5 e/ H& x; u+ o9 ^/ x

% }0 N* n, U! Z, P x }

% } t9 v: J# `% D! k$ j 在is_allow()中增加对$this->savename的二次检查。 7 H4 g4 k! G6 |$ P( N

5 T2 x: D# a4 b3 @5 o

! H1 [3 }" ~3 k4 u' I _ 最后8 w" c) s2 Q2 C( O' e; j V! Y

& r7 z+ G, A3 e' y

9 I3 ` @6 H( ?! N) z0 t- W; m 嘛,祝各位大师傅中秋快乐! Q8 M) f7 f+ z% Q0 o

# p) X% H) q. m) y: `9 Y

3 t% M* C; R8 D/ f! R* A   . ]. n8 }6 X/ g4 P' d N

" R1 U, ? {4 B7 G2 g9 _
回复

使用道具 举报

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

本版积分规则

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