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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
' E, Q! ]+ t8 G4 g r; s

1 L7 M/ x. N/ n4 e5 n7 L- d- o* Y

# m C! d! p6 U% `7 L. j5 D

3 V0 }3 P: ~, m1 A4 \1 y9 C 前言8 m( ]' n) l# s/ C$ l

# D( @& n8 X- T$ _; c& n

6 q0 P5 ]# P, I% | 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ) _+ d( r) G+ W1 l# Y

$ u b3 g" p) N

O, [9 x& x) ~8 L2 W   " g- Y' H; }; l; B1 t. B

4 x; v2 g8 @. X% ?

% y3 p" G9 s" Z 漏洞分析; ^+ J+ J2 o5 k3 \' B/ u

1 e$ L! W' s$ x4 W

% K7 B0 l O6 ?! \! m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:: E, q7 z9 z; E/ n

- `, j; K$ n+ @; _. E H( j

- e" [$ U% J. v2 c  1 r' e0 y; ~8 D0 _, X/ e& a

. e3 G& w2 g0 M# O" a: L! |

, ^" ~% u7 V9 S; F/ R 对应着avatar.inc.php代码如下:$ A5 m9 u8 K- `7 ?

H9 c& ?: `" `# W4 O/ a+ a4 @6 K

( c: S8 j# t3 ?& O1 B5 h' h. M" c <?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) {. N, F9 C0 D5 e9 I

; Q; r0 q& W0 G( E, V1 n

; }! K! ~- _- @     case 'upload': ' w% ^* }0 E$ B; @+ K# B" y

% w2 X, S4 I3 R1 O& C, L5 ]

) V3 g9 B$ x# e' F# @         if(!$_FILES['file']['size']) { 3 K1 S9 p6 Z( Y

, o. d1 t! @* d+ k4 v

$ v) n( d( ]; Q3 z2 ]( E$ l4 c             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); # x! w6 U& o2 @3 h/ [0 T5 x1 Z

9 E( _" V. ~+ }0 f' D

( C# [/ {3 t& v l& N3 p2 @             exit('{"error":1,"message":"Error FILE"}');( W9 @- C' K/ H7 C5 E: ~

2 }4 I$ x, d- \

$ T. Z3 s: C1 ^1 L+ s6 }9 t         } 5 @ d' S4 m% ~; s0 f+ M: {+ K

0 V1 ]# h4 u" I' i n2 F

8 S4 v, p2 K# l% N" P! c$ O         require DT_ROOT.'/include/upload.class.php'; 4 q0 n6 c/ d- T% `% p) j) h/ T' k

6 n8 d8 h6 R3 C1 t5 I3 x) V) Y* D

" I# I. Z1 T: B) ?  - O' o3 ^6 x1 B" p6 V

3 z3 y8 F* z4 S) e( z S2 X

1 p) C: O& q# F" ^% O: Z9 r         $ext = file_ext($_FILES['file']['name']); * L7 T7 }1 |' g" ~; m5 Z7 K" u

# z# L4 g% q9 Y

