找回密码
 立即注册
查看: 2067|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
3 {# ], N. _" K; ~

: Z$ G4 p; Z0 T7 E

j+ L. j- \0 B2 p" y

$ }' k- [8 p- `$ `4 S. L3 k 前言6 c5 }" G% C: i- X

( k7 m, H! v$ n5 M

" P1 P. M8 A; N4 d 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ; ?) s0 P* |* U5 \

, @: O1 ~% H- i1 X; P

0 y4 B+ Y! l" A3 w- C   # v# U9 H+ g% y4 { u0 M) e- A

& q" A9 ~" Q* U- T6 Z

1 l9 W6 g7 K0 y* d" d. i$ u. ` 漏洞分析- s' ]* Q! _1 B/ d- z& B

- U% e$ O* z# V

/ T3 F- H# H& Z: o8 ]( K6 Z# j& v 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: N7 l; I/ R6 \' c% x

, x: N0 p5 C9 O9 i

- F7 ?9 {! T7 E( @" `* L  4 e0 g% x0 O5 P7 H3 _. ~( ~" ]

8 w+ ^4 w# {% s* i: h9 @

, w2 Q# p+ f; H. u- Q( q! |3 L 对应着avatar.inc.php代码如下: 3 ^" u- w7 e( ]

# u& P4 |4 G( f, V1 W* s

. h& F$ V0 `" T2 m& h! A <?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 V9 ~9 g9 [( A1 O( C5 J7 \

/ Y* q1 {# ^, z4 Y, d! G# |

8 `6 @, Y5 t& Q( p M+ c5 V+ M     case 'upload': & j9 i( w1 W9 V9 Q4 P# t! `8 {6 X# p% o/ V! ]

% ^; ]- D/ h9 T

0 p9 r& E: U2 }" r, o. P         if(!$_FILES['file']['size']) {0 ^. v D7 S; V- n/ R2 j; X

, q' {! ]* T& j3 W+ L

5 c, D w+ P/ O0 b7 t: M" u, e& _             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); + B4 ~0 G3 w6 ?, o4 y- F G! o

( b% q+ O! G. N v

' s1 j$ o# L0 b( y0 |1 B             exit('{"error":1,"message":"Error FILE"}'); r/ V5 l( D) Q: v- m- z. E6 _

5 R" R' a$ T' ~. \0 }# L

. A7 l! W5 w6 e1 z         } * Q- W0 t. M$ m; u6 S

* _0 K0 Z0 p. \- Y3 Z7 d% X7 m

, Z( ]! N& w8 S4 k6 N! ]) K) Y         require DT_ROOT.'/include/upload.class.php'; * Z" T) x7 q4 i; }( Z

! `" a4 k' E% H% l" N# k& J# n

5 M$ ?3 t6 Y; h4 }$ ^2 C   - e/ M: m+ g: v; ^

" s/ o/ z; K& B, a) e* X h

/ T( n5 V6 ]% B$ A/ J         $ext = file_ext($_FILES['file']['name']); 6 O6 q+ J8 M/ x. g! W: E

7 V+ Z" r* y# K5 {

0 {; r: a, }/ Y$ M |4 d9 {# E         $name = 'avatar'.$_userid.'.'.$ext; , _% x/ D9 A1 |' A( D6 e0 F( ~

8 U/ }4 M k+ A

, F F) {# `8 \+ L8 f+ E+ T% k6 L         $file = DT_ROOT.'/file/temp/'.$name; 2 t0 |' z, r7 H4 U. I; _* e

) c! ?: r% f1 R

3 ^+ j& ]" n. H9 m' i   $ j3 a. m6 e u" u; r

( T0 p) n! u/ f8 F

. Z6 t1 q0 u; J ]$ ~8 v7 ^         if(is_file($file)) file_del($file);% C6 S9 r6 s+ O: V

) y% w N$ [ W

! w: V' c1 b3 t. ~& ^9 Y         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');$ ~: P/ q9 k: u

; `; b F0 O" q( F/ _

2 B# e2 K3 o+ w5 F, i8 W$ T   $ P& O: x; U ~( N- h$ S+ q7 j( c' {

" d4 z- X2 v& r# m/ ?

$ o( ]5 B# V1 h0 a9 R1 b" r6 i+ \         $upload->adduserid = false; 8 J4 ]: f1 L {

4 d' C* O) a2 R/ C

3 B! o+ B( X+ i& G1 y4 d   9 B% B2 V( |1 S$ F( M3 w% G% q

5 q* M/ C+ t# b8 v) X, H& W

$ q" y- R7 U) A% P         if($upload->save()) { 9 h t! A0 g# s; d* R* H

p$ t! p$ Q9 D4 S

; M, k) v- Y4 z1 e5 M             ... * b$ D. D$ q* S* Y: i

0 s2 e( \" _$ q+ K

' r6 w7 l/ [5 K b0 R         } else { ! Y6 v. p+ Q* E c

5 F4 t) }4 q# h H* z/ @) _

% d7 n/ ` j4 O             ... 0 ]$ X" Z4 ^7 P1 {' `: ~; h7 g

