中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
i& k% r% Z5 K E# B

( Q) o U& _& ^8 X! V2 r( j

+ l$ q- J, [/ B. b r9 v4 J9 v

; ^& z0 u0 {( l3 Q- S7 ^& G 前言 ( J! m+ f* _9 G0 k- W4 d

1 `+ p/ Z7 e1 z5 B

9 T' A. P* ~/ C0 w0 N9 v 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 / p( h- b. \0 k4 S$ F4 A# p

# C% f3 y7 J( D4 L# a' U) V

* M( ?( d* ^! |1 a  % v1 M2 x5 I5 |+ y; A* d+ ~

% l% V8 @8 S1 w1 G) E

. S! d; ]1 X* n1 Q/ l6 S# Q5 ? 漏洞分析. b9 }5 j0 F3 o0 W

' n3 O# \7 s( P2 y

' u8 L* z; Y, j# k# n" E# E 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:$ M% p& \ u3 s: B1 @4 z3 a

5 {2 i A' O A' ]7 }4 u

- |( L! l, E0 P* R. ]3 ?! H   . w) U+ h4 V4 f3 J% ~$ e* L

, p; ?0 j9 w: R) d2 I' E

1 c0 e% `& u$ M# s. Q3 T 对应着avatar.inc.php代码如下: 6 m* n! d6 h) G$ i' ?" R+ t* ^# U

2 R7 c2 ]: G8 |3 p! C! P h

+ K) V( u7 h5 p: @9 P <?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) {. @) k. a- u H1 H

$ K* N# q4 t4 m/ R0 Y6 }

7 ~9 Z7 S& l. h0 T: f {% s* t4 L     case 'upload':$ G& A# k3 Z! i$ X1 _+ c4 w

8 q, E0 w# t5 X; ~5 N' q! a% v

( \& j' G: N9 ]/ L         if(!$_FILES['file']['size']) {! v; M3 f( A+ p: d# A& [. e

1 P1 ]$ N% q8 O7 F1 Z

0 W ]& C9 E7 ]- S' X) l8 K g) O             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); K/ d9 w, _, o7 v& ]" p

# G% ]+ r5 z% O# U5 n

6 J" w1 D2 Y% v9 @+ r, i             exit('{"error":1,"message":"Error FILE"}');7 v" ~/ w" Y8 c# {

; J8 L, ^5 j% s1 Z1 X7 d& `" k

4 i7 O! j- q2 Q* u, C) t1 L9 r; c         }/ v" S; j3 l" h

! e* _* u# g, b% ^

g/ Y7 R! p6 w9 X         require DT_ROOT.'/include/upload.class.php'; * z) s& z& K/ \" D( Q

& p, b, V! R. A& L* J' f0 G3 o( B

* ]/ p* j, l7 `0 A: b  8 f" U" D: H( s ^/ `, |; B

1 h! Z1 t! E/ X6 b' @

4 w1 D6 {6 H( B' M! k         $ext = file_ext($_FILES['file']['name']);0 ?* T9 F( c6 a+ Z4 W! ~

8 R+ S+ c4 g6 U' `

5 A$ s, y; p3 U3 N; r5 q         $name = 'avatar'.$_userid.'.'.$ext; # H4 K J: L+ d/ U& @+ Y3 ]6 M' H

$ A2 g! L5 l3 h) A7 [

! ~' I$ t6 y& A- V6 Z! X         $file = DT_ROOT.'/file/temp/'.$name;0 Q6 j. x- ?: A& N

& v5 \# U. @$ }# U6 e; Y

0 e- d5 g) g( R/ K$ q7 N   ; M& Q- P7 w# ~ m) ?

- }# ~4 ?1 W5 p& n# w

/ D4 \$ l" I! }: B         if(is_file($file)) file_del($file);4 i7 F' o, M- F2 V$ P0 w+ t6 |

6 o! Q" X6 q2 i1 k: ^! N/ F

2 Q6 S8 A2 T7 t" Q6 K9 E) ?         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); R" g$ v8 L8 W1 d J

& r. i5 \& v$ e) v

' \. i X+ @' I! A   0 |* W1 G, K+ U, c# ^- N

2 |5 n. o5 z& r, m7 t

9 A3 u1 ]2 P' B7 {* F6 u2 w# I5 n         $upload->adduserid = false;4 F' E1 \" Y7 S3 z5 k# X! `

2 @% i: ~7 r& \9 W- o# U3 H