, u* Z& Y$ t6 {+ D         $name = 'avatar'.$_userid.'.'.$ext;5 Q; [) G6 w- B6 d

$ V% c: Z/ x2 `" u$ E1 j- ^+ X

9 k& N" e. \* n! ^( O* W         $file = DT_ROOT.'/file/temp/'.$name; E1 M+ o8 j M' D7 G, A4 w

E& m X" D+ W' f3 z$ b

2 o z7 ]6 x7 b+ c6 o  ; e; Y7 z7 i0 Z, i1 T

& u2 S: R0 H5 p# e7 P3 V

: ^0 J2 b# x# @% d- i1 Q         if(is_file($file)) file_del($file);! a! C2 x) U* U3 ^) F1 z0 {* o

% {1 z( }2 Q+ r. y7 E( B# e

9 q' L8 }8 a) F( a3 H- [8 j$ Q6 S         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');1 L. \4 T4 q: b5 W5 c( G

0 o0 t+ [4 h3 @# Z2 I. r/ {

% r9 q: T1 N! t* W( N0 o  2 g9 t3 M6 X9 J5 W; C, D' _

- x$ q) x8 h1 G! K$ L* k

" |& E' v' ]* Q, h% z         $upload->adduserid = false;0 g; I1 ^; C- D s

+ I4 f1 k7 i3 w- H- I

+ L! u8 [7 [ k; D" F$ V   ( A/ [3 ]4 K2 y7 R2 ?7 O2 u! f

) X5 u( d# ?- R+ Z2 a4 v

8 ]5 M/ ]3 y$ n/ O# k         if($upload->save()) { # T! e! l# X4 j8 \3 P

9 q, k! P; M1 ]4 c) l" F( ^+ h- e

% e$ M \3 F: X1 ]             ...4 o# u5 k$ A+ S6 q; c/ L- t

& ~# ~, ?( W& @) j' Z: U5 v$ Y

/ }% p% k) C: `6 N: M1 e: ]         } else { 3 F2 X1 G5 J6 j; `) B* R- f

* c1 S" |6 Y1 }2 ~4 e" N ]

) r, L. t; i7 q ^* V# i             ... 7 h- O# n+ S# Y! T- U

8 d( [% \' U" C

( u% W3 e& w d# A* h         } . I+ b- w2 }- [6 t' v

% A9 H/ ~/ b( E8 [ O$ S1 }6 Q

. x5 p% c1 r# @! S- }8 M6 O. j5 N2 O7 R     break; z2 F1 O3 I" U

N& |) h, s" _9 ^) o- [9 e) t

0 f( `/ l" r; V+ A 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。& s: x d/ d+ d/ ?7 q1 h. x4 s

8 a# c( {9 f/ b' y; ^9 V( C7 C! @( [

& e+ Q) v/ D1 L( v* ^2 ` upload对象构造函数如下,include/upload.class.php:25: # m# s1 W0 G& s

" j% T6 A2 ^6 l7 V! r( \2 V1 }

% F- b8 w- D+ M! T <?phpclass upload { / g& E7 Q' q9 t r0 K

: g, [& i: k1 q$ g& A+ r: S' r8 k

# v; E9 U% V4 ]7 |0 m! O4 _     function __construct($_file, $savepath, $savename = '', $fileformat = '') { + P* l/ J7 K: I7 Z' O4 x4 T( e% g0 H" G

% W; {0 ~; k# B/ w) W" y

& T8 ]/ p( x3 h         global $DT, $_userid;* k$ S. h8 ]6 K; [( s$ g+ w+ o

' z! J# i+ \- J* T! q5 I- r$ X

( u1 [; U. J5 m k5 Z& W         foreach($_file as $file) {+ h4 o0 T2 B6 A$ O- I, w

1 l* ?2 w8 i, d/ R a# [/ }! B% ]

# g3 d: m$ _1 `" g6 |' G" `             $this->file = $file['tmp_name']; / j. U3 v" ]% Q$ U5 t3 A

7 R! m$ L, C* v7 p" w

% o6 j8 [3 {6 y             $this->file_name = $file['name'];* `- c; Z& j1 h4 R

+ v' m, b3 B* L. {

. G, X- F. \4 x; A             $this->file_size = $file['size']; 2 @1 V9 J8 c* ^2 _; Z8 M

, |" X& @9 M. V

" W# _+ u9 N' N- c% r6 s             $this->file_type = $file['type'];0 R! p) h/ }+ S

: [; l4 r& L; |5 O+ O

/ p2 j3 S1 {. q$ e) @1 P* _             $this->file_error = $file['error']; % _4 O/ R; G- R0 X- m* f" b

& w* U( d2 i' f

, Z( J7 @0 `+ t1 v8 i  9 t: D- H( t4 o1 Y$ U5 z# X

6 i4 C9 G2 G# b" H

3 E# ^% g! g8 i" A/ A         }5 k: }& E; `% N' w6 X% \8 f

+ x( X) Y+ B$ [. C4 C- }' H7 i }$ m0 _

* D/ k7 k% x7 u4 w2 {1 A         $this->userid = $_userid;( {; g# c* ?. T/ C Z* d8 z

6 X+ b6 ^: w# q y g' |

% Z! i0 w g w( _         $this->ext = file_ext($this->file_name);1 f4 y( f9 {# P$ v3 @, o. K

4 N2 X5 S+ b2 N" j" W# }5 c) i

* X& ]% f# x4 ?, a) o: F9 r! u& f         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; D# O: S* K( E: H+ Y8 t# q: m, P

6 k& m4 Z' v) l3 Y, b! C: r& F* ~% t5 _

- _ @+ k+ u' T$ i0 |         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;4 _. z* ~. e+ ^3 a5 x

' P; V( j" S; @: N: o% d

6 p+ z8 G9 R0 x5 [! s: g         $this->savepath = $savepath; + U1 Q2 v2 a' ~1 T! _

8 M9 R2 H. _- z: \& B x

% h7 P- }: j4 w         $this->savename = $savename;) ], v: s# u7 w' B& g: |0 y

6 u: ?3 U; g) e, Q3 a+ p+ T! [

% F9 J* f' J A* @4 g     }} " y2 s2 V% I9 \

. O6 M/ Y$ r g. Y

9 ^( }- \7 j9 ^ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 8 A/ B+ C9 `6 p1 U( k2 B) b: k

8 T$ x3 k1 E: x

; c1 i1 W3 m u7 L. U" H 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 $ w/ y C3 ?6 g9 k& \$ Y4 H; G1 |

' }3 u: k; B2 P

1 E) ?/ H' c- q0 v: u" S $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.php2 _: Z, R# g) C7 p

) R/ h6 ^& [1 c! H6 O; c

8 e+ E; E0 H; n( n+ V+ v" Z9 ` 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:9 n" Z6 x5 d: |6 e

6 ]: g0 W" P6 \, M! d p

; A: f0 F! d& v- @ k1 U5 z8 p2 U1 D  ) a0 h4 ^8 b3 g

& m* Q3 O. u \

- _7 V' ?: I0 C' | 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: ) C* p u# N, M

% m) Z: z; k8 j$ |

- n+ n! X8 Q' T" F: s <?phpclass upload { + E: @% J2 p% v1 l, h( L6 U0 k

3 V6 k8 D3 p) Z

& Z. s% }/ J4 S! n# a! J: P# g     function save() {' \7 _/ K6 U. N H# s: }# |

- {* M0 ?0 P0 D0 k9 |

2 R4 T0 \4 z) p) k# q! i2 C5 S         include load('include.lang');8 P" m+ Z# ?. l% N; J

