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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
$ H" ~4 Y) d8 G, @& C3 a- H

2 f0 y# T! q3 x& l, m7 X4 b

) g/ H! q1 d* g% e9 [* |* G

$ {8 q% U* N, H8 z5 f2 r 前言; z- B3 F0 j8 [9 i/ O) J4 Y9 W0 T5 R

5 r! ]; s+ M8 [3 F# `" B

5 F9 y) N W9 a/ K+ P5 W1 z 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 8 c3 G0 I4 N8 |" R7 W

/ ^9 I# a2 B+ O5 q4 |( m

: _$ S1 i, G/ p1 y; k- K  + B" j8 t/ P7 h4 P/ Q/ {9 x2 w

3 e# c: A/ a, {( ?' g; d$ |

! d; O, F6 w) L8 G 漏洞分析! g& X# S" e" }! b

8 R" {# Z7 M5 Y" `' |3 s: H

, m/ a i0 W4 V% M 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: ^: s: M9 w+ q! w3 v2 N2 t# u

: ?( P! e4 A/ L% }! c

]4 C8 \; ]; q/ g* G. M F( I  ; f6 P+ f h" ~+ t% `& F& H3 e

* g2 p k' }$ u, x% d1 E

& q; a# c# J% o3 m# x4 k1 C 对应着avatar.inc.php代码如下: $ E, l( \6 o: S( O& i

& y# W, }$ _* g0 x+ b

; b' y* \& H; n <?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) {" a% u- n; B" `* Y

. G. F2 M+ N7 }/ M

}! \: N; Y8 O; ^& C$ a! J     case 'upload':% h. h: t+ D5 Z+ X, z

4 B: i: i0 M2 A% P' R# q0 T

' `# Z2 d' x" Y8 j- ^( y         if(!$_FILES['file']['size']) {, @" F& W) u- b% @- S. x) Z

0 E% Q4 {' o; Q; k

5 S# x; H6 c J+ o             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);& w+ Y/ s% n6 R# L" v1 a

0 j9 g! S; L6 _# A8 p

3 U/ |. }% u4 D! N5 _( z             exit('{"error":1,"message":"Error FILE"}'); ) v6 w8 @) v$ q

7 G" u$ q0 c: f0 {$ L/ @* Y

; D* O6 x2 |2 i9 V. U         } 4 B; N6 R `& N7 w( s; ?. I

* t$ X1 u: e' S1 n$ r+ I5 Y

9 s7 J5 T# N% C, a2 ]. _$ M, Q         require DT_ROOT.'/include/upload.class.php';7 m' s9 g$ M( d7 N4 }# J

0 t; B7 i u. ?$ {9 k3 S7 j- G- X

8 w+ y6 Q D/ J6 ], O! m( F0 v0 N   a. L# i) V0 w' G% K# K

; W$ \4 ?" w9 Q

+ Y$ Y6 Y0 v/ w- f1 u% j$ w         $ext = file_ext($_FILES['file']['name']);0 U, \% ~( k; k6 C' T% e, ~! N

2 x1 C5 Z. @- Z0 _

& j9 U. ]( C; f* ]: x' H         $name = 'avatar'.$_userid.'.'.$ext; ; `: q) @ J7 z2 E: \2 |

6 q% k c6 j! i1 h7 I# M# S