! g, K3 ^6 z. _; N4 Q) Q, {; P   / K4 `7 B. D2 U# z. _2 P8 E3 E+ g

$ x, s3 z1 ?6 u' Q: u, }

3 o+ Z# g9 m; U. E( ]3 F         if($upload->save()) { 6 J3 d) C" f2 Q% @3 C8 c2 Z5 P. F

. }6 T( A! `4 k$ W: n3 |% l: [

7 d: |- O3 k( }1 r: R             ...% c- c7 M1 c6 ^1 Q7 m

# l0 h& c" ~1 h1 K

, ~& z8 B" U$ L8 w1 x; _         } else {% }% L8 p& ~7 s& M

y1 ^# }+ ^8 G

. ^+ I2 a. S7 W8 z2 X: {: M8 J             ...1 a( v5 e+ I; C& X& p# N

& h2 P+ {8 O; V% b

& [7 D9 K! G7 @# ?( {         }: M# ]. t1 ?& B U$ `; z% @

; K7 ~- B/ ~5 G8 i( |& Y. R

) r5 h% Y [8 b1 Z1 h     break;7 F" p: f5 e9 d) F5 q4 o2 Z) C% s

" I& L- M8 Q/ @ }

" W5 ^* J3 v0 \ c" S+ p" J/ B- V% { 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ' x) T. H8 G; T2 \% g/ \

N) I/ m# N% w

R8 ~: }$ f6 ^ f" o/ }) { upload对象构造函数如下,include/upload.class.php:25:$ I; v' v) Q# L4 P6 C

! b0 |: V( z4 H# X

1 N" g: U8 D5 J; a7 Z, b <?phpclass upload {( }8 X, h( T d. T

& v2 g0 N0 C- D

y/ L! e/ u6 F% ^     function __construct($_file, $savepath, $savename = '', $fileformat = '') { p5 M' V3 n+ x% s8 C- F' K

7 ^5 B5 _1 L5 `( }6 I/ g1 x

! S! R6 ^1 Z) C5 X         global $DT, $_userid; 7 V- e5 T, e5 u% d

5 G% C8 M* j3 S% w- |) f$ c

/ h+ l! W) o8 `         foreach($_file as $file) { % S2 k E& B9 U( x/ N& Y

* Y* Z0 G7 |% r3 ]1 f- v+ m

% g! s, s& j! }% J3 c( u8 O             $this->file = $file['tmp_name'];2 T7 v$ [6 P" {, G8 H% r9 Y

0 ~) n* q) u. A

) O9 P" s1 I6 S$ D6 k             $this->file_name = $file['name']; , R5 \7 T* l- A3 X3 [3 H

) C9 S( h+ g2 _* W

2 D: {2 L3 f) ]             $this->file_size = $file['size'];( E3 q' n9 R5 U3 n% H. }1 e9 g2 L

/ c. p# @8 A; {8 J- q& H

" \# u( R: l, ?; v/ w) w J' r3 p             $this->file_type = $file['type'];4 |( Q5 q W; K0 {' |3 O

9 @; Z# G" ~6 n: b

$ L) b e0 ?& B6 a             $this->file_error = $file['error']; 0 u$ J7 l7 f) Y% m( n

3 q' a0 ]! v( i( l1 @2 J

% v+ p7 D. \3 s5 x1 d   4 o. Q! z @6 E# s

+ S3 d& ~& K$ a3 Y" d

7 ~# x: \$ W# A4 Q         }( h8 o' C, P8 D9 `

4 G. j; M% [( @1 `2 ?

. F8 u5 {# R6 W         $this->userid = $_userid;5 O" Q* n8 i8 F! F Y t

Q) m7 g! L7 ?% e* a+ l

1 t4 L1 a% ?4 k# R4 L0 a         $this->ext = file_ext($this->file_name); , M0 H" p6 u U% }

; u; j! g X7 t

) h0 p% S- Q, s, H( _, e0 v         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 5 w3 t( s. e+ ]& M

8 ]$ K# y0 Q# i( [& O

& d& S c1 N& E$ X9 F0 o4 U         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; - [- g. \. `+ J: m

0 D. n0 z8 y) q; ]) }3 t& z

1 k) ^- j- {' Z         $this->savepath = $savepath;6 f' ~6 O' t" d9 Z+ ^6 _4 ~/ T

& O3 r* |+ ?: R# j( g- J

% L' r7 @' ^- ?, |, D3 w         $this->savename = $savename; # `5 N8 L, Q8 U% f/ ^' @

0 u, |2 ]8 P) b9 d$ A

4 u) ]$ q. e' i# _     }} & r7 M8 Z9 {) L/ C0 h

9 p# n, Q# \/ z/ b$ E& c

