中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
- F$ v/ s4 @0 Y( Q$ \. D5 ?

/ [- V( n! y3 {! _

; w" p& |# S" C* |% d4 }. P

# @. _2 L6 d4 P9 p8 {* d- v1 \ 前言 - m8 H' H; {% C5 Q

8 ^ j2 J/ h' x, R. o1 R; h/ H

0 S0 p* n) R( h1 Z. y i j 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。, ` z/ T: a9 I! i* s

, x) r4 Q$ _# o; @0 }; d

6 D5 B. D& _ u   4 m" ?' q# J7 X3 @, p5 |

) |' O5 c g3 ]! _0 |, l

+ V R+ k* {* t1 t& ^ 漏洞分析/ l7 v( ]1 v+ R7 M7 U3 V

. T# ~: s( ~3 D3 S8 @" T+ i

/ q( B. d! j+ j8 x( X 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:/ L5 K5 c: {$ ~% T& B

' [ V/ Y! e4 O# [5 I

) r9 L$ p7 z# x. d) l' Q" @1 h3 k  . x* h0 ^% I" h4 _3 [

9 O- c$ j+ O! ~& d6 r7 x3 X

- U' y- F) @2 C8 b 对应着avatar.inc.php代码如下:9 b: s+ H" N7 } R* O

5 u0 O& B+ V, P

) r# [1 K6 P2 Q$ w2 h& |! L; j <?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) {5 C, v1 z0 `5 \- E1 R! p

9 w% G, x+ C$ v2 j

3 y3 T" c+ x% z8 g W     case 'upload': + {. k( A9 _( a7 D5 N p

* c5 S" s4 \) l4 J$ F' J

+ C# L* [$ ]* z3 A         if(!$_FILES['file']['size']) {* `7 a3 L# u( {2 {9 k( T0 L

$ l' J5 a7 r3 [

: _# v( p) b9 v/ J" V! L9 Z             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); ( |, z& i2 F# ^9 |) w# C( Q" g

* ?( ^7 Y7 n2 W: a

1 y! |# F: W4 M/ A6 c3 E             exit('{"error":1,"message":"Error FILE"}');5 q) Y7 y+ `" h% M. o

1 ~; I# k. L& F: j+ O" m

& x9 A6 Z0 G( |0 s$ E( r         } 8 X8 q6 ]2 ^- h% S) i

9 p' d7 O+ U; `! q' @

+ b7 \$ W ?* _, Z4 H4 S         require DT_ROOT.'/include/upload.class.php';5 {* ~0 i. |8 a! p

! L: P7 _9 f& L. [' u* n" s4 n8 }

5 \& Y8 p: E0 ?5 V! _/ [; S8 E   ; I- y' F, {$ p

+ L# L$ R0 X! Y. g; x6 R

- u* J. C! Y; q# z1 }* K# N2 B1 f         $ext = file_ext($_FILES['file']['name']); 5 ]) L& H7 ?2 {$ ^/ M* {

7 H+ N. L7 a0 u

5 g5 m- l) N& Y- u         $name = 'avatar'.$_userid.'.'.$ext;! d6 D( S$ E. d( A% u. F

+ S3 Y$ y. [1 i8 x$ p* D# l3 N

1 u1 H5 u4 U4 [6 Y- @6 O6 K         $file = DT_ROOT.'/file/temp/'.$name;* a8 a0 ^" \# a3 ?7 R, g. h' X/ y

]& `$ I% x+ X9 l+ |( y/ U

5 L' J$ V- G- v6 `( D   [2 z0 N9 ?6 _* e( @' b% ^

/ p9 |! l" H" C E! N% f Z) R0 s

, p! I! k9 M/ n f+ Q: r         if(is_file($file)) file_del($file);8 K1 K/ t6 S, N

9 I; E Q( D/ O" q. ~, J0 i5 B" t

8 Z8 A( k5 Q l6 @         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');& L! D# B4 o' L% O

2 M. B: Z2 u9 F8 _. ?: _

- k1 J4 ?9 z/ R/ E! d. X; N- V& J: f. J: n  4 H; f6 t4 b, O5 n& Y

) o% \& F3 a. A4 r- y; f- P

1 X; B$ r- w7 t$ u0 d         $upload->adduserid = false; + j8 c' u! W: n; c3 U# F1 |

" P, O' q, H5 y: r/ e

" O" ] x/ ], n* V   ) L, w% ~: M0 V* z0 j

) i; G) G3 `, ?

8 f# i5 x/ Z( L( p. ^6 q }         if($upload->save()) {5 A1 [* U4 K( j, T4 X8 M# i A% P

9 [$ B: L: h$ c9 x4 f

2 N1 @5 e' f. y; _# d             ...' W2 [" z( C v0 y3 F. J# u

0 a6 n4 `& w/ |+ m7 y+ j. P" a/ l$ n! [

: a! M- ?% |( T         } else {! ?- P& s9 {% k8 g( q+ Q

7 m4 W4 |# {2 X( e

4 [- c" t3 D- J; H# x             ...8 Z' |+ K+ _. f7 A" m6 p

0 y( X# t; |, {, p

0 Q% k6 n( n* r# R) s         } Z) p4 m) t! d* m7 ]7 R; I

- d3 i7 ?0 Q- ]. D9 s% A, o

# _0 b! b5 e& u6 ^     break;8 r4 \* z! i$ E$ e; g, {- z: b

3 d0 x9 N$ \4 \& u

1 G! {' u. R4 M1 k8 K 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。; G8 c# h% k& p

: s4 R A# I8 @; V

6 m# O6 a" J1 e$ A. D upload对象构造函数如下,include/upload.class.php:25:( t# R" I1 @8 P' H- g( L

% L- e. O/ `5 s* L

7 @7 C6 H% I% I6 T3 ^( U <?phpclass upload { 5 E; m5 m, k4 Q4 O7 t0 `

, S8 N8 V+ q, ?7 K

, k& _/ c& X, ]1 k     function __construct($_file, $savepath, $savename = '', $fileformat = '') {) x" M6 k7 C9 m7 \$ E

* g% b2 y0 v, J8 F

* _6 f# u7 M) p         global $DT, $_userid; 6 @. i4 K# f1 d( r1 |& @+ E

& }* L7 x# h; t$ k

& p7 q ?' e$ d t7 W         foreach($_file as $file) { 0 \! ]1 Y. v2 ?6 h1 V% o

; b: o7 X8 z, v6 c" k4 M6 e

* `- q# ^7 b7 V: J: y4 Q* U @             $this->file = $file['tmp_name'];7 b- b. g) u3 L1 v. W3 S0 q" }

* S5 e. ~ Q( |# p7 [

# i6 f7 Y1 T) l; e# u! G8 I# K5 w             $this->file_name = $file['name'];3 E+ n @: E) f1 R! w" u8 `

$ F# o* y. W- }' t, k ]! Y. Z. r

0 c3 G7 j% m r) V) a; S' B             $this->file_size = $file['size'];0 f3 f* b8 P2 s, k: k7 w

/ i& r7 P! M! S+ G8 \& |

, T" w) | o9 n, [* P             $this->file_type = $file['type'];& [! } l# P; @- Q

4 i' B7 x6 h& _' g$ m' L

$ I- V- D9 O' B9 }2 @- j# b             $this->file_error = $file['error']; ; K* m: {" [' e# q& l/ b3 E' r

, p9 l6 ?' t; ?- x* a6 U9 d

; n2 P' H6 Z f! }+ C- \$ V7 i. G/ I3 b  % r' ?; x- Y" Y0 J3 W4 o( n

; M. v4 ~2 m0 i+ F9 p

0 a0 {- X: q9 Q: |9 {$ {         }6 f! K! f9 y! w1 w5 e4 k3 Y6 j

) q! _6 k) J2 \' o8 _, r6 f( g! y

+ D. m7 Y7 E) H& k5 d1 \* w9 V$ x         $this->userid = $_userid;$ E' D2 N. Y- b% t! U3 ]

) Q) }2 i% r; {. c

4 B+ C" s+ W6 q         $this->ext = file_ext($this->file_name); ' s# X# }8 n0 T- m' p

, K! ]! t' U( a$ r) h: H/ G! _

9 z, {+ u. E5 N/ B9 D. g         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];, \0 I! E; D4 A) |& T# ]- I

; S- T3 `3 A3 _2 ]

x5 A4 k, _2 \( v; |- E7 R         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; ! ~% u4 M0 y# Z' w, r

, \+ Y% L6 u5 Z* [: b/ B% Z

2 q; [0 F; D4 J# \9 o         $this->savepath = $savepath; ! f9 E$ ^$ p/ O1 b \

4 A M6 n- ~: N, o5 Y/ F

& r! L6 d! _6 @) N2 i         $this->savename = $savename; 3 t3 m% r8 `/ u! t. y8 ~" i8 r6 H

# I* Z# ?# f" J, b5 R+ z

G9 r1 t6 ?5 }+ a( V     }}: W/ S# U$ A$ S- R) e

/ Y5 X1 I3 t2 m, w

, Z$ D8 I; I [. X# h# S; c 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。7 `8 Z/ Y' y6 k/ g, n' \

: p- B5 C& l$ p3 g3 x r+ P, ^0 q, a

$ o c1 W( v- n+ }& C 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 7 j! G$ M" T( m h- z

& g+ A: d4 d: ~: ~; C

9 _! c& h& q+ p $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* |, W6 E7 W$ Z9 [' I! c. I

8 E) K$ ?3 D4 a% @ A- D& `, E& m( o6 x

. f1 I. x- h# {$ `% {# Q# k! w 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 1 t& F9 R1 j0 ?

+ o" i c' g/ v& u" U

- v2 {# J+ p6 ] Q  4 \. k5 h# m3 M! W

1 k( Q" b" ?9 l+ V

. v, N) h2 Q R+ B& I7 [ 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 3 G' s: b3 s0 O& P9 s

) o8 Z3 A7 G4 x- d

/ q: R6 N5 l) z2 p% O" w. x; F <?phpclass upload {8 l, g- c- `: \) L6 B. a9 i

6 I0 G/ J m" Q) j: ~

* B0 b3 Q; r. p2 I3 G3 J     function save() {/ H- z2 [) O- {$ v& S

2 d3 @) Y# d5 ]" H* u

/ Y* B% C9 ^; N$ g# M         include load('include.lang'); 7 V7 W) V' R- o( o$ A4 j

1 F4 L2 Y/ ]& R. p

* J) `, i7 L3 W; V         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 0 g1 |! n; ]0 c$ |

2 @4 L5 t c, H& _# R$ q

5 \4 e9 z1 |4 [! l  0 T% E1 k: {+ D; P3 i, o% I

" ]# B9 x4 s: ?& x0 M' @

+ _8 o2 P7 a" q; U3 J5 ~$ j) C         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); * b/ @$ J3 ]6 j N$ S. V* p4 k

* o& i( v8 F" ^! t' G5 |4 [8 `' ~

( d$ K, F! x0 h b   & E+ l$ ^0 F! {% I

. e$ T: B) x) @9 I! J$ E( @

3 x0 |( A5 i; a, Y; ^' @         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); C% q6 O' X$ N! ?

* F* q, h# |' [: ?

. A. E( `* M0 G   3 V+ p. a0 w7 S5 Z" l6 M0 D. e

- t3 c* o( I# ~/ F7 ^7 j0 |

/ j& q9 I) \. e5 B# `9 y         $this->set_savepath($this->savepath); ( M L4 F( e- B$ {8 ]2 G8 G

. ]" k# w) M( u) ?2 m* e& a6 B' K

4 d% _' d- W" O. z ?         $this->set_savename($this->savename);: B5 o2 {. D- ?; g

8 x, {1 W/ K& R2 ^: X

: U- n% v4 ~, f7 D* S  $ L% p: G8 ]- O* h3 U A

5 F; A7 J( f" Y& A) y; {

& C; ~ R9 h8 e" }7 n1 `( c         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 8 _9 h+ \3 k3 ~- {* p" ^

* p$ D6 b$ }6 x' z

2 g# g; `$ N! [2 e0 V         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);, G0 c- n' y9 @5 e9 Y3 @

0 u4 A1 f9 p! u8 L; |! X" W

) ^4 P/ ~0 T, _1 y         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);. D7 p/ Y% H( k2 ^) V( B

1 _2 k+ H9 H( E( r# f

, B# b f! i5 {6 a2 z: O! @8 n2 A  & [/ }# H: S& u. J

, E9 f1 l9 w9 G2 ~) _. Y6 ^6 r

' E# j b3 K' ~         $this->image = $this->is_image(); ; @8 @$ F1 g4 `5 j, r

, c, G' p. F9 Z% l7 G2 N

( Q v5 W' ]! q& n+ M2 B         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);% Y# L+ f- j: Y

* q, O- A0 j. b3 ~& I% B+ X% C. {

% n) P) G2 c* j3 a1 `         return true;' e& \+ B" P# L4 W; [: p v' f! I

% i. I& X: G. G7 v7 N8 |. K/ Q

% @1 o3 V5 P7 p, i, q( W     }}8 G4 J+ T1 a8 d7 ?8 O0 l7 D8 ^

; s- b3 m% U9 k( N# Q

9 U a& }% o v, e 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: $ x: N0 e8 R" b+ c: N

" \' Q1 ^( Q7 z- H. x6 w* F7 t! S

; q7 ]4 }. i6 s7 @0 d) N <?php . C0 }( D/ b0 H( M8 o

, \$ u" z R) L1 F3 C

$ y$ m! ]. P/ V8 f7 B# M     function is_allow() { ' j; h* d6 {- i3 ]1 ~

8 y7 O0 `9 x1 o# `

9 B: R! Q' O6 |( t# E% e$ ^         if(!$this->fileformat) return false; : R, [4 _3 K5 K2 F

6 h% ]9 L: E; v

; m- ~6 X) Q$ R         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; W5 e; ?5 D6 |5 f- a