: Q1 v ]! \2 D7 X P7 y* _* x( j         $file = DT_ROOT.'/file/temp/'.$name;' O& f% J$ y, E- t4 Z

. ?$ j6 R7 u2 C$ q! A; d

( A- {6 q/ B( m7 b7 ?   5 n( w1 w1 \5 s" g8 I0 {2 P

- |( x8 P0 i; Y) F: x

+ Z! X; R" O. {1 \         if(is_file($file)) file_del($file);& E0 \4 R7 S Y( H U4 v/ u8 K' M

: a! O: m& _; K6 _

! e( ]( c0 q5 I& J. t3 d" a         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 2 S% T1 Y0 z: c

$ R- i& W. _' B

2 `. q6 n6 w7 x- b  3 U! i$ `- x3 y) O G

1 ^2 l4 K: \* o& h) ^. O

( X: }- z/ s, A3 H, ?$ y         $upload->adduserid = false; W9 K; x9 W; B8 [. s& P

- Z* Z" {- q1 {6 G7 z

* x) H( Z9 s' y2 Q% r  7 Q! F# R! W% l

( p1 X- N! _4 A: s3 a8 j, U+ O

8 y. ~3 w7 K/ ]4 i: R( B         if($upload->save()) {0 Q3 P0 l( a# s* p& e

$ K9 s; ]* t, I, d

; N8 H. s* K, I8 f* K             ...' p- R$ w" {* r$ D2 `5 G

# J6 `# z. {7 O

. E ]# s1 _" v         } else { . o0 N+ v+ W- i" F: G' X7 b ^2 l

1 e* G9 S( H& v/ J- T" ^

/ ?" @) V3 K: }% e$ L6 k) Y3 |/ D             ..., Y! A/ K4 X1 ^( ^! U/ y" n/ Z3 G

# w8 z, j; m) c

# n% J! m. ?% o2 k' h$ w% s         }+ J$ X6 k, ]$ E# q

$ V* a: C7 t( d0 D- _/ B

% a: H9 n5 F, o3 _) u( F     break;. v3 N% P0 O+ y B5 m* S/ i+ p' o! T

; `6 K1 i' r. k. V' c2 I6 ?9 _

/ W( o- d) j8 U, F$ i \ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。) _! L% c; v; C1 E. ?& { ?* q

3 X4 c" A$ ] Q9 {

7 _- S% ]0 o. J; I0 f upload对象构造函数如下,include/upload.class.php:25:1 g9 w2 V; F: t m) h5 e4 a' g

7 Z+ r* T( u+ Z" n* {

$ J8 z, ?& {: V% Q4 ] <?phpclass upload { 0 o8 U; U! ]+ U! h; |+ F! ]

; n% [. o- x+ E9 ~+ p

" w( q- u2 J* d# J& R5 e$ q     function __construct($_file, $savepath, $savename = '', $fileformat = '') { t5 m0 z, ~7 O1 d. E- ]

- i+ e; i* s5 I

2 p6 ^, `! D5 R( ~         global $DT, $_userid;/ k+ Z, M5 M) u v4 F

- Q+ X; b4 l# K0 c

8 B9 t& S& E) e; [3 X8 X' n3 F         foreach($_file as $file) { $ b) D$ E$ k0 |

$ h. D6 S }0 W+ d3 J

2 D" z' b4 S5 b1 i& ?0 o             $this->file = $file['tmp_name'];5 C/ Z* G1 d, z

( j* r* {9 ~( U

0 b# f% n2 D9 t) c0 Y ~5 |             $this->file_name = $file['name'];# [/ p! c9 x6 s

0 O4 R& N4 B. m1 I+ t; V

$ k+ P1 Y2 r' |1 B             $this->file_size = $file['size']; 6 h% W( b$ E$ P1 o/ t

! H0 }0 O# ?, G% d/ K! C4 d! m

+ Y- g0 \( k+ W+ a+ P* k% U9 w: U             $this->file_type = $file['type']; t% ~) X6 Y7 [# A( M5 a

: b/ f4 F. Z; Q6 Q

4 j2 L6 N* J* J0 L5 h) a6 U             $this->file_error = $file['error']; ( G6 K$ w2 | h& {# `

/ d" Q4 M* n# q" W6 r4 \- D

5 N7 s2 l. e' V" R   $ Y3 p: ]6 L+ g& w A

6 G3 L t: p( f3 r7 @: d

7 X: d# b" |' Q; o/ }9 o8 A         } 9 K, z0 o+ j& s1 p; N2 W4 N4 t

% V4 W3 g" z5 F1 F

0 f3 b/ @6 ~- S, x- r( N         $this->userid = $_userid; 5 Q/ @* c. H/ |: i

" f+ Q' Z6 K6 P! C( Z' U8 n

/ B, V+ t' S+ [2 j8 A0 n: T8 L8 J         $this->ext = file_ext($this->file_name);1 @ }) r+ _+ P9 |6 M& A: y

0 T3 E, w8 q3 X1 k$ t- k

) N" v+ W+ n8 L- }1 V2 p( P% L         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; ) X6 J# i2 M8 k( m% i8 o* g

& n) h( i. V+ K8 U

/ x. x; W: o I( z         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 5 R( a; P' _( Q% t

' ?3 f& ~& x6 H# F

' O( U5 b% v0 z( o8 n( O' N         $this->savepath = $savepath; - w+ R% X) c' w/ \2 k$ r0 g3 I$ r

W/ t1 J* u8 v- p. O6 g

# W( T2 A% i+ g' E         $this->savename = $savename; , S# z. A/ X v! x5 J$ J

( e" i" k6 e3 E, w* S

7 O0 k# \) y8 p% e, B8 b1 ~, U     }}6 \2 P7 X9 |3 G3 ]9 t8 c, }

' ~& m8 R' y" ~# p) M0 c( S9 U

{ x, r- \7 h4 d2 ~- ?. E7 h 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。0 |& g2 y4 d$ [5 P. d

8 Q: L i* Y( H3 I) d! X1 x) i

* i* s2 J- w9 y! { 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 - a+ G9 |! R' y" E. K

! O; O- B3 m" d- T' V, o% t

; r e+ J4 ]2 Q; c4 p+ u+ r8 I' B $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.php0 a6 ~8 f1 h' O+ c! J2 Q- e1 g

& ]+ I9 ~7 J* d4 C$ T

: q. K9 n) W1 E( q% |$ ? 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:- |- k N% H9 f, d* |

: _# Y/ m! Z9 Q' }& B: z1 X

- p0 i- }% M8 }1 D% r6 r5 g0 b  0 m1 D' k! K) G- U& t; F# x

! F/ P5 c- J4 P! ]

1 L3 J6 b- U( }( U, b( A 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 9 D& Q4 }# M6 d2 Q

" G8 D* [9 F" }

0 p' r( D' i( I& P4 U <?phpclass upload {9 |' A7 L" c3 p! @+ w; n; `

& H: j% r- A% o0 h& k, ]& W) w

& }% m5 E3 _, W( e' s     function save() {% c: H2 a& [* R3 O- i

8 F% }( c' z. }2 x" x6 R! O

, N& g7 Y) e( ^" n' o! I$ B. E         include load('include.lang');. F _4 V D: J6 t% V( p

t2 Z2 p% e" N9 Q% j

( w+ [' {7 L6 O0 m. q; {" u         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ; f2 z% `, ^1 _6 U% o7 x2 R( M

. `6 N5 o9 M/ n# M W6 ^

* ]. `* Q5 y% t; O) n! i  6 Q: i0 H( L" r0 m D+ {& K7 q

. s5 u1 ^: t1 V+ l

& m$ V- w- L: b; d( |4 }" h5 E* x* s4 s         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');; p( e3 Z+ i2 U8 A

) r2 i0 [# H5 c6 [

$ |$ i1 R' Y6 q8 U1 w C* u   ' t/ Z* U0 x# {+ i% j: D) p0 v

: L x7 Z/ D4 Z- _% ]

: W1 E- u& r, W# W         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); % l w1 T X: @" N8 D

( E E3 F( O! o

* g5 Y# z0 }3 `4 e  2 ]; L, @1 g6 r7 l1 Z+ n' V* Z

! F+ V- L& P9 A% J. C" c* `

6 x* H2 W; c, `2 U. ^         $this->set_savepath($this->savepath); $ ^7 I& H& S' V, @" R, {# Q

; J2 T$ {+ I% Q5 P

T2 n! j- ~+ @( \' |         $this->set_savename($this->savename);: _: ?* T/ l, s

5 e* r9 E- |. F' p1 N

5 {" |5 [, T# {: w  2 a, r! B; L( d7 ^1 l& R0 |

. y% i! e! Z1 L& m8 _: D

$ e- y4 u8 n( B' Z( F+ [- a% o0 v6 L         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ) B' n% e4 p% r+ A& v z

/ ]. b: ~- p G1 S4 l

7 \ O* Z3 L$ c! t" j& {2 g5 Y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);+ A6 ^2 X5 O1 [9 x& n: Z( e: }2 z% |; X

, u% \% N) W! c \/ e

( H; x& C% X( [% x4 Z* D! p7 e         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); * |9 H' n6 {6 _* T# h' I/ O

2 b+ T: |; u: f% O( U4 e" ]* y

9 @& z% h( L4 K8 U4 c' I  8 W3 J& p1 x2 h) b( ]: n0 U

) n! i+ K* P5 ]

* U9 b3 i' x$ ]& G         $this->image = $this->is_image(); , d, g' X Y+ g1 }: N# P# P+ P

7 y* A1 V/ {$ m

- D3 e0 k+ O0 [" \8 ~         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); & O; ?; g3 S7 S+ h) b

* E+ b' \4 F7 }

, M# t$ k/ P, s! N         return true; * D: _0 b# t1 H# p

9 S& R; @2 e% V9 s

$ ?+ B- H# k/ l2 E& {     }}' T: Y# ?* L( N3 z

+ N1 r$ g9 Y. m' E+ Q

, P* b% ]+ A* { 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ( b0 y8 I& ] V% W

6 W7 s5 B7 ?# L/ q4 b

* @0 A8 ^$ s2 Y4 ~* b0 }& O" p <?php3 u h g9 j8 g3 T. z

6 x% j" {) a* ~* e' B3 e6 W' Z& p

( j1 b @/ R4 x, o1 C U1 b' T* @     function is_allow() { 4 G7 _3 f9 c5 _* ^7 {2 n6 ~

& @: Q' I' I# M8 t4 [* E5 b

Z( G. v5 f7 e         if(!$this->fileformat) return false;/ _! v2 ^+ I' d- W( X# V

1 w9 N) h$ k& g7 k. @* s. T

8 H7 ^9 |% T O3 f, E- R         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; $ J, { L: R& k6 \

3 v& F% d G$ b9 L: J

, A: n* l: g2 k; 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; 5 W! @$ J* Y/ F: D4 H

: ~4 O1 p1 J% Y1 L; n

3 h( M# `& f, M+ ?, C) v& F+ |         return true;/ d# g% S% p* U0 O+ L

/ Z$ M7 @8 M8 Y" Q8 d9 b6 ]( S

; h/ F4 J; j( v; R     } ! W8 u, Y/ l& }9 Q1 `& e) p4 j) B

. D8 p) S4 o0 V

! u+ s. F% S1 l: C! l+ U, } 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。7 ^8 _% c- h" K8 W

5 L) ?9 z! F2 p. A9 D& }. H3 f4 y

1 H* b( N1 ]/ L0 q( e5 ]" I 接着会进行真正的保存。通过$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文件。 ( ]) g6 ^1 {& u6 J( y

' h7 T$ h7 L% G8 Z, o

. P2 v7 N( S3 r& n. C- ` 漏洞利用 : P' K* K) ]( a0 B# F+ @, r

- |" X3 k# e+ c& I/ }) E- T; Z

( C( k6 v; [4 E# D9 i 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 : e' s7 l; r3 T! B6 t/ K. A

% D+ K" \4 `: ?- l

7 u( y' _6 F/ }. T& [0 ?   6 |9 K: P0 R5 V2 h- ]

6 k7 m S& n0 T* m9 y( V

! I6 a$ o# m! M- F. s% X- o+ y7 [ 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid * [1 A/ O" F2 ^

3 |' v) l* p' f' v

: ?1 P% Y1 M( m 不过实际利用上会有一定的限制。 , `% b9 }3 M2 P8 j

" V' B7 ]9 }7 e8 z$ i' o; D/ U

, m1 S! \' [- i. w1 o 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。1 o4 O2 k( d8 e% R2 q

, Z. S; a' r3 k h: X. [! N

Q g( }( @' Z, m   - k d& a/ Q* [9 ]3 k5 y. d* v' E

7 M7 F9 v0 n7 M3 _4 Z

) b2 r7 P. }& A 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 7 K8 Y; ]. f5 V& P9 `; P; t

7 H S1 ?& f0 ^5 M8 }

- X' b. P( e6 ~( P* Y* q5 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]);省略...8 t- S) E( P }+ N8 B1 A8 E' P( ^0 @

+ y9 [9 t" c; [' L8 x

" p) X$ G& c6 ^' S 因此要利用成功就需要条件竞争了。 , V# x y2 w4 u! R% g

# p) `# I0 j6 W; d: } R

; X* |% D' B' Z3 p) e0 h 补丁分析) T: k# N5 Z0 ^& e/ H: B {: @9 ~

3 y, z$ P. q: C |- |# r

# i) Y, V4 }& K u4 t$ l. g' d  ) U- b4 r9 s: |6 j6 V7 N

; R. ~+ e7 G2 T! m! K3 ?7 K9 K* m

0 j* P+ g" {; |! V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: & M% m3 ?, o3 N* }: @

$ t5 J5 D7 r6 a

& |9 s# v3 D5 V- ] function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}0 V& |+ _2 C' j8 N' k& k |1 H) ~

7 M% o2 R1 G# t0 ?. t: F

: }: {9 n M5 k+ I3 z- R* d( ?   9 B( y; u2 r% P% a5 O# z" W r6 o

' a8 i5 i8 v8 m; N A/ Z9 r" J% w

$ _; @( Q$ e4 Z! h$ R8 c9 L 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。' ]" U1 H2 x# [: Y# j+ K# ~

1 ^5 g7 D& [" m' @: t

$ ^! w! T8 V0 m+ c) w1 y 在is_allow()中增加对$this->savename的二次检查。/ [5 ~ H C7 q O2 _+ n

' j$ S1 T, U+ l" a

, W ]: Z! Y4 N0 X/ O& a 最后 / x4 `. i1 r% q5 y, }2 v

% B p3 J# B, S3 v4 O$ i8 D

7 s" a' [' Y6 Z" r5 F+ d 嘛,祝各位大师傅中秋快乐! 6 J; z6 G+ Q* o; D% z

6 I8 ]) _$ Z: ~) @2 @2 r! r( X

3 n$ W0 @1 m" O  # ~. R3 m) D2 w# t8 M9 Y

/ [5 A* l; W |; D9 P
回复

使用道具 举报

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

本版积分规则

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