9 N5 u5 A( r" A, Q 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 ( I7 a: g$ Q, [6 a

2 j6 e! C, z# A' \0 L' u% }

3 c' @2 O. O. y/ s 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ! X0 L9 f1 Q- Q5 t4 L; C. I) H4 B

( r9 ^0 u& h, C6 u$ q" i

) h% e" c9 S8 o8 P) I% A) X $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& n3 o- {5 g1 P" o* N- F; o

3 ^1 y: {# Q/ D7 b' i0 y

* ^# |% g* O# h$ E- l! _0 f 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: + d. f, M4 k! @, G4 E" D

* Y$ ?* v6 J3 L$ M2 D1 U# f9 O

& m5 I) @& `- V$ S* x: L  ; _5 m4 z) t" l8 m5 K4 ]

$ N- S6 f5 u/ ?) o; C/ d

7 [: e3 B: [+ J/ `5 d* o 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: # F3 [' W5 h% t8 ^* s F

: g& | N8 _1 \

# i% G/ m- w$ }& t" ^ <?phpclass upload { 2 i: d. B% N h$ ]0 y- |. G

% Q5 d! l4 F( g/ s) N7 T$ p

s$ U# p( B% ]     function save() { 1 r/ H8 C" P; a* u0 T

9 q! j, e3 n# t$ y0 D: Y8 {

2 _6 L9 J$ ~( v& f- Z$ y4 E! E         include load('include.lang'); % @$ ?: N' P0 W

# t9 P$ n( A+ y: U5 }

0 F/ @: h F7 d% r' Y" p5 S         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 2 I6 e. T0 ^1 h3 }( V7 x" @3 O

1 m, G6 J- t( w

6 H5 U0 a x, s. M: [   5 _5 B. d% b5 p: G

0 ^! D. p/ z# ]$ C4 U

& B/ b+ H+ h* t& G% P1 C         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 W1 u( m4 v$ c9 F

5 R4 x# p0 T, y8 v, n% g+ _- u

k8 g9 P/ E8 E  * P1 r( l) V% K$ b; S6 H

d2 c- _% |. H/ g( Y

+ k% U1 z5 B3 A0 p         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ; ?7 ^: |5 b: c# u3 A& |8 m

9 w4 V# F! S! [4 f

' g% L4 c2 x' [   $ O W* L: f* z! O- ^

1 O6 ~, |) _1 F7 p: |% {3 W8 c

( T& k) N6 _# n ?" ]9 M6 M2 e         $this->set_savepath($this->savepath);8 \& I+ i' k* }: l y

/ E* [1 H$ `& Q0 p2 i

, Z% D! B3 Y; N1 _         $this->set_savename($this->savename); ! Z4 I% {$ M! x3 d; ?4 E( h7 Q

0 L. [ {2 [& j+ V9 } h* z

8 g. N+ ]2 j7 s7 J( z  / q' x h; d% s1 u/ g5 U

, b& C9 V3 w+ K8 X) p! ^

- E, r) l& H, d3 D# r! g7 O) M" K         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ! [& c; C X# Y) f+ p, K

9 i/ t R% x0 v: o$ A1 D) ?1 z9 K

7 p6 o) z5 V5 d! ]' P. t         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 8 H0 }# P3 N" {7 _5 C6 d3 S

# O4 B/ w$ }$ ?" p

+ W3 x t' ]: [ w& O8 c         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 4 ]6 M9 z- }% D H1 a

9 n% u/ R- t' P( g

. C2 z5 s& _- J2 c2 W2 C: ]' o# n  , v% y) a2 S& A4 j- O

% x( p* p7 A9 b3 ^" x

9 s# X0 x9 G7 n) d- o* M8 {& n         $this->image = $this->is_image(); 0 ^: n% q6 R1 a% `3 H

2 b0 y3 P7 u* ?: V0 ^, s

# s7 R2 z, N7 |/ F& L         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);7 {# r" L2 R7 I7 a7 K

# f. G Q* w9 S" u1 n- j3 w

9 @' n7 x. x" f2 y/ [- b' U1 c         return true;( s0 f' V9 r5 |2 c

g! [) t3 ^8 _( y7 p) M

1 H0 V, i* D/ s" }. X$ T     }}# o! R/ X& M, V& V

2 N$ c- M6 ?$ N% `, W* N7 @0 @

" c5 J4 @, p8 C% r. M5 @: U4 G1 ? 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:9 o, A6 }$ @; P$ K6 s8 N

. d; h6 q7 J0 j1 h

0 r8 q6 }5 e9 F <?php 4 _: `6 b6 ]8 K

- M. [& R/ J# \1 h" k. T% e

3 `7 U' t& {9 {% I8 a N     function is_allow() { ) v* j8 G1 E1 w5 K( S: g

3 }6 u d1 w. y6 D

! e5 z; i1 U0 |% @7 A+ ^+ t4 j         if(!$this->fileformat) return false; 0 C/ @+ [/ ?2 \# \3 C- ?, \; a' j

0 Q6 }0 e$ Y6 |' f+ |- I

5 a/ p ~+ z- u0 ?; n" A; \         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 p$ s7 T8 I7 h9 J1 d

, ~2 Z7 @# X0 ~# w

, l1 D7 k* L' g6 Q         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;3 g' g1 V* N$ [, {

# q- Z, @' D5 n$ ` {* a

9 a, x- W3 B/ f! _' z: V% E         return true; & z7 r9 ~: @- e; {7 C- ~$ Z

6 `% T t: M! Z- b

. P2 C5 z; N' X* a4 C     }+ [1 k+ M0 l! V9 z% b! r6 z: X

& ?& l9 s; A" _( s2 l

- v) y, w6 j( a3 E, Q 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。2 ]8 b ?* E E

+ R/ m% ?+ N! H2 w' n: A# E

/ H8 P3 T4 r2 H L; [+ N7 b' A- s 接着会进行真正的保存。通过$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文件。, m! V- _& E7 P2 a3 u) C# [' V

7 x" n: o3 L% P! u. X

; }0 r8 d8 G( ]- L+ {) Q! |& W 漏洞利用3 g) G8 Y: m6 Q9 ]% ~" P( b0 q

, [: h( g# s: t& v

% |$ d$ i7 b' {7 f 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 + f# \7 u3 l( O7 i' @' b5 R9 j

2 i$ l; W' ]- [7 I

+ T/ f- \8 W" r4 p   , u+ q% |' g. q/ Y+ q

8 ^7 X, e9 z& T- q% j: |4 i

5 c8 z r" | y0 u$ s- D4 { 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid : ]* W$ Y& ^. \' Y

( s5 `3 |( f# T o, ~

# m* [- { z* K& l8 W 不过实际利用上会有一定的限制。 , L' j& I7 F9 A2 u

* S# X, s5 G2 Y% r! A9 C2 v, S! J& E

: U2 {" J' F( z( F; M7 e 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 ( F/ R% z; F) i3 ~% i

; \$ i/ [, S. G( y' G: j% Z# e8 ]

/ L. ^* @. B8 H  ) U, a9 k, C! F: @( }- X" h" [

/ \3 F6 T1 i. } O9 r c

( n8 R0 R! M- S 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: , m% _/ E. Z) E4 c' s+ h2 ]

5 @$ ~5 O7 ^! e' |

( J( f* j& b5 x- c+ D0 s3 V 省略...$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]);省略...* Y# i& H: j2 |

) r; x8 m8 F7 J8 A

; b5 [ C9 d7 D; ]& } 因此要利用成功就需要条件竞争了。: C5 F% l/ {: R* L: E- Q

1 ], n2 ~- A% D5 I% U# I3 l

N0 u' W5 V, W7 N! J 补丁分析 4 @, ?. g# J) I% @/ w

$ W3 i: ]; g' ^) c

: S3 f* |- _& f# _' N6 R/ E   8 n8 X% _3 d5 `! R$ i$ T

+ M3 s3 z( Q% D9 W* @: O, t

+ x) l. l, @0 q3 `- f# r 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: * x. j' X2 a' {! P5 a7 m( a

7 o- S$ w9 l6 a3 I: }# P! t

: s8 p/ I/ r+ C: P- w7 K function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} % R e, M5 H1 X: n( \2 m+ `

1 ~1 Y: S9 w8 ?0 i2 f( ~9 w' V

- |% I/ g3 A3 T0 x0 l. y   ) O+ q6 I; u* Q

: X. ^4 d% E# g5 V* r' H% ^- G$ s

7 [. d- e7 T: j0 X% f2 i3 M- Q 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 2 E% E# X6 B$ C* X3 F

/ c( N( z6 ^; ~# o( U2 T5 m3 |

2 z% L9 ~- S9 }( ]3 O' y7 R 在is_allow()中增加对$this->savename的二次检查。 - I( R# G2 i* T

' b! f& Y) S: ?

# k4 A4 c: @2 P, W. s% }$ m R 最后8 r1 |" H6 O9 O4 X

; N9 A+ \, m) x2 ?& C; E. f6 v

3 D( H [! m/ ^- H 嘛,祝各位大师傅中秋快乐! ' R) L! f! Q$ {; w) ?

5 Y8 E, \/ k: A; a5 ~& L

H: m8 ~+ |1 D( {, l# W4 \/ e7 \) |  + R, J, A3 n5 x

6 K$ n% E% Y& p w7 }0 G9 p





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