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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
6 ]( X, k" T h. {/ {1 c" q# I2 F

6 {/ x& R* d( \0 J6 ]% b4 l8 f1 R

/ u7 D! h0 ^2 n ]

- ^7 J) `( P: M. L w* i 前言# t% n- g1 o( W% F. R9 Y

; S3 z& Z1 m$ O1 K" W

4 {5 a( s' W! t4 F 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。& Q; V9 C2 O- o

! [; c" M3 M7 B5 E! k7 u6 Y9 g

0 V" W. S4 p* P( L$ M9 D( G  8 ]5 g$ T3 r' Z) `1 _

' @5 A2 u7 @& T: X; z

: X- Q* L0 n4 J! B) i2 Y- ` 漏洞分析 1 _6 H9 i; @' `& ]

5 J" ?) Q8 u. |& {8 v% u S

0 ]. \# E* V j$ L 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 2 \4 x( y% X N/ g8 z' l/ F2 G

9 z+ O& N3 `7 @( L9 l% \

- B5 A+ V2 M4 N' z4 _2 {* R* G   8 f! W; c& ]1 b9 q

0 n* I. ^) c/ ]% |% s9 t! y, g' C

7 G; o& f/ `; f- }# T4 q' ~ 对应着avatar.inc.php代码如下:! d5 z/ E+ N/ ?' G/ I; w- `! O! Q+ u) a

# f, N3 t6 R; F3 U, S

' a1 B; D* ~# _+ y* s: ^0 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) { ) t4 a' `' b* L7 A( N, W

( M# N) k2 ]9 t; J1 P

% m1 h! O; G. L& Q e: U     case 'upload':9 }2 O9 s* h2 a/ s9 D9 u9 y

: T' v4 J# j1 e6 h& M% G8 B

6 N1 J6 t) s1 T" n" L' U         if(!$_FILES['file']['size']) {1 i# i( ~) w1 @/ k

b7 D0 w4 U2 w, m' @* A5 W

9 z% p4 ~/ `7 ]- X) W             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); . I+ N, x [2 w: m

2 s# p% r/ h/ ^; d2 t

" n, f1 W/ E9 w0 ^0 ^             exit('{"error":1,"message":"Error FILE"}'); ' S" \; W: b( }; c

+ k; T+ x. z" P* h9 w. k

& X! Z8 C! T6 |* N' r         }; V: V2 p) \# s1 r. }& h* J

8 f8 ^& Z; B" g9 W5 l* D; @. V

8 Y0 @- N- s2 J3 s         require DT_ROOT.'/include/upload.class.php';. G, P4 e! c0 R- ]0 O5 u& ]/ c. B9 @

! x$ p- s) d9 y) Y6 E/ ~

2 S, O1 L1 J7 R' Q6 r! T% Z: N  1 k7 z9 S* ^3 p6 ^ V& q$ s; c0 H

9 R2 v# Q* m/ K! W& e9 S

' m) R: t0 K, g: X6 C         $ext = file_ext($_FILES['file']['name']); & O) @+ P; \- G+ t! v( h: p

4 H7 s/ r& n- L# x; _4 C( g1 C

, z5 {4 ~% t) J: V0 V' l         $name = 'avatar'.$_userid.'.'.$ext; & z! j3 v, S4 x* h' n

5 |& c+ b& ?( C: F0 `& p

( y( m) P$ |" h( y, J$ d; ^, Y         $file = DT_ROOT.'/file/temp/'.$name;' U2 w' N; r8 _7 P. ]0 v0 ^

. w) j4 t+ B* {* G. m# C4 Y9 \

# d) l$ N) E J( _7 }/ J   % L$ _) ]0 L' y

8 M4 J- w' i! x

! {9 }% o5 Y+ T/ J& {' k$ p         if(is_file($file)) file_del($file);2 ?/ ^7 h$ Y9 G* ?- \

6 ~! q( Z& ?+ @" \1 v! n) S; d2 M

! J5 V* T4 \ u+ \' v; L         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); - e) }+ t1 G5 a! o# S

! J+ a2 f+ t m

, i+ b8 c) Q6 C! g8 d: f  0 t7 k. G% l' k- ]$ u6 f

' P! g' k3 q% S8 A- W

% d7 G/ z2 \+ j; M         $upload->adduserid = false;# H. A; w4 B$ L/ ^$ n1 C

2 H, `3 E+ t; T( r' ?( O' Z7 J

9 r$ }' u* W" E5 j" W; m1 W% {7 h   0 t% F, d" W3 S# P% e" U+ E# g+ @

9 S* ^) g( q* d4 _: ]

2 x* o* A5 @4 r, h' W& f         if($upload->save()) { 9 W& V; {+ r [7 P+ x0 ^7 j% I6 a0 g