9 [% t% _& A7 O3 X3 s# e3 r

! }6 v! W* p( L) u: o         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; $ P3 w6 x2 W5 X% K2 \; ^3 g

- A) w7 x- f) \# ~+ r3 m

. c8 c5 ]4 e) y8 X+ b- }/ k, {/ j         return true; ]0 v: c y1 T: x, G

5 D* X6 c0 q% A# g2 \( k

$ W, b2 p2 r) s, [* ?     }9 \0 f# F- y; r/ W' n

9 D: J- Z& B# o/ u

2 P$ {4 R1 t+ W2 l$ O 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。# a+ j9 O. s9 R2 A, H

( C+ a! \+ f1 T1 b$ ]) T

6 \( A+ F' n& @% J 接着会进行真正的保存。通过$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文件。 ) i0 F1 n- a2 `! u# L6 V6 W" O+ g& U

* L: m7 V7 w. d, p

: q7 M5 K* F" g# Z& O 漏洞利用 $ G* [6 d! R: q% @( B- f

+ n/ m' ^! c( @5 D- w5 o5 k

2 E c! H) o: `" n1 Z4 v 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。& s) x1 ~2 B0 D H6 s

5 ~2 O. k3 D' P4 m1 {

0 `3 m* U- X/ s9 J- T4 g3 {   # @% H; k; o2 Q

4 L8 M. U5 O* I

$ q9 p: Y, F6 w 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid , C* b! w* V* F1 K

+ q6 E: }( u2 N

# L) L. D6 n! C 不过实际利用上会有一定的限制。 9 y5 Y& c! W4 [& ?( X8 B. w% q

3 u! i5 t. R5 l; k+ W% F9 q

0 C7 C, |2 m. _. f7 Q& D 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 0 x1 l4 |/ Z3 K" u2 V2 s

( ^% C/ `: K' |% ^1 G

( x# Q% J) P0 }0 r   7 h9 y4 B7 F7 Z# X7 [% ~

/ \9 a' T7 l* U' b4 t+ t/ E

: K4 l% b& O5 u3 S& a5 |' K- O8 ] 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:$ O6 J4 M" w8 V' k$ q* L) N

3 p" L, F7 Y/ s" l8 N; Q) _

( f- j) j4 h" W. ^: l 省略...$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]);省略... : Z5 a" z% Y9 I/ w

/ k2 D& M" c' u [- A

+ q- N! f: f2 f6 x 因此要利用成功就需要条件竞争了。) L, B; c" j& O: I" r

) u1 S2 ^4 |( B2 o

" U; U& o' S3 o. I6 K: o$ R0 i 补丁分析 , _2 [9 ^ z2 m. Z/ C

( \5 j) \! S# T5 C; n/ z

* F; p# S5 `% A4 S ]/ K) o  3 [* |8 U! I9 M

0 [! |' t6 G$ ?: j- Q- R N

! p, T, K8 l+ K" g# U' |, _7 x" p% K& a 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: , H! [1 b9 `8 j/ G5 x1 P

/ ?0 P( w, G8 L0 |. _2 F" i( X

, q% a/ x- v7 a5 k! D* A9 O' y0 ] function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ! L. c0 R2 X! v4 l3 J

3 h( N5 p% x8 d7 _6 P' }

. d% `! b2 M4 l5 x, N8 {/ {   9 I+ `' D B2 D) C9 [; x* R3 Q

0 d; [4 r& ?9 p3 m* Y

0 [" v" D1 o6 B 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 " t4 k& Y: N% U# r$ i9 a

( r* t0 D. e4 W( n7 r/ X

3 X, P! m# p5 [" J g! N* X) J 在is_allow()中增加对$this->savename的二次检查。 , ]7 E& m* l3 @& ~

/ Q( o6 ~4 V s4 \) b5 F

* u) w9 M/ U! t& Q, J8 o 最后 6 n o T% P* |+ v, U! a

5 s- T$ ^9 x0 Z, d5 A7 O+ z7 s- C

/ e! w' Q, m: T 嘛,祝各位大师傅中秋快乐! " S& E. |: p6 I3 Q8 a6 i1 [; c

2 j6 r# [/ S. \1 p- n

6 _9 k' h( w( c   0 \& b' e3 @& J, @' D2 [# s

+ U8 o- m' D( ?. K# ~





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