7 y. W2 }* ^& @/ m0 I) j# L

z3 `& m6 a' L3 v. f+ }9 h         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');9 G) J7 V5 Y+ l$ W# s9 C

8 M. b& a5 F8 q, M! O9 N

+ O% b: @$ z$ N1 Q4 n  7 @1 y+ ^7 a, W9 G4 U% o1 i L2 R, }

0 x) X" a4 W s& D3 }9 D

) `1 q6 J& l- A4 S8 d/ S8 N         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 L/ @2 m$ T& G h% r0 I7 K* Z

7 C: L9 x- n( d( {4 f/ e! T6 k# c

# X" f4 L7 _$ ~6 C  + g3 K* E3 s4 s4 @+ U; z! I

- G( Y5 e' L8 U

" c6 r: H1 B( s7 V: q- z5 d7 J$ n; u         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);8 b9 B, g% P2 V9 ]! w

/ i" D8 \8 s$ r& B) |

, w& n! V, Q% x3 E( j; Y I  " l9 R/ {* x; u. I

' O1 d: |) r) }; ]

, A, k2 |0 g& |. L6 ^         $this->set_savepath($this->savepath);; D q" o' B$ T, ^: P& l( Z% E- I! j

J5 |4 g' F- l( |8 B, G( N1 F

# u9 `# {1 _7 P7 M# Y. c$ l         $this->set_savename($this->savename); - B' r$ s2 l. h2 j2 s0 A; o

2 K2 `7 `! l; C% V1 |$ d; A( V0 |

$ e) i' s5 U' |. m& ~; ~% ?0 e  + X B6 Z+ A! V1 H0 S

8 N9 `! W* |5 T+ c9 y# I+ ]4 b

' W0 z# Y7 \$ k: H; y! M         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ) M2 ]' W) y, e0 X

1 U# n' }4 k! Z1 ^, M) b

% a, l1 @2 v1 t- ?         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);+ n: i2 x! Y/ Q# J ~0 |

) }/ V, u0 O5 z2 \8 D* ^. B* } e

& l9 g' W+ h6 d$ M" ~# u         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); . J2 v& P: G# Y

) u* h# |' N I6 n& w0 |1 j

3 F8 p0 G2 _. `, E  ( t* S$ q2 ~4 U( ~

# _0 ~5 [ H1 [* j5 E

% g0 ]: ?' [" y0 \         $this->image = $this->is_image();1 m7 C! h. r' d# n2 A4 L* E2 l" z

$ a9 c2 ^3 L) c$ Q

0 H, h& X2 J1 T4 ]1 ]         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);" V& z6 ^* C+ z1 w+ u

: P: F% P6 n- k2 g: U

, C. ?$ P/ T. S% A2 B. q         return true; 9 F6 `# |3 R e9 V2 w0 b3 X

$ S4 |% c! l9 O# u3 c |0 l

) Q5 `& M4 r% a     }}8 Z8 m6 y# e2 X, y. }

