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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
( \& B2 i. v' l. m5 O9 E5 E4 o* x

' @1 G' l8 M }; ]: d: B- n- [

8 b/ P' |) f* B3 ^

" S4 r5 J* |( P R) y 前言 - E9 V# J7 [6 f

" M- O, J) w& I5 l- n+ E% s

6 }& w) z; ~" p 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 & ~0 c" ` y7 M1 ]6 d

|& u% o4 G! H8 R6 d0 U

! m. [- o( f# M6 C% ~/ p2 k' i* _  ' |3 `! N+ g7 }8 l( x

1 N- L7 g( U v5 D

! S$ M" M4 m3 ^' t: d% T) I 漏洞分析 " u! ~% S3 L5 L

1 n0 c, t9 \1 i3 {% s

% B1 E5 ]* K# z3 n% ^; [/ u) \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: . v+ k: b. {4 G$ D

7 x/ ~4 R. _* L& w) p' @

3 }# S0 d" E/ Z( |  0 E9 V# h4 v" U

V3 X/ o* F4 W8 q& P6 B

, t7 _1 K3 r9 @9 Q3 b: o7 _: Y 对应着avatar.inc.php代码如下:( I, G) x# s; P; Z

' }' I3 U3 v- y1 z8 K

) V, y& s- h+ s- m) w <?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) {- Y% o+ l+ T$ Y W9 P& w% c

, Z, \/ X# O4 N- a' h y" P

. W( s* R! R. q     case 'upload': ; n i/ {" I. S2 D, ]- V

) ?& z" @, |$ J- \* C

) E% n9 I3 H3 \" n+ p         if(!$_FILES['file']['size']) {7 I$ b! ^! }4 h2 E5 w

0 p( O( g1 Y' A* {/ g% W

9 [" C1 d K) J$ H5 b0 }8 n7 u' S0 b             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 2 W O0 x0 N6 b

- g) ~6 Q9 n( |) v

/ b7 V8 }5 ]: t$ I! C6 X             exit('{"error":1,"message":"Error FILE"}');8 ?, z6 p) Z$ c' Y5 u

! W; X1 |9 U7 j+ P* I y

2 i9 n, y, o& j9 p3 B         }8 e5 J' @3 H7 v g3 U% [. F+ m. k

' @ c8 Y v/ D) z# @

q E9 N# q+ ]6 _9 }+ q         require DT_ROOT.'/include/upload.class.php'; B' F! ?$ S: g0 A6 h

/ r. G1 E. z/ B5 Q& G1 [

9 d2 i0 P f. c9 t   ~7 f. o- i6 i7 ^* K

2 }6 U9 D7 C$ q4 f

9 ~& @$ t7 y Z; s         $ext = file_ext($_FILES['file']['name']);! j. [5 ]& H! @! i/ [+ j

9 k |% u7 j+ @; V% F

) ~5 s/ b/ z& J7 a. j         $name = 'avatar'.$_userid.'.'.$ext;9 n* h9 n0 h' V Y9 D/ Q* K; D1 q ^

8 u% F; Y" F4 |6 }% `' d' J

: N4 y! Z! c; h' V         $file = DT_ROOT.'/file/temp/'.$name; . _' ?' ~/ F2 p. K: E G

3 L( }+ P! b/ A G$ E

5 ^7 x3 B& y. ?& @! n   $ {) L6 ]& p' {1 T& X B7 s& G' T

' { y% H) f$ n0 N

2 ~! `# c/ q, i# R# S         if(is_file($file)) file_del($file); - Z. u; E/ H8 z; H# W1 C

' ~# t# @1 @# q* L' |

+ w$ q. J* v. t9 X8 n5 Z         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');; k$ p: _5 Q$ Y- }$ y+ j Y

9 l- }( u7 q) B5 {

7 i; @$ i, |/ Z8 \5 I7 V& }   4 L+ p( ?! i# f5 S5 k

; X& e; B$ r! |" {

/ ~3 m6 {& }/ s2 Q3 M) B3 z* V% J         $upload->adduserid = false;1 F" J/ ~' [* |% _+ f

0 Q+ U& h7 R w! ]

- I! e" d+ {( _6 h& \+ w: L   * ~; x9 h d7 c1 [4 I

2 l. ?; a- N1 }% ~( g

: \* }1 c Q9 b% W) N* K# H         if($upload->save()) {2 N( l+ N5 D( W" | P+ I& j2 C+ C

7 w& [* n! l8 B7 f

1 B% N7 R4 L- ^5 J2 x             ... ; G; ]1 l1 c {3 M& i6 C

4 s0 L# _! c7 Y# j3 W

- s* Q: D+ M8 X( a( r) v! j         } else {+ z0 f5 I# p3 s

' J- r/ K) J) u- _+ {

$ E4 T; _1 k0 ?2 X, d7 q             ... + @9 N2 ~- W g5 z, O

+ X1 f' @/ T- A; S3 v) [3 g; @

* l8 o( G( v3 g3 S' k$ G! v6 l" G         }2 n' m* V; A% c! f' K$ m

/ d9 {1 D" m6 t

' G8 s0 P" k. A* o" V# Y9 Q     break;& ?, B1 E5 r5 R# m

+ Z1 p, J& Q- `% M- c0 r, @