7 ~/ `/ Q" ?' i% ?4 e( \' `) V

8 S' H% G# C$ e) ?: l3 g             ..., \% O( }/ s$ s9 F7 n2 o: G. l

/ J1 e( o* x. T, K2 B$ g& t6 ~

/ \% X$ T3 h# z+ N# B, H3 N6 U; y' a         } else { ' ^' k, T- [( {2 h$ m

. M0 n* J9 q0 {8 D) t

* v& }/ ]% i, E8 p2 I- `             ...# v8 v" P: O3 n a

2 [4 S7 l, G# }4 f# ^7 X6 d

$ w, d/ C {0 u1 ]         }" w( L2 c. N0 _8 F3 P( p8 U

! k* i, j. [9 a9 n3 h; u

5 I9 F0 j5 K7 x* g' f: x     break; ( V# J# E' h3 c! Z9 o3 V# I) o7 i

$ N) E+ s/ |+ `+ [# w

2 T; y" P) p+ ^: F: O5 P& R7 }- X 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。/ b) j/ R* W5 A7 T* I4 ~" f2 z

0 V; O+ q: |- b% b6 J% k7 n

& u$ l2 z0 N6 _3 O% ]. | upload对象构造函数如下,include/upload.class.php:25: $ D5 M9 H' Y. D