; a6 @+ I4 G5 I

9 X; K% o/ {& i         }0 V1 E3 P6 X& ~5 [ ~

4 e |8 e8 N5 ~# B. i

( Q( U( _' S7 m' [- Z. `$ t1 e     break;5 p; n0 w* n0 i$ }8 s% y

- ^ E& ?1 h" R4 u& x9 I3 Z

, L7 E7 N- W: a 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 9 C! C+ g) @/ j1 O0 X% O

; T) N' j3 q5 B. D3 Z8 v

4 [- s& O0 n% e8 K$ | upload对象构造函数如下,include/upload.class.php:25: F& C/ U' ^2 @

$ e2 P* y$ P2 A: W/ W

/ U$ }( [$ J; ]+ t. _: k+ G$ | <?phpclass upload { 5 h. w" K, R' y; n; D3 t% a

+ Q. D0 m7 s& B

O1 M9 o+ t0 Y* u; K     function __construct($_file, $savepath, $savename = '', $fileformat = '') {4 U" ]2 t" x8 r. y

. [8 P |$ C/ A+ J

e" A' Z- F5 ?* G2 C- A/ C9 |3 V7 G/ R         global $DT, $_userid; ! b: [9 f- s; U0 m! h

7 a7 \0 a& u" M

+ b4 d5 _: x: L# P$ r8 |+ | l         foreach($_file as $file) { ) a& G4 _3 h3 z" T2 Q" p. c

) S3 f$ `1 W4 b

% N# K V" g p( ^ D; Z             $this->file = $file['tmp_name']; 7 w/ @- T, W9 t9 v( a& F/ U% M! H

4 J \8 |% Y, W0 Z. Y- D. Y( ~/ }# I

& T" C" Z! h$ L7 S             $this->file_name = $file['name']; 5 ?+ e# a4 M: C+ G! a

: F2 ~/ d# Z% h

0 l4 W- O- J5 U             $this->file_size = $file['size'];- _* r" e( i6 E% Q. r

/ ~0 ]; x+ `1 J

. a$ m) {6 Y7 M" {             $this->file_type = $file['type'];: F) \& x0 s9 g; o% ]9 U" t

, i4 q1 A" P: V8 h

4 _* F! Q0 I9 v. @5 Q- i6 l5 t             $this->file_error = $file['error']; # w, L! Z! s* i' m" b: ?

7 ?* X b( ^. T3 E: ?

" N, u8 T8 H: T' u; D   " k) l! }# `* W

& X1 V P2 G- h n

# Q/ k4 D. e4 a! I         }& U) r: M* z V- o

3 z' v" o. j3 {1 Y' p+ F' V2 O

& g1 C% A& e+ o9 o Z+ `         $this->userid = $_userid; . d. J2 u' y, k

, C3 ~/ B) `% V, T

2 i" ^0 z- O" J         $this->ext = file_ext($this->file_name); , m$ S; m& t4 } l5 s2 J, f( k

: p( I+ d9 Z' m% b$ f

; d$ z( p. b1 O1 q         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];# K; ` I- I3 T0 z

9 e2 F% p- f& M4 I6 b

, M$ o4 i+ _9 y( T4 y* I         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; & X/ m+ ^3 f" f5 b2 o4 m k/ |2 h

; u; Z7 N. K0 i. _

7 d' v4 X6 i- G3 n. j         $this->savepath = $savepath;3 O' z) h$ J' H

! U% c) z+ j# U6 J. B5 a! n! C

- ]7 n& a6 @* W7 g! T         $this->savename = $savename; ( ?5 C! e, z. m4 ~* {

# H7 X2 w& C/ \& o

0 Z8 L' K8 h% C0 X8 F7 R: Y! }     }} ' Z! o* ^3 u, O& I: b- n

) n* |) f$ I. f% w# ?2 `. I

- `# P* n7 X' k6 P2 K 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 9 e: C" z% m. c% f. a) U2 O3 Y

7 C% q, _: |: M, w& I4 ~

6 G# B% F0 x y' K 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 9 J3 i" V7 n1 E# v; g, C

, M3 @1 P( D: }- ^

& M7 f* y+ a+ q; @ $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 + s5 s) }8 |1 `

" P- d$ x$ J* `: |' D$ C

: c8 K. E# M* P* J: a 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 1 e6 g; f+ O$ \. g' U) L

' r4 F$ q* e, @5 f8 X0 b

. u. t# Q% k) m' X: G. @& C   * Z- F$ i! T a

6 e6 u+ I3 j; } C* r

& A1 ?+ C+ ^, g* ]$ @# W. z 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:! H8 L& Q8 O) ^& C5 G [' r

! t& t! C' [+ E% S' v

h1 X+ L2 G, C! ]7 w <?phpclass upload { ; R' Q9 i/ Z8 Z; f0 p s4 j

6 O" [3 g# z7 S3 |" T4 b# X

" n' y$ X4 Z; M+ J+ v# ]; T* U% a     function save() {2 {$ V- ~9 V& }- w+ n

5 R! D9 T# [, P- L% o, }

/ q! U+ E$ `7 V6 m6 @! D- C. U         include load('include.lang');9 _# N1 i8 F: u0 ~* u; j7 N

' i1 a/ E* C7 }# B4 o

, h; \7 W9 K+ |6 \         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); - q1 J# x5 y* V" _2 N! a3 l

f- h9 H6 H3 |

: ~ d, |4 a+ C$ y. u% b `   * F N- A* j ?0 Q7 E

- V& R. m P, M$ N" ]; u8 m3 V6 r% ~

) t* m7 E; @7 W* x, e         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');; ~8 n+ z x3 ^5 x( H