8 i1 I3 M; D( Z( S, m0 K# D

" {) X' i, u" @% _ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: . z& d% a) I# v; k

9 Q# s) V6 _- n' l- l

~' _# o4 F6 o. w/ ^$ b <?php' p! X( l( {# M) b

" y3 v D. ^$ F, k( ~. f6 i

c0 C# p3 J% u2 d, r4 {9 u8 k9 Z     function is_allow() { 9 K1 M# @9 a) B5 L2 _5 I7 A

4 ^6 V: m1 }* r$ M: V

0 ~/ @5 j/ W- G- Y$ f' h5 {         if(!$this->fileformat) return false;2 `9 n" R5 c2 ^9 m* H

: e4 b4 X3 ~' L0 Y: [0 L0 Y

6 M5 @* {# D8 b' T; `         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; ) r8 T& r0 K7 r8 u7 a

4 D4 {: D! j, L6 J8 y6 z

$ ~5 s% F2 @& D; f# q/ _+ X         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;* j2 }: v* E& ` V$ b% }

! m! C/ V9 c& g

$ g+ @% T# k( n' j% a         return true;* F# |. C& ]( ~5 f( L1 J

: u9 m- c/ ~: x9 N2 Z3 s

* H7 l5 t9 e6 y" X     }5 }9 W5 Y, C- y& e* \ y: f. g

* T0 U$ _7 i# o% n/ i9 e* d/ y

0 `) G1 x: p% w 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 , p2 n& x$ S* q

- `$ J5 o6 G' V" }6 |4 j/ j

7 V2 b# C. V) O 接着会进行真正的保存。通过$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: B( `% a0 b

& B. t8 g, O) i( @ M

2 h! {3 P9 h+ Y2 A0 \3 O; f ^) p 漏洞利用 5 L) t! p4 S+ G; h# v% I

# Q% @7 q1 K% |! R1 O+ B/ z

7 O0 k. _1 Z9 s. ? 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。% y; H7 a) ?* r; b

9 ~& G. J. j; v" o5 F# A A% W& Q) |

. s- ]3 u0 L8 c6 \7 x$ A9 Y   M! U8 u8 ` w* X' i

& H0 V, _ i3 d

6 C! k- H/ a& m! y5 u6 N- c: z, D 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid # i0 t$ d' b6 r+ c% d/ G, P' y, Z* i

6 n. X( ]) Y6 |* b: W, R

& [- k4 D+ E" s$ Y; J& S A 不过实际利用上会有一定的限制。 , G; `$ v: P$ V5 o. d7 n* v

0 X2 M9 {2 q% |, l0 t

$ Y" c' ]5 w6 I 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 ! U) @# J5 H7 {8 M h+ l7 J

Q: O& L+ h" {7 M; v( c

2 d, f6 C' R8 J   ) t' |; C% L0 _- y) v3 V

) }# Z% i( x. z! B

) u V: z0 V; _! s" p9 Y 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 2 k; g$ A9 W2 s, w

- J' r* x& |' J# v4 ^

4 A4 e: t% V' F 省略...$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]);省略... 1 Q2 @3 ^3 s8 |/ d$ d. `

+ X; X' `7 O/ O2 }, Y

$ F4 y' b( A/ s 因此要利用成功就需要条件竞争了。 u g, \ C4 v/ s& J, O

0 e- X$ B: d; _

; G# A/ J' Y+ p 补丁分析 : b. d& Z( |5 s! R# Z( E% y

) D/ q, f! s) }/ B6 G& m; G

, A4 P! ~% t+ L2 m1 X) w  : E' A& y. q. X

( c& h& Z1 L# S/ A2 P

6 ]$ A2 Q4 Z& h2 R: L 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: , M' P2 P. i0 T6 g/ v

# F7 Y% [# d( {* [# S

; A1 {$ M3 {- k8 C( G% w function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}) b; Q5 C9 r2 f

0 e, ?# c6 G1 Y4 i% b0 V

- |) y, {, K3 s7 V  3 Y! Q( i8 J# Q4 s

. s7 R+ R+ V) e. R3 @: y; a

1 A; s5 }0 G2 T/ O( ~* e* c 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。. t5 g. W; f# s) P5 F. H

# i1 Y9 ?$ l {6 ^: V( ]; x

7 |3 \# F4 {! P- C# g u9 W 在is_allow()中增加对$this->savename的二次检查。 0 a) p( B( v; t1 a' Y$ n' c

1 O; v& B+ |, [

6 t2 e1 |/ M8 r6 H: P% ] 最后 $ e( f+ Q- d9 x

% W- j- V8 I6 E5 r6 s% r) c

' {, {- g7 y" a/ \( X6 r4 J8 h 嘛,祝各位大师傅中秋快乐!0 J# W# T+ e. |0 Q1 q8 _0 ]

7 L: w0 ~) v; D/ r7 T

5 G& {( n/ Y4 W0 l   / z! m7 `4 S) n. Y4 k

$ Y/ s7 [5 V7 m5 p3 d
回复

使用道具 举报

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

本版积分规则

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