中国网络渗透测试联盟

标题: Destoon cms前台getwebshell [打印本页]

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
" n- y9 \, w8 Q" x- e! E

& F0 ]7 R0 O3 S6 S y0 T' a/ B+ t

; m; g4 {' b( X0 x2 J2 D

; c& R% o& y! ~ 前言3 l& Y: e% C# C3 s

; X: S+ \: x! D, u

/ I' p" h3 X1 ~' r# ~9 h. m. r8 K! T 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。# l2 B0 ]+ y5 d, ~# F6 }4 G# n

: u" [. w; Y7 B

4 {5 B7 k" p* e   3 m( E/ x( e" [5 d% L

% \/ y8 l0 r, ?4 K3 o% |8 G3 T& o

# a4 t U/ e" w: C 漏洞分析$ T" O$ _1 T- @

d2 _7 z( I$ m* d: h

; [$ X' B t! t& L$ ] n 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:+ s/ \) w# n2 r) K7 d

1 M) C. Q, u2 q

5 X1 g7 Z/ h' T7 e   ; t. J( \2 w' | j: ?7 m+ Z& b, J

, V2 \; a- V: P- F% g2 |& k8 _

# w' M& n4 A( m0 E: a# x( w0 m; D 对应着avatar.inc.php代码如下:4 e. h1 r. V8 m' T- }

4 ]. ~$ v. x1 ]6 K, E6 w

6 b/ r5 ^4 ~0 r% ^% d. O2 N9 ` <?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) { 1 o& I) G5 C% [4 f! N; E

4 a/ s d& ]2 O+ W! q

/ f1 y& k2 Q8 Y' L! M     case 'upload': : E f8 e4 ?" R; C* b5 J

' K: t/ H5 p9 S. K! N% I) V2 E& v. }

/ |2 d8 [1 x0 z+ `         if(!$_FILES['file']['size']) {; T$ I& b4 d% t+ L1 O

& w7 N/ D$ z6 ?9 Z& C

1 {/ i" W# B K( p3 H( \7 B3 q, C             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); p! G P+ a [: q9 a# f+ b0 B0 D

; q- k* c6 j" z/ {6 e( D7 l

6 h) Z f' b6 F8 U# T$ l- T             exit('{"error":1,"message":"Error FILE"}');! B/ w# \0 v/ i2 t$ P# V7 Y

. p u+ Q' y: A3 B. [6 z, ?4 s

% J* L1 c- ^$ G" }         } . ~: o7 [2 I$ U( Y

* {, b( [7 E7 Q; N

+ }, P# `7 S( Q# ^8 Y+ s6 c         require DT_ROOT.'/include/upload.class.php'; % W! [! y: ^* O& W

* y! t/ p1 {+ @" C! `1 O

0 {' k9 L6 K, R0 u  3 _* @! Q, N$ k- ]4 k. F* Z" q. w

2 X" _* F' H, p6 Z+ [, \

! ?1 o( V0 ~" g& w4 W* n; G         $ext = file_ext($_FILES['file']['name']); $ S" Y2 e6 A% u# @) S; I/ F

+ A: y3 W" i8 M; E5 ]7 T- v7 ?8 S' M

; H6 x" D# J# K0 ^/ T0 M/ n         $name = 'avatar'.$_userid.'.'.$ext;8 p" R4 Y/ y, M

o9 H G( ?0 ~' A# n

! j+ W. b7 J" R7 g, `- E' @8 z         $file = DT_ROOT.'/file/temp/'.$name;: ]: {* { ~/ b9 r7 V

, w6 A; k6 g4 ?2 ?) \/ U# Z

, w0 H, K9 T2 O3 W9 @; x2 m) _  - }( s- Z2 Z y+ O9 p* r* E

! T$ X" k. N$ n& C1 M% P6 t

3 A5 Q" r L5 F7 ]( H; Q         if(is_file($file)) file_del($file);4 A0 w* q. c2 [' M

/ @) r0 i8 r8 o+ i

9 p5 P; z- S+ y$ J: t6 ?1 u         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); / x% n. L$ ~* I

6 p8 C# O- b R/ w. r

$ j7 b. V) H. W" s3 p. j: N  6 J$ q& Z# J8 h' ^% Y4 v' d/ b

2 Z f& A1 g! O0 a- m- K

0 z, R3 z8 {' T: D; O         $upload->adduserid = false; , ^0 R; A, i5 k. |! P- u$ ]

1 q& v" j( f! J$ A) C V7 y

5 N: u0 T9 L/ ~, b   6 @# w2 F( e9 i7 B

+ ~& I& |' W9 a9 k

: x8 U# A7 N% U" v! p         if($upload->save()) { # Y& J: i7 }% {

8 E5 M- h6 h7 y3 Z# D. m6 [

* M8 L- T; I" Q; R0 }9 y             ...% t$ k+ y" V/ V2 ~% _9 Q8 q) |

% [* m" J% ?1 c. ?7 G

- \6 B% o+ G( \0 Z. N% }7 N9 ?         } else {8 r/ s1 }0 k8 a

+ P: t' R9 u% \/ p1 A0 r

1 C; T& Y( U; E& }0 ~6 p) j* a/ d% d             ...% Y8 J! z, A7 ?1 N4 Y& J k& `

. U. N0 r' x& ~

2 H% j( O' n8 B& i- a         } - K! `+ B& ~, D3 t

: r& l' `0 w; e- R' H1 `: W3 |

; I% ?" h% _9 A9 z) H$ [     break;+ p3 I0 ]0 n5 M! H; H

3 z* ?$ I. c! Z; H9 R' z

. Q4 N5 _0 j4 S) U 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。; u4 c4 I' a- ?! X- L- T

+ z4 A% s. j+ G2 J0 f

) L" J2 I* A% p' ? upload对象构造函数如下,include/upload.class.php:25: Z- g6 T: Y9 O

' R6 N U) J8 x A" x

% M, c! d0 x) X1 c' Y/ m/ {8 a <?phpclass upload {' F7 ?+ w6 H( n3 x0 Z

8 K! Q1 }; j4 J" A: f

* s5 w& O) R0 J/ j& J$ V8 @     function __construct($_file, $savepath, $savename = '', $fileformat = '') {9 ~( M; p, `$ w" Z2 {0 j! G% Z

( ^4 K$ B6 T& |' Y5 C

# A2 `% @6 J1 k' C         global $DT, $_userid; , o% i9 S* o9 H0 S- P O/ A8 u

8 e" F }4 j; ]+ {# o7 j

! g( r; v6 t; C- {/ g+ H         foreach($_file as $file) { 9 Z3 [: T' H e& e! G

9 U+ l/ @5 P( L( a" a- V

, H5 p$ ]- Y: s( N a8 [# R             $this->file = $file['tmp_name'];8 }5 W9 }0 \: [4 C* x6 m2 ]

) \8 W2 N( }$ B

I% M$ ~( [- [* q8 A7 ?             $this->file_name = $file['name']; : Y9 s0 x: l$ k& X% t" ]' |$ r" s

* e6 \' \8 x) {/ a" k3 L

% G7 \$ G6 V0 C" z, k             $this->file_size = $file['size']; 6 ?7 k5 Y/ J3 u- n- `& R+ w

* P% q) }3 s, E: a) U

- T+ `+ ]( a% f u# N( C             $this->file_type = $file['type'];+ D' h1 h7 X& m/ |/ T+ ^! h

! M- _( A5 }0 z; ^) R+ `' r

, ~ a$ a4 P! ?0 w             $this->file_error = $file['error']; ; R" S/ ?! e9 Q- u. I, }! M

6 r0 H9 g( {6 H2 R( ^$ X& g4 h

- x$ G, W4 [3 e) \) Q   ; j4 i; {" }# ^" `: f: B

# |% ^4 l' v! f$ w% i. O

. d' T- ^9 ?8 X: V& t- ~" T         } 3 Q) B. O$ g2 f8 ^) f

' e; B7 v& w( M: A

: d" ?. J2 f5 j8 S+ u- |; q         $this->userid = $_userid; 0 U0 v& D$ i; m9 S m

T% G( K! A5 t" x! U* d& \

. j+ `- b8 `+ i6 w- d0 _" J& L+ e         $this->ext = file_ext($this->file_name); 3 L9 `* _" O: u0 O

6 G6 X" w7 Y0 R, k3 [6 n3 X

* L/ {4 ~6 l/ H0 I& M         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ G+ D% q8 F7 C+ P$ F

n% g, j/ @2 m R$ ^; B

3 n& N; a( }6 K- \: R: n9 ?         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 7 R$ {+ `/ w; o; R4 r

: u$ s$ B( e& r7 J6 X2 y

5 `8 z1 m; A4 S/ e" i         $this->savepath = $savepath;- P! v, R5 O3 a4 L9 U4 p( E! j

" g( i1 m$ f& I- \1 n; }

$ l4 w. `, `4 ^# d         $this->savename = $savename; % S- p0 ]# u$ i8 [' L5 M4 x+ B

! }+ {( i) } F( @6 m" z

?, n+ t `7 A) u, r     }} % x# ?4 s1 s$ C3 x

( U) F# z1 Q5 R

. \' s/ u0 j% @! E6 ` 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 0 ?, X) i, k8 f# ^1 R) K/ D- ^& ?

6 f) x: L( A; C2 n: J6 b2 I7 _

! H; ~! l5 n0 t- d! u9 V5 a3 | 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 , U k# u: w+ Z3 e: F, P, ^

6 e4 W6 J6 g9 L% b8 C5 U; T$ M

3 [7 Q2 A* X& j% ` $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 * n1 W8 \! Q' {0 P" B* p# n

; [# T0 E( o1 K5 j- l

. `; |8 q+ ^& s- E, k' W9 T- o 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:8 I' y# z: H$ b" S! u

E% y2 z( F" M: i$ u

1 v" U# f' Z& B6 a3 Q  # V+ t5 A6 J1 s; c

$ R7 i- ~, y, i; e5 b: n

7 Q% l* _& x, w 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 8 ]9 Q8 |. A* {: |5 G7 O3 H1 [

: i2 o4 l1 o7 P" B9 }$ k

& ~4 g" B" D V/ E* k! j <?phpclass upload { % t5 C8 ^( m7 y. C4 P& @

5 ]% W A, K- w5 z, j( J3 j- q

2 e# n# k9 y7 y# D5 @8 L     function save() { , J* [8 W6 Z7 _4 p0 u, X- C. U/ m

* i& F+ V9 Z2 h& s

( j2 Z) _& `, | D7 x) r# B         include load('include.lang');; S% e9 I' I4 h5 W; h3 x0 Z: I9 M: E

& e( L1 k. Z+ X8 t" O; L) p

5 h4 ^4 ?8 l2 W7 s, U9 k         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 5 [4 z% B& v3 u0 r

* V# j- h6 E6 ]. g/ C

3 A4 ~( B. e) F- G5 `5 S  - T( E# d& a1 c

. U7 M# Y4 k) h9 n, G- a

. g5 C2 a4 |% O3 C1 R6 C         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');" B9 f, @# c( i4 r( ]

9 U5 l& M/ b) S# Q9 M3 p

) H' Y& ]/ U& H2 b- ?- z   ; \8 Z. N T! m8 K

1 }+ \ P9 Z; d' m8 _" k2 B+ j

4 ~% `' Q$ x6 s7 C: H0 m. S         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); * W+ _2 v' W' p/ V2 f" \

' k6 _9 b4 O9 S7 }

6 H$ ?& F9 F# C9 M2 W I5 E3 A   9 l: C7 B4 W6 J6 p; c

7 b' {% V! p8 o, y" }9 L

" d. Z: r- h( D- E         $this->set_savepath($this->savepath); $ I% ~' u4 L) g3 p$ }+ X

1 W5 N! o" x y1 a

, y# V! z$ [8 m: k         $this->set_savename($this->savename);: B- D" J* _8 [, e4 E# f2 M9 |, s% U

9 n# O% B! I0 h: b8 y5 S) N8 f$ D- K

6 T5 j0 _9 K2 ~' L* y, s4 s- Z  8 U. a! j) c* R0 _8 H' h

4 } H' S' c4 L1 w6 @1 |

! c8 I1 B) S2 F+ j5 z( Q         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ; }/ _3 v% F; e0 g

8 V( g3 O% d9 @4 [' O

' a: q5 G! a$ @+ \$ _6 `         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ; u" N2 S, H: E1 F' V

2 F5 Y- B# Y% F1 G

9 H! K1 D; |* z         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); : }0 }3 |: U+ q5 p: V

4 u) G" C9 x$ p8 o0 P- Z

+ c: ]3 |# t+ r- W) N  % g" @: c+ J, j# f5 ]2 I

; Z( c4 X6 ?2 R) T5 K4 q; n

' B: o0 B0 V1 Y- l [( w4 T# q# m4 {         $this->image = $this->is_image(); , p0 p3 \8 U- v9 F( {+ k0 b

" R3 r$ Y! y1 Q

1 a1 c% a# n! e c         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);4 Q* Z1 H- N, _) G5 u4 N

& p+ ?* c: ?9 s; _

$ N* H! c" m0 V4 V9 K7 {) |1 V         return true;# C( x4 z. @1 ^; h% p, ^/ B- [

4 i4 q/ L) v' W, u% [

2 f; X3 J3 w+ h+ P1 D     }}" q! A( h2 V) G( l, S( A

3 N5 E: G( [2 r& J. A' r( P

( D( B3 t& K D5 p 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:0 j) z7 b6 r! N$ k7 ?

7 C0 U1 B2 ^; D* q; U# w

- K" ^" I7 k! F <?php & H* G* h# ^: Y% y. ]: i4 B

. k$ O: d( G4 v5 v( a) U

# v$ e) X0 g/ d8 s) g     function is_allow() { $ X5 d, ~. @% A; w/ ]

% X* g; `" e; q. A) i) B

' ^* p; q- m6 E6 I0 S) z7 I3 k         if(!$this->fileformat) return false;9 S1 o7 v1 A( i- i7 F" p

& J7 r. k' _- x% ~

, L+ o8 a. y7 m/ A# i         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; - h0 T0 u6 e c* r& C. k# ~

1 C/ L$ G4 ~* B0 |

7 a' H& b! y# L$ o7 @         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; $ N* r$ \0 q- j7 i- e" m

/ E' _) _7 P; _; q c# @; b

8 \/ D9 g) R: y5 I* z. o         return true;: E: ]3 A5 s5 j: |

9 Z) [ \4 k& m: T6 m }

' g1 k6 ~0 |8 N& J# Z- N* L     }! \* w' D5 Y" \4 Q7 O

. q4 y( ] \* S

6 S% a2 M7 {# N) C 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 ! K. b2 B( N& u% `/ q, p

! F7 g+ n5 L& _/ L/ r

- V0 p5 h3 i2 W1 F; i4 i- y 接着会进行真正的保存。通过$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 m+ A0 w+ o5 [7 E& S

8 j; h8 c# v6 g# ?% D

! s* a$ V9 V( _ 漏洞利用 2 O6 |! b" v4 W% Z

# p8 ^5 R7 S! `6 w

$ p, j4 q# m2 e D0 h 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。) y7 A( X* K. S; z+ N

% s" _3 V# z5 w9 r$ v& T3 U R

' Y; L7 b8 W- c" a   % K( N: q) E$ m% s) x8 ^. F

# {% f2 \/ P8 v, ]( \3 ?

" a9 y3 \* G w3 K 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 5 j6 ?! i, O6 r/ s

. [. U( Y% I% t" H

) |) N% j! }1 i8 V 不过实际利用上会有一定的限制。 : A! V( W- F" p9 z" s, n) p

0 i0 t' d. `9 K

( Q, M6 ?4 u, E! E) T3 p 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 + d: C2 n$ i! L Y8 h7 ~

@5 D3 ^ a* \: s6 ?/ Y) v/ ~# J

4 D* I1 s5 d' m ]   ]! p1 V, f+ ^6 c

5 D- g. [( F6 Q( c4 A( E& D

7 n; ^. Q% J# T2 t 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: : s. G- J; t6 R# k

7 Y9 M" W( u. A% q( |$ B

$ O" ^, p6 I& Q8 `; _$ d 省略...$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]);省略...& L6 v; ]( X F$ h

$ |) H( s6 K/ W0 n

$ N& b5 W, @% B* v* Z6 n; h( u 因此要利用成功就需要条件竞争了。 : P# u `4 e1 b) i

]6 J+ w k; ?+ ^$ [

8 d$ t/ _: s; f3 ?& m% T 补丁分析 : m U: T# D; Q% z) d2 h! w

1 ^; _/ i* V. C7 M

. d# K1 x. `( U( _* G% j3 m+ m& Q   ) F3 L# \+ H) ]; y3 d" V& t- }; I

+ O/ e& ~4 p# ?4 K; O, \! U& c; Q

' W/ w! J7 L& V$ J) D 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:5 c) V5 |2 B/ t2 D& t( z; i

" i, G9 A; [$ L+ s2 K) x5 x1 |

" k$ x, q" [1 V! l" r function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} " W4 q+ p4 r6 s5 V. p0 r

! @2 s+ k" ?3 P g& |

) I' B1 J V0 [' X3 x2 N" }   4 A L# T9 D9 b4 S

& W u3 X% ^/ j6 A% |1 m3 E

/ v4 Q; T$ H; M* j. \. Y, t 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。( r( X4 a0 K: z9 e* o" l

% b6 g5 X" [; b0 x1 d/ @6 w0 L9 f

3 i2 i: ]/ W5 t6 N* A 在is_allow()中增加对$this->savename的二次检查。( X3 `( e# C$ C6 |7 j

5 _$ g+ H; G* a" y( U* R4 d

" H. V8 P4 ]2 N7 y8 {' ^4 P 最后 0 r3 M9 @; O- }- p

+ X: B. P6 {' Y% a6 `# l

$ M- B, u6 X$ v+ O# \$ M3 C/ j8 U 嘛,祝各位大师傅中秋快乐!# y0 M0 x" q% H2 @$ X! M+ n

l1 z* e% v8 b( @9 ]$ z' H

/ r S* ?) W" K% ~+ T' m   # P+ V" t3 n, l7 N- L# C7 k" g

# A& N3 N. _7 C3 _+ B/ f7 v





欢迎光临 中国网络渗透测试联盟 (https://cobjon.com/) Powered by Discuz! X3.2