7 |8 _8 W$ V6 K* B0 o3 S2 t

4 a1 W( N; {) [/ G2 J+ `   4 o3 @3 v+ y# z" n# L% n, ]

$ {, \* M8 b0 d `6 `

' d: K7 Y7 R$ q2 H         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);. m7 |, e" j5 n( a- x) r4 p: I4 @5 X4 X

; [$ b% h% N' |. r7 k1 M3 m

# E$ v. N. }' Z) k8 I) A1 D  5 j3 V2 M9 y1 ?9 a. s7 u5 H+ v0 s

$ h2 s9 F; ]" K$ Z

, D1 i; {* l% K; i* J         $this->set_savepath($this->savepath);/ A f0 |* A8 x% E2 D. M

5 k# s) {: y& O0 x, l

1 o( I3 e, P( U         $this->set_savename($this->savename);; Q- _, ?3 Y) j

6 z& c, `% X/ k2 N

+ y+ T1 F1 h4 l" j  2 U1 \& E7 P v; V' D

2 ^8 _& O( Z2 X" i( A( D$ r

. }; K8 _" G; I+ N% i5 s* X         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);2 {# a1 U" t5 g3 ?

$ ?# h9 o( Y. Z4 o8 }$ ~+ M$ x

2 y4 T0 Q# E1 @# G* Q         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);1 i' A+ a" v/ B Y& w. n! e4 Y

. k, n* k1 Q% b! l

, C0 n, P- d: |8 |1 I% M         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); ' j: E/ ^7 |' m. p8 h' x

6 K m( e( T8 \2 j

3 j: \) K# V9 x6 V+ Q5 h   % Z* o; n% F& S- ?* g" x* X* R5 V+ [! z

3 D9 Z$ ^5 {, y1 D v7 |9 G: A

- g9 F2 n' H ~( S0 h: d! M         $this->image = $this->is_image(); 6 e1 S9 X( X9 \* s7 W- Y

6 e% N3 W/ E# j9 n `6 W' k

7 E' e$ ^4 q0 ]7 }         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 1 s5 r1 S0 F" ?1 H

* i$ D0 ~! t8 P! j I

, X6 T; j: e6 s6 P U6 S" |2 ~         return true;; q5 d+ Z, b1 E' t8 c& N

. k2 c; a/ H! o3 w5 K Y

# P4 W" B. g3 l0 M- N     }}* @/ }) z' T2 B! _& m7 ]

0 `2 S2 ?9 k# |4 X& H$ K

4 H# ~5 X8 ?$ q+ N 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:$ @9 x: y& U4 a

* j/ e( T6 o/ c% J

1 M4 W8 _ [: T <?php ! j6 T8 ]" L ~/ f- i |3 I

1 G% h" |& K) ]( F- f* u; s

1 {0 V& ?% T! a. e `, i: g     function is_allow() {+ Z( O6 d- k+ f/ e

$ o0 {& j& c/ S+ k/ P- Y

n$ `0 w7 r% q5 _         if(!$this->fileformat) return false; 0 u' d T; h) y |' L. O

5 _* {0 L$ E/ L: S

! C8 o8 `3 U3 K- n$ j ?8 a         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;8 B& x, P* v. O8 D

4 Y6 l; v. i/ \+ }: |

5 G* y4 L& z9 H# z         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; ( u! ` l# w5 m

5 q+ `9 d0 ]) H( A9 p! M( N) S

; }8 I! N% X9 d7 Y. B- m8 v% ]! n         return true;6 E1 V! [/ N2 B- `

" x; R* A. v. k" n/ Q

6 y8 N4 y9 i$ N& O     } E$ d. b1 j6 ~+ v3 @

9 b6 G3 c9 D& G# D8 |- J% z5 C, h

B# c% X. `0 o; }6 ] 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 * E* ]/ V8 P8 D

7 ` f. ^1 \& T% f+ u) a. y! m$ _- b I

' {$ [0 K; F& z! W' S4 P0 K+ b5 B 接着会进行真正的保存。通过$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文件。 9 M/ m# a# c. o4 a2 u

0 _; Z- M) Q( w/ J3 @! i4 a

( A9 H& n0 o: H4 `. @5 F 漏洞利用 ( e7 u2 w+ Y. L) r. [" l$ [

2 i" u; _' X2 _7 |( g% G0 B: P7 z

. a8 @+ o6 k" p3 v# e# x Q. q) o 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。6 M; V" Y! x# O+ V6 t

. d; K0 n' Z# ~3 q+ x, }2 `

3 O, a+ R9 e- g   ) F- m5 W( g8 ^3 L- R

8 U1 X G, `; A/ R7 g

]8 N9 A: H: }1 F% @ 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid# ` T4 O+ \$ n2 S8 x" v- T5 \, ~$ f

9 E% B! M1 j8 u' l3 z- I- s

+ @2 F* t3 s; _) j8 w, T1 C7 _ 不过实际利用上会有一定的限制。 ) _- G' J8 E! ~, Y

1 y( ]3 A2 ]( F# n

1 z P5 ]- s( s 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 ' ]" w) S1 P. C

2 A0 m8 O8 v' K) @' [2 P$ j

" h: e' z/ R$ x% l0 G8 D7 B2 U. s   9 s, o9 s% h7 t! N/ x9 Q

- r3 q; n, }! ~. o; f% B& M

4 e' F1 J0 ~1 s3 D% |, ~+ [ 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:' h m: K" ~0 H' w1 j9 W( s

, B. S* i0 D& t& F

8 U2 R7 x- B" w, `8 S. z 省略...$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]);省略...6 E& n/ O5 P" T6 s9 M

1 A; w6 V8 Y; |# x' \4 q( T& s! V

* e7 r: I0 y& U' Z4 { 因此要利用成功就需要条件竞争了。 + r* ?* t c, e

: Z# l- j$ z" y" c& h/ p

5 `& `4 r, P6 @! L 补丁分析& K3 k2 ?* |5 U( [' t6 V

5 ^2 J1 w: Z! D; X% w$ i- G9 I

+ R- V4 z5 A' F, c: o; C   7 _# |' e9 N9 z0 B8 a

+ o: w7 M$ e% T

^: J) G7 ]- ?. x& _- [* s8 |4 P 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: + I7 W" ^( X$ ]

4 \/ h. d3 x3 x; f2 X

+ b0 m W& H5 s function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}' \+ X- ^! e5 M. {; D- K9 G

+ k1 H! Y, p7 T. t: x& _5 u3 T

6 Z# V2 P7 i4 p5 e   % k1 `6 H# X* E+ [$ X# |

" i1 q: C% ^% ]! |: `9 Z- v

! F/ F% {! p. [! R% e) ~1 O 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。. C8 P- h+ Y, d( W! _

6 `& e; `6 p0 v9 _4 {

# }5 a1 H& h% A9 z7 T' R 在is_allow()中增加对$this->savename的二次检查。 % [$ G- J" y. H

' u( U- o1 _. z* d. q

' Y/ [9 \8 B1 m 最后 $ h1 n# j* N0 J8 G6 W9 ^$ j

( N. R% U: ~6 B+ z9 ?- g i+ C0 ?

- S$ {. ~, Q! `$ l. s4 X0 e 嘛,祝各位大师傅中秋快乐! " U/ B. B# ^# d0 |

9 v& n4 Q: H& W5 T. W5 l4 l

$ j- U' g+ y% @& Q3 d3 m4 G   * ~1 I& f- C9 g) W

. |' ~# h9 V0 y
回复

使用道具 举报

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

本版积分规则

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