* r# u: K1 _" h& y 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 & F* a2 K! [" {/ t0 Z$ E

3 c( O; C$ |/ l3 W/ a6 R0 n

9 ?" X4 v) l" G% \5 J upload对象构造函数如下,include/upload.class.php:25: 5 P* Y, J- ?6 [$ m0 y

/ O0 f' x! D* I( I

' k! p/ ^7 s9 F8 B+ m/ [ <?phpclass upload {: ?2 p: x( R9 X3 f& M# e$ a

3 a% f; w, n" e$ k5 u

+ V4 F' d* j" l# _# x     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 1 L/ ]% N7 k( y6 j2 d; x! ?! a

' X8 [( |, ?! N! d) s8 I1 r

/ u }+ W9 r" q+ ]         global $DT, $_userid;% b% z# s: u( @

# n6 k; s0 |. d, x/ ^) S1 T: v

' W0 |4 _! v% }( _( o2 p         foreach($_file as $file) {. t6 \7 [3 m3 C; G I

9 Q0 f. P( A: r0 H) n) f! n

$ F' k/ B6 Z: ^. q8 C3 n+ u" P             $this->file = $file['tmp_name'];) ~* h# O+ b; ~5 e' A

7 R1 n }, R1 R0 S

8 p8 ?0 t3 F* Z3 Z3 C7 o$ T7 q/ e             $this->file_name = $file['name'];+ U" d: @* F3 e& |