4 C, j; F1 Z) S1 _& {

& I8 l0 {' h' B5 y <?phpclass upload {! H M0 \% {2 E1 {8 |0 t! g

3 J4 B2 i/ y8 X" ~

, g" A( X3 ]% O/ z: @1 C     function __construct($_file, $savepath, $savename = '', $fileformat = '') {. L; ]! k# \8 H/ G9 y$ W* D2 |7 G9 f! F

: L2 K4 w( L% `+ y' `6 l7 R/ C5 s$ Z

+ v2 g& _3 S" {% X2 [$ u' M         global $DT, $_userid; 4 N! h" S6 w( |9 r

, ?2 C7 s. N, i

9 o# n9 m" B* m6 O( ]! [0 l         foreach($_file as $file) { / E/ b6 x: p6 n* f6 i m- n s3 L9 `

- s7 g! t4 f# t5 J

) d- O$ g( C8 P9 }             $this->file = $file['tmp_name']; + w6 K+ ? \' ]/ ]. _

" b8 v7 E: Y6 n3 k# U8 |5 |0 s' d

9 k. o* A' K2 Y; v             $this->file_name = $file['name']; ( s; O/ K) N5 d& t5 A

$ r2 L6 H! t8 n( R8 u

3 w$ G" w7 ]! @: C3 o2 t# W             $this->file_size = $file['size'];& ~9 }% U. T/ z- Z) V- i

0 y+ x: ?! [0 [) ?2 E5 V

1 n, m& x7 y& H/ { [3 M             $this->file_type = $file['type'];4 j& A1 R' J: ^2 v/ T% h g4 N

; x+ C' O/ G* l1 [/ @

1 g1 Z% W" F+ I. V5 n: N5 n             $this->file_error = $file['error'];9 y' [* l. F" s( k2 @/ |

( b n5 H' z. t* a7 t$ F

m/ i/ G/ ^6 B3 e; p   & E% b" U- F/ {

2 W0 y" j$ r" k

+ N6 F- b+ s9 C3 |         }) F6 F& ~+ S$ ^. x& B2 C$ f! ~

' I8 F3 S! \( a% F, E K

2 Q% }4 K" J+ u         $this->userid = $_userid; 3 Q8 W: M: e) ^! d5 p' s7 V7 M8 f! @3 R0 l

+ t$ \! w/ D% L

7 V" J4 _4 [$ T2 c, ~         $this->ext = file_ext($this->file_name);6 p# w4 P7 @7 U6 S0 ~6 e

" @, e9 l# D p

# G! C, I0 v* u; ]7 {) n: L9 P' _         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];: v1 J0 K& H; u, s# g: r

( y0 e; ] h4 y. r) P' V

, _* h; t/ F# e s         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; F! K* h. C( ~& R, }! w/ W

f6 N4 {8 A" i$ o% G4 [# [- U

- R \( W8 E4 J3 \1 ]         $this->savepath = $savepath;, T# z$ p; c: l# U$ B% X; C# Y$ |

: J" k1 W) O, ?

" Q, d1 a* e2 _! Q: t9 N' [         $this->savename = $savename; * Y; i" q2 i" t' S g n

) v+ I, p0 M4 f

& z4 G# L/ P9 q6 M2 f5 ?     }}6 d+ v6 [/ F# C& @

' F0 n) L8 {/ c3 e" \7 C

1 g) N2 X( h2 L 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 4 y% ^2 {6 ?: F8 ?+ _, `

# W# y: t( f+ E( C+ ~3 t" C9 l

, W" x! T9 G* Y& T" K9 \8 Q; C$ P5 T 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 : S$ s. N# k. \& h6 u

+ B! p5 ?8 @ s

p& W6 I, Y1 K' t6 D4 A) v' t' s. t: 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.php - N8 f8 b& E1 o Q' L0 N8 v% ^, P

( T$ e' }: B G% y( L5 B/ a. h

8 _# s4 ?- R1 L* B: O& v$ W 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: : M8 t( Z7 i! C

; l2 e0 V6 r) V% S" |/ w+ [

5 m0 ]$ K! k4 T$ U+ I  7 U; {3 R0 x4 _7 K! X

6 @: A% i5 E$ _/ p

" `: ~; j3 a/ E 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:: g6 r# s& b1 N; [

! `, b0 n- x; G

; a9 `$ J3 E/ d) n/ k; E <?phpclass upload { 7 b8 b8 _2 i! ]& P

* Z6 ^9 s7 m8 A B

7 Y$ [0 V2 F) d i     function save() { ' J9 \. m) w: {- R

' \/ d* H/ Z9 J; D) R J

1 y1 f2 @% z Q6 F         include load('include.lang'); t. N' _* G! b5 S

, _! _+ T5 b8 }1 ^* d& h1 v

+ [4 F* Z+ _' O/ [/ ~' u4 M; t         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' M# c* k. W$ W4 z' ?3 ~0 [: w. V% W$ m

* ~) M6 ?* Y4 S% V; f

) K' v" _# ^& N' L! j  % R O2 Q& F& ?3 F3 w( ~

. R- \+ e8 \% h2 v

' }' E/ }/ `4 m- B7 _6 ^; \         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); p. V. M+ p5 g3 m6 [+ T5 Z' x

- _1 C- L h- T) g0 n7 S* N6 b

1 H# C, f& x9 U& h: q& @  * o2 L0 q; Y0 ?2 C, Z

: M: w% C3 r: ~; _) {

: ]8 k) k8 B$ t( A6 p         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);$ f1 j# L( m8 c1 j

& l; D- U$ w! i% Q( p; k( Y" w

* @3 a' j$ L k" w- Q/ |   & P- i* e7 y5 M# @+ `4 c6 m

7 f- v: ?0 ]( J- s* m0 R

2 E4 y6 q: A" e6 ?. P' `         $this->set_savepath($this->savepath);% ~; e; g! a2 N7 }1 v/ T

9 j4 ^( {7 N& K) S3 h0 v

h5 m( u" n: t; k! _         $this->set_savename($this->savename);8 t7 @2 F; m0 w5 ]. i

! I- C* M2 f( _$ ]6 v a; _

& ?! a% Q$ v% E& M   0 B' V& z$ @. K' i9 p- J

M0 D p1 E2 w

# ~. ^: C- H* u& A         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);3 y+ M% S' ^1 F5 Q0 v" _) Z3 b

9 K1 Y7 ]) g5 i' g

) P2 ^) Y: e) o4 Z, a5 g8 B' ]         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); . i3 f2 W, j6 e& r, q1 O/ [1 l) X& N

) W% H; e8 z0 ^. T' ~7 }% x

/ F. o, D# y% m         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 6 n) _# U4 f/ l+ K4 I v8 y- U

' u8 w3 X. ^7 k2 H

2 S: S, k& t z1 S  ( W. p$ |0 A8 ^8 p

1 j2 d/ [; t: p3 M1 [+ \7 R/ Q

0 {, f: f% Z6 Z" e         $this->image = $this->is_image(); % j4 o1 M0 T" E% i' q

- Y( K r- L' ?9 d# T* M

" @ K- E2 z# G( I% n: l         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 2 D! ?" T, S# j" p, |" }

: T. b! ^! Q7 X8 f. h/ f, b

% w1 X0 z9 y% P" X0 B         return true;% s/ c4 P' N) Y0 b. p o* j

7 J. j) F( p2 {

& |# M! G( i' S' I     }} 9 |6 H5 J6 O- Q7 H

& B! c: O( w, T1 {

' t' n8 k% ?1 B s( u9 `4 K [0 n 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: $ v8 _5 J! d7 c

) [: k1 {+ ^( \$ @9 Z! I

+ h, X9 N6 c. F <?php 2 v" ^! M# _/ `* \7 W5 I

\4 A3 U& p/ C2 K+ w

2 I: a: z; d$ @& @0 V! ]- o- \     function is_allow() {! Z7 z0 b+ ~- B8 S

4 Z4 m. h g! [0 k

]1 ^1 _, J. }9 m/ I# D# y         if(!$this->fileformat) return false;5 G, ^( @$ v0 ~

4 a0 ~, _0 u- U

- L n7 J9 D: o4 @. m         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;* @, ]' [! p0 f6 d# H$ ]. L

2 S# }; z5 w/ Z. F0 C

. Z! T( d3 m+ @" j" ^         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;4 X' d2 T* W; J5 o) N4 L

* f- }7 K! W& c$ |, n6 i- N# I# h7 ~

% q6 w9 z8 B5 E; z, {9 U         return true; ' j% Y7 M7 B+ h& C) D

% u' d/ z; H! P/ s/ J" ?) J3 D

: m2 c: u* a0 X     }/ ]* _/ U, X2 z. D9 b7 H

$ @- p; X( F0 E/ q( b! ~- t

- ~, n: l/ Y: j7 A 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。1 c+ V$ f+ s% ~/ A' S! r

* D- X% e% `/ J5 g a

2 R+ O0 u! h, z! w! b 接着会进行真正的保存。通过$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文件。- h: W, d D3 k/ F9 ~5 ?8 R

o$ q7 C+ f6 L7 u! p7 J" J, f7 M

3 I( H* v: m" T, j5 I 漏洞利用 , l$ Z# T, U' p& R4 q

) ~- K5 P; t8 x1 ~

$ s3 x8 C) R! K0 X" I, | 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 , T$ |, |0 U0 u! n: T: L5 L, _

. t2 l" ~& u# ?

+ V+ n! H# U2 Q7 @   K) R$ Z' b, ^4 F" s/ G

# L( |& z5 \6 L4 K; B9 n

: I: z* A8 h' u: u 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid8 {. p9 H+ f P- |

$ J/ y% `5 P3 | P' ~

i( Y$ l' j7 }; G+ G/ ]0 D 不过实际利用上会有一定的限制。 ' e4 \; ]' h- G3 L4 G1 B! R

) Q! g% `' u: }5 y

4 T- n) N! O% S4 l 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 , o9 }8 v& W& s7 a& P- O7 z

7 n' g, \7 t# k# e2 t1 |+ Z

% m1 L n3 h+ C. Y% a" l   9 g/ N+ J. x/ x

6 `. A, I7 W: B" i4 Q) P

: v3 [& @. ~& |1 n( o 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: , G( u0 [ z3 N& g

J) O% f8 z7 K, w G! i% L

$ C$ S6 S( o. I4 q: t: u 省略...$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]);省略... ( X, _! }3 d f- s" l

/ h9 T' {- s8 N7 n

9 ^4 P. w$ }. P7 i* Q0 q% \- @ 因此要利用成功就需要条件竞争了。5 t! H0 ?( H- f+ y6 b- I- m

8 Z+ d f/ M# W u2 M: H c3 U

8 r; H* ~' h8 ]8 E) w0 Q8 a 补丁分析 ( {/ k. X: K7 {1 X' b: ^( k

3 t0 h: Z& P2 a/ [+ `

' g% |2 }+ F6 W   # w: }4 K9 l" A/ a; S* k% M/ |

* Z( f: _4 Y- ?& B: w

3 k ^& |' I/ O' }% f* |$ [7 Z 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 5 W1 Z& z( g% L$ }4 b, I

8 P) t8 Z. p/ C

; M# _1 v4 S1 {2 q function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ! C+ C; b3 k# a- L0 {

0 m0 K' P+ r* W C- `; _

/ J. N9 s O5 I. o0 O+ t/ n) J$ t  . N4 l0 I _9 @& E; j

( L- _( S* d% F$ Y) p F

: `! n/ Y% g. _, l' u3 `! V- g 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 : Y5 ^/ T9 D! e4 y- ^9 ?4 W$ [

$ |3 v) L( z3 U0 N: U

0 U$ D7 S, e2 ?1 e. y( D! l0 X% i8 _ D 在is_allow()中增加对$this->savename的二次检查。! q) X/ G/ q G8 {4 ~3 b" Q

3 w; j+ d# Q0 Q! S

K3 w* |( e( k8 I T/ Q! T 最后 3 H7 H% j% s& I3 W9 _ Q/ S& h

8 ~* c3 R/ Y% d1 d7 d

: R% E2 k7 P* f6 c( \. I 嘛,祝各位大师傅中秋快乐!& e& X; @& K) J7 ?, r) G; M( `

- X3 L9 U" A+ @ p7 S0 e: V; r5 u

$ ^' C4 h }' u2 E W: i" r  + i0 g+ y# [- w( @: p5 y- }

7 S) ]. C; q# `( b
回复

使用道具 举报

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

本版积分规则

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