+ {- a' S8 j# s; |; y# L# w

8 b7 {: T# j$ W             $this->file_size = $file['size']; 0 l: W5 i* j3 K; M. |; p% y

; ?; h1 F# D' ~

% [- p/ U3 t8 ?' {$ H# h             $this->file_type = $file['type'];' M0 Q( ?8 b7 t' m$ c; D

& S* \( ]. s& {5 u. F, o

. i+ T/ H0 \; C4 q             $this->file_error = $file['error'];# u8 b1 a; H/ l" U& I* E

! _2 j" Q- [; n% ~( p" v. |. R

$ e: l c6 s) B1 w   + H- U ^- t+ V: u, A+ v4 \# ]% I$ E* J

. q" S: S7 P ^

/ r( t" x3 T, z8 d% e, P         } f4 p5 ^6 p% W% I& Q4 Q

( k+ a- ^. m6 w4 R! \; A

8 T! W3 B4 n/ v: V- n         $this->userid = $_userid; , A2 w2 c) r# `. |8 H- F

7 {8 z0 _, w+ ^" Y

; n* u; Z8 m5 b3 t" }) e \9 Q0 m3 A         $this->ext = file_ext($this->file_name); 5 g# z; j" n, N. E6 k+ V; |

% x$ |' t) _' T2 z5 t: x- Q: D

2 |) E5 y5 Z7 g3 W         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; ; L- {% d0 U- c8 w

" y5 e' [* Z8 _7 z" B5 O0 Q4 R o

6 D/ L2 v3 ^$ `$ @+ `- e         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; & U, v( E2 q: W

, g) ~* M: T& e. U3 T

4 u) F: f$ N2 c) H8 K         $this->savepath = $savepath;6 z" R( V( N/ b. [

* b8 r' B; a$ x( k* Q h4 a4 D

% F2 o" G2 T; O4 F7 l) m: Z         $this->savename = $savename; 8 {4 t; f+ O' G" G% E$ r" h

- v& ~' X& I6 a( o2 e

J a3 {9 E% V" m     }} * C) X1 \) }8 V

8 n+ B% j* k P+ {

8 ~+ |0 t, R: V! q) d/ { 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。) Z* I' ?, k3 b3 d

* p; i6 Q! q9 K

, U* A% o% B) i5 L 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 # m; v9 s" b4 ]/ j# q$ c2 A

4 q* C5 x' a7 u& @

7 q0 k& O! D8 @5 M2 Q8 Z $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 - d- U0 b, C9 y+ {& _- h" G

2 B5 K3 z0 A$ |1 }# i3 p

5 I6 b- F5 a$ E @" R 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 3 s! |; |+ R: g* A7 W; C

2 F! c8 \7 q0 e# m8 W/ t

$ ?" {. L/ B/ E4 J' \ m  + Q/ S( H: i. p' F7 H$ q6 P/ X1 R

% ~9 Y+ {/ N" o U5 l5 e

# F" x2 P) L" }& b 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: $ M3 f0 d; X) T D' H/ Y% |

; w, Q) Z( \. O

6 E' z( n. e7 N) K4 h# } p <?phpclass upload {- l" o; Q% G' }' L) c# }

$ j5 @: U' H' d8 U

1 i0 T! k: m: C% a* {4 O     function save() {7 l" e/ |6 ` N' j" E+ Z8 |" r/ b

" g! H q" ]/ X' ]2 o" C2 S

& i9 @0 Z- K: K2 P7 U         include load('include.lang');; a! h% |; _# j8 A6 o0 f; W

5 s, e1 ~5 D7 \- D6 e

- a( h; o3 M8 ~. U         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ! M2 \" j& ?% T6 ?

. P- c9 Y) F# `/ M/ r3 ?2 h2 ]

7 z! L6 ?; `2 V9 H6 ?  6 l3 }: g# y3 }4 K! F0 ]# [

/ \" s6 Y3 z; E/ Q& R5 m9 @

; r9 u, C. \; k6 R0 S$ l         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');2 h" e3 a# V# E' w+ s' a: d2 `

, g0 m- u+ k' D- P" n

9 P7 J. C9 N' X   * _: A% a! z; k

, n9 q D3 v3 t/ s' }! c

. E+ T8 @: `' _6 B& ?         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 7 F8 @+ {6 {3 G0 j2 i: _3 d

0 U, Q4 p& E6 S: z& ^) W2 V

6 Q5 p: w9 w0 m3 u2 Y& G% M# H   , u0 Q4 R. N+ S# l

; J% f$ t |+ F! Q* `6 {$ d

) u% s9 I2 }% b$ ^7 e9 @         $this->set_savepath($this->savepath); % t) Z0 L- ^4 [0 W3 b/ M8 A9 u

?( q7 Z; O$ ]# W6 p

~8 n& c2 j% C: |         $this->set_savename($this->savename);: a/ O& O9 }3 l) R; I- U+ {

5 o, K& @# J% Y+ U7 Y

Z& f6 g; K! h5 G5 m% M# D N7 V  $ ?. ?4 m/ [% z4 f; G

+ ~9 i5 f) F" q& [2 z% X

6 L3 D$ h; b3 t' r" p/ c" g         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ' K8 A9 T/ z8 u3 j3 @' H

; ~1 V8 U4 b( `# ~$ j1 S

- e+ n% u( T' r7 {( B7 Y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);5 d6 j. O2 J, S7 }0 N

6 n% V! ^( G7 Z6 W

& a7 ]" {+ h; c4 i& b' J4 T6 J         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);1 E! Q1 o/ W- F

. D: I6 l8 k3 Z' R* Y& W

l/ y. o ~4 q e+ I. y0 t; u  + ^! T6 U$ e" {0 ^* G6 }. }$ K1 H

9 I* f$ I% J1 [

% n, i- O! P: ]1 T         $this->image = $this->is_image();0 Q8 i+ P' B# o; u

! G0 x( j3 [% i

& v/ L8 |# ^/ `4 x2 Y0 H         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);4 O9 @. ^: k; c8 K2 n1 y( C* h# m

% D$ A- V3 b7 D- \$ g

1 D2 T) ~% A! m! G. K) k% U         return true;: C4 Z `( y, b2 v% D) _

: E& p9 o' b( s3 U

* g6 i k6 n. ?$ p: K5 G% P4 ~     }}4 V* y X6 W; `+ T( X4 U

$ R. |. w6 c2 V% ~0 C; l' P

( y* |" \+ B0 ]! v+ P 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:" e: j9 c+ s- J( o1 v7 [

5 y0 H! f" `+ f

8 H( c; E( R6 j; o6 [9 Q% Z <?php" ]# s# I2 O! d5 N! o

F* n/ |2 ~& ~4 ~ B

+ r) G: W# t( I6 t$ `( M; D' }. u- ^     function is_allow() { * P1 {3 C4 d `$ b5 Z0 ]

Z4 e# S1 U1 j

, q( K5 Y3 g; F& f4 U- L# P         if(!$this->fileformat) return false;5 G& c1 x, T3 ?$ O

9 |" @3 \! y! W: D

1 j7 @, L! e" R. b         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; ( i4 Q1 @, d# C/ q% P0 n6 Q

* c5 T% }! I9 B. [

: a1 | i5 _7 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; / r0 n8 z4 k8 s9 Y+ N1 t5 ^6 r

: S2 `& w: T B7 u$ q$ p

8 B3 x: m" ^- c$ Z& m$ h         return true;+ n$ P0 ~: g! K4 u) G$ c" [! r$ ]

* [5 E6 A0 h; l% O

5 x9 a+ j+ ~! W# F4 I* h     }4 { C2 |$ o% \

1 L6 L$ Z- X( _& c, A3 U) e! Z

6 o1 v* J8 I( B* h' H; C: |% ?: J 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。7 |* ~9 [+ @! `7 Y

; F& K3 y, j! l

# v4 z) o% O1 f& W/ f: w 接着会进行真正的保存。通过$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文件。 3 j5 K' R. Q* M. d/ _

! o0 G- a0 e& q9 _' \, O3 C

b6 V! o5 P0 j u5 [9 s 漏洞利用$ v7 ^% ~' q/ H3 K3 f, I, I9 a

( ]+ R2 C# h* ~' U/ x; o8 x

$ X# t( N) r# Z! D( S" h: c" m" z 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。. e0 L# |2 W8 K5 m% q5 Y1 j" f

& E, {" \) G$ L: p! Q: I

0 I9 F1 z0 [2 t0 m   % L F6 T) o; H5 ~+ S5 j% i

% v% H Q5 {# H

& ~8 G3 N$ F" l, {% b# O 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid / K4 D7 h- t# X+ A

0 E7 A& D- b0 W9 U0 ]

/ C: |* D) F, l1 ^8 q5 i1 F e 不过实际利用上会有一定的限制。* ?3 n8 ^) V% v( Q6 D5 M" Y' z

% i, V* n& d4 I! D- z; }

( Q$ l" t: c1 ?7 \9 u4 W 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。- U! Q/ p, B2 T. i( C

) B6 u6 Y9 q4 L6 x: L

! P s3 u* X" m: n$ q7 n# z2 b  # f! k6 U/ ]5 P7 @/ f

( t* d; G. N. v7 M' A. M( _+ k* z1 o) O

( ^& ^+ N. h/ g8 ` 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: # T2 L& t* I6 W' W J) l; X% t

- C7 L+ r3 k5 n; ?$ ^ E

5 ]9 Z8 h' F 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]);省略... % F3 {) M( ?$ A" m5 B

) a& a( Q% u- `" o! ~7 Z0 e

* W# Z6 O( H. H8 f( B2 }0 x 因此要利用成功就需要条件竞争了。2 z( X8 P, S: b% b! ]) r, E8 T

0 l T* M/ ~# }9 X: V9 Y% J

5 Y% S7 [3 Y% r: N3 c7 ] 补丁分析 3 p1 {+ L$ B( F

# f1 L; t( Q ~% Z# p2 Z* Y

2 a5 }# [/ Q9 F% M   7 X& d1 T( T4 Z

; d' ?! o8 k0 C3 o. `

, \7 z5 w% q! \) o# i& J; {, m' P( c 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 9 k# Q# {' P& u8 b3 y

# D* M+ ?: g$ W5 ^0 V

" R! `% A+ S5 O+ D7 D& \- r' W: I function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} - {; A$ h5 T: {1 }, g5 o

2 O) j# G1 v) t, r! w

: |) Q. m" \0 d: P) c  " J6 n* c [0 C4 E) K# B

7 e3 V* o4 n& B i4 Q- }: M( U

. r5 s5 m" z8 X; r( E0 K! N* T$ [* p 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 $ ?' R* q, S+ x" a8 L$ Z. P! }5 i

7 ?$ \' V; @1 _, p# k

' A8 `* F! @/ z! s9 O 在is_allow()中增加对$this->savename的二次检查。 n9 L% b1 W8 [: p

- I/ v6 S& \; }1 k( v X* D

$ `# l/ v: o7 ^* Q4 c1 Y6 ] 最后! ]6 T5 v, n) e. o/ G4 G2 C( ]; |

, P6 z. S1 L1 [7 V# i" L

1 z& v: C: d n 嘛,祝各位大师傅中秋快乐! v2 c" ]" p. W( v5 Y: n' p: D

. m8 U$ q" G2 |; f

( w" ~2 o. B# Z" H. H) Z7 l7 x( D  / x9 k* {0 [+ D$ s

+ {4 i) r: J6 r; `
回复

使用道具 举报

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

本版积分规则

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