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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
: I' d+ H a! b$ ]' u% J! I

4 l8 A# j4 d9 W' W9 S

$ e* v" b7 i9 E' X3 N3 |. s: o

# M" Q; q$ @ ^4 J6 { 前言' l5 g: Q* _& X2 K7 Q; y9 W+ k/ [

, U& j6 `2 z" c3 j; v. a C

' Z4 m6 B5 P$ x 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。6 L/ g& r) ?! e: Y1 n+ `* `

$ I* x1 T" {, s% p4 Y; ^' q+ U+ F

) f- C2 J0 F* Q; l, [   ; u, C) C2 b( Y9 v2 W. a

{# [+ k9 H2 W/ J/ |* \8 U

! c5 i2 n* X& f 漏洞分析 K6 l" N/ U6 e

# K! N- P7 Q4 D" D/ ?

9 ^, B% k3 v2 a1 Q 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 4 ^% L0 m& x$ j: Q H4 {6 h

5 G. r' s1 N- y& e/ [

4 J0 w! p+ v# ~0 N" j  2 G# {7 X. c, S2 {- ? g

! g1 L) S5 ^2 g) y8 O9 N

/ z0 M ^; \! G- q- ]5 R& L$ U4 h, d 对应着avatar.inc.php代码如下:7 Y; K" U: i; `- P: f5 I, R7 @

: g- }/ z: p- E* _/ F( j. P/ D

|! b9 B8 \8 J I! L$ _& | <?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) { $ S2 G+ T( T! m% i9 Z

6 O v4 U: G/ g) c! Y$ v+ K

9 o( o- L( p, N+ P1 i( |% {) y, K     case 'upload': 3 I8 Y' G3 `. h6 Q( T) Y

& h5 }" {5 V4 z! R/ ?: e

6 U; y; u" J% p! ]& E j         if(!$_FILES['file']['size']) {% ^ H, o( C$ k, G8 N9 S' f

& J. f7 u$ v8 l8 O# ]+ k5 c& E, T/ v/ Q

) G) y' r( T6 s: C0 z' I4 A3 x+ ^             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);0 [! B+ i* L. O1 S

8 |# l; E& G5 T! O- w) C& n

$ q# s8 q( B! Z- \4 L( N             exit('{"error":1,"message":"Error FILE"}'); " H9 Y& }4 E! Z0 T

3 k8 a" c. @8 x" d, U

, v) n t- R8 e         } 1 I) E, x$ f7 h b

, y4 z& G& z' W* Y8 u$ [. Y7 M

0 R# L5 i2 U g( n         require DT_ROOT.'/include/upload.class.php'; & Q, v# J# }" r. A5 j4 ^. o

d# c% V# f, I# t' o, c8 c

# z% @. e* V4 t% j1 E1 q" Z   * W* x% I, a: J. l5 N! f4 i

) B" n4 p: s1 k! m% V8 f

7 O& O0 E& C! {$ R: W         $ext = file_ext($_FILES['file']['name']);' a1 V+ P. K- e" ~- V

: i0 _ J \) k* Q# R* m' N

6 D9 c7 A, n+ ]/ [! \) g, ~" t         $name = 'avatar'.$_userid.'.'.$ext; . K/ I! x# x- E/ h6 x6 o" h

7 j+ U& g0 C9 Z1 z; Y- j3 `

' T5 d$ W. }$ n4 H; q! J         $file = DT_ROOT.'/file/temp/'.$name; w' ]8 E4 {$ D* x' `3 A

: d. P$ A: [$ n9 E3 m2 {

, X9 o* m6 \9 l0 W% l* |   7 `4 W" K' V% @) G. ~

7 W. ?; ?0 Z: H$ h

+ t' i" h! o% S# x# ^+ y         if(is_file($file)) file_del($file);3 Z. [; B0 C, g9 M+ V' G

( \: J* O4 B. j. E7 p& J

! h0 u' s! S4 C1 o) Q         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');7 P: c% s+ P* M- e

; `$ L+ H2 e8 a1 C T* Y

4 N) ]) l' P% ?' w; Y' C8 X2 h   , H, _7 S) { o! c6 N' Q3 w

3 @3 m) M! m3 q" Y9 A

& Z, A% i- A5 c& g2 Z7 S         $upload->adduserid = false; $ Z; B4 g) u3 i( g6 t

4 s r! f7 `4 ?

/ P$ g X8 e7 W9 j( Q# C   ' @" C* F" {0 w+ N, V; @' q

# J+ R) S4 B3 M6 y0 C, S

( t) I" U$ } i$ f# l" _         if($upload->save()) { $ p+ x$ V, a9 H. `6 n

' f2 P" _# ~% \& t ]( o. G0 d

( i C `% {# D. x, y' |             ... 2 B1 t: Z7 y5 p+ B+ E5 }. C

* u! E+ a* c: s& _, p5 {

1 [6 M# X: |. w# W; A4 _7 c         } else { 2 y9 M' K2 W R. u! ?1 ^/ G6 X7 Y2 i

# T2 ^9 n( I: X+ @' c

$ R1 C$ c @! y9 m& M) q             ...& g5 T2 S+ ~6 x! f( o0 H

& E2 P5 ^% f5 S. \9 f- l4 {

6 e, m7 @( J1 Y) W         }( A2 r+ @9 v& b3 K

' L: l# F8 U' s# e* x

& v8 d3 r% u; g0 B/ `     break; ! J( c- W- \& T4 I& J( _

D. D9 C0 y+ A7 M g3 n

, {' v8 T/ N& g; _ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 . q, d3 f6 H0 {& U6 u4 b

1 O9 O+ \7 F4 M

9 ^! M/ X' f) `9 Z upload对象构造函数如下,include/upload.class.php:25: 5 k( }( R- N( f3 b. i5 K/ S

4 X6 P- G- M$ z5 W" N; F

~4 l0 R3 C3 t* U- H, b: ?, I3 J <?phpclass upload {. M$ C) x/ y3 m! N1 b( N

4 S6 b% g5 \& {, F# q; `) l& a! ]

j5 w+ |9 h) i     function __construct($_file, $savepath, $savename = '', $fileformat = '') { # A% d" b/ \1 x4 l# ~8 \

$ A4 o: N! X# @7 |. V8 P, ?3 ^! |+ `

& m- d( ?) n6 o) a) j" I% j         global $DT, $_userid; 0 a, q& i5 P$ H! T: P

1 F. v+ P6 ~( y' O

- G+ W4 O9 t! y0 [9 l: `         foreach($_file as $file) {5 |6 `1 e0 ?% t! w3 ~

/ t) E0 p- h2 v; `& B6 g

5 c3 G$ x& `6 O8 U4 [             $this->file = $file['tmp_name']; & @ S6 S2 L; a( V

% a/ W% V: N1 M/ J9 A' }+ R/ y3 Y

H- }$ ?2 C" \; C+ ?7 G: l             $this->file_name = $file['name']; , _6 K8 ], E( c( e

: b. ]2 E4 M& f6 F+ o' q' H6 {9 [

0 @( \0 u F" M; |. }) ~6 L             $this->file_size = $file['size']; 1 |( A9 p3 s1 w* K

$ y% f9 @5 c/ t7 p( ~

$ j( K H0 `+ i! Q* E             $this->file_type = $file['type'];3 h6 ?9 z; X7 j

% [) S# }5 X) |2 J; d7 l/ [3 `: u

U6 Z2 b8 ~2 r             $this->file_error = $file['error']; ' d$ h' }3 z3 U7 Q( U$ @

! N2 z. n, F( [! T2 ?& o

- x% ^9 [! }7 _1 D4 E  4 K) A0 z; ?1 ~% Y2 p5 u

( l. z# X9 b! f6 x V* O

4 q& h5 g7 n/ s: ~4 X& r         } {8 x; W/ `+ C% w" c/ ?

3 Z4 Y& u) I7 I& `% |

7 H6 |: W" d/ |         $this->userid = $_userid; ) v, N$ ?- ?4 n& p) G

) V }6 ]* t7 n0 I

5 u- G5 O# Y- k% n+ |$ ^, p3 C/ B         $this->ext = file_ext($this->file_name); % O" G: `' |3 G. C

- x! M. x. ?( D; a! n1 j3 I2 V

' Q7 ~, H/ I: D* E         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 2 l* j+ b) O* h! N& g' V& w O

: L5 I3 Y6 I' [$ I

$ }: c9 u- N6 ?5 q, ] J- `         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;& g0 ~" e$ _5 Q

( u9 e+ [# _' }' X" a! F. H" }

1 i: N- `2 n) r! K7 o         $this->savepath = $savepath;% f* s' |5 y- v% C+ j: ^# \( W. K

5 B* m# P( }+ x1 j0 Y

5 i1 C7 H3 w9 S# F: H         $this->savename = $savename;1 E/ D0 U5 N [" s0 C

}- d4 Y# x2 z, I; a/ m2 U

& Q- `2 Q- h: k0 r1 P; K/ f0 C     }} 5 a0 j) [ b$ w$ s( X) u

4 _7 a) w7 P; O$ A/ w

$ ~$ M+ E. K) ~2 I6 R$ N 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 6 U& }5 j/ s" B! ]" O; ^& K% A

; l+ I, `1 M, [' A9 j

" _% i# J& t$ y# L1 M5 H; P2 W 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 : K$ D, v* Z, k `7 q2 c

; q( }- d/ D( C/ c

7 P5 E; y) Z1 R$ p$ g- W- 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" u* D! o2 H% q" ^

# K" X- Q* w2 @& y) |# w# T8 E

* a, P: u3 ]: u9 \$ T4 g4 K- G" ] 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 1 F6 \2 ^& b7 y: _1 P

: a. Q! J* A, w+ G% K6 j* G5 P

' T& @' w' [, o) Z1 a   * ?; T' Q- Z3 F6 U* A1 h

- D2 X0 s6 z4 {

. q" a) T7 q: O. M0 [2 F* p/ l+ e 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: & I) E) i0 t. Z1 o! z( e: G- V3 J

e' S1 c0 O1 H" p- c

' c2 b/ l: ^* ~7 O1 } <?phpclass upload {* c+ q. I4 y* i! D7 G% U

4 V- t1 o9 b! D9 S4 p

" s8 ^( Y$ Z }8 V5 N$ Y     function save() {2 f+ D* C- n* q2 {* i: M% Z. e' U

5 c: J9 }# x k) ^# b, F) p {

+ |3 l+ F' T D8 h( t2 ^         include load('include.lang'); ) @! ]# C' d: z- r* C6 b, \0 o

3 N" Y. S1 ^2 K/ [# x1 e

. }3 A9 @5 ]" d1 h3 C8 h         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 1 G4 s' s$ c4 l

5 f6 N. v4 X( x- k7 g

c) A6 C, y5 V$ T' v1 s! [1 _  ' j) R; r, N; E5 w, O9 p

; Y/ m m# L# L9 b4 a

: V9 ^' u% x. D& r         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 a' h* n! o6 A

: w' z! x5 @( C" ?) M

, `; i! J( q) h! X  ( ]3 |, ~) N% `, Q. L9 g' P2 R

5 Y9 k8 D2 @. E5 U1 u! y9 ^! ^$ H

& f- S$ F9 K4 X2 R0 `: [4 }: L) U% X0 z         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); * x0 p1 x; m: n9 U

" w' B/ k; {/ R& a

5 |: J& A$ i3 N2 P2 @5 C! l! Y   * V& _4 a8 ~/ j% }* B7 K0 c6 Y i

. E; X3 @6 o; [% v! s

9 u( ~" X# V1 X: z6 i% H" `8 m         $this->set_savepath($this->savepath); 9 h" F1 g# z7 v- x' P- \0 [; H) P

( Z9 C( K9 b5 z# B) w2 P z5 V

/ P- J( C9 S/ a+ s" A! a         $this->set_savename($this->savename); + t, m- V6 v# U

# R6 R7 z' D% O% L- I3 A1 m

; P: X" T# h! O+ G/ q$ p4 G   ( J+ r0 o7 D' d: M1 c0 {

/ i: X* N Y8 d; h: W6 ^3 H" P+ {" k

& g/ ~7 O& x7 j8 U1 c( ^% D         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);+ V; c+ a1 `* Q# O0 f. [* l

+ ?7 ?# i0 B& M& V) _% k& M

3 _3 I1 l% ]6 O" D, c) S6 |) I         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);* {" S) a7 W; d5 K7 T( e

& l) o/ o2 X: a

4 I/ h7 o0 F6 u9 @) o8 J8 l         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); & h. {6 c0 f- ^$ C( g5 P

]5 X) _8 Q) S. p) ]9 v

/ K- x/ N, [7 Y+ J  & ~7 \# Y6 i& E" I4 d ^# D& u

- [, S( K' ]" j1 {6 v6 C( l8 v z- r

3 i! B* p9 j5 W: a% p- i         $this->image = $this->is_image();0 k$ g: G/ g r/ H/ u# ?7 h' R8 M

' ]# |8 Y2 d C# B; \8 F

4 n1 C! Q6 X! d0 H9 I         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); : m& L3 |" c, I f( ]

7 ^( t1 |5 h8 [& c" b/ v% A

, ~# `: }) c5 j2 |         return true; 6 @0 ~6 R: O6 O6 o

7 H+ u! I: N. R/ X

# G0 D$ Y0 [7 Q: M     }}0 T+ Q! }) C" H) c9 U$ T' F$ k: u1 C

. L1 d6 P9 h! }+ f

2 V A1 F0 |& B# n 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ' z+ {$ x; e/ L

( H) F$ w# a- \6 P0 R. F, o( Y

3 |% G2 O: N, v% F0 x8 ?/ W <?php: x" u; R# u U0 i9 h

6 M' [ p" O# h" f' |' i

8 r3 c1 f3 n8 U1 ?8 D7 R6 u     function is_allow() {, ?% `4 `9 x' ?

- h- [, R6 R- C3 `- t9 N

! c, b& ^+ _- ]; b2 f3 G- U/ I i2 O         if(!$this->fileformat) return false; 1 J- i7 F0 v d2 K1 w" z0 ^

E. X; Z1 B' r$ m `

$ B5 l% x2 B" }7 M         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 L6 l* v2 M" h) O9 r

! j5 A% ~# f# h( C

: w" y5 k( ~) |+ P         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; 6 Z+ i% w: [) f( k

' J. o% B+ v: ^

- }9 y$ B( ?7 r/ J( Q+ E         return true;, i% T8 w) `' b3 ^3 l8 n8 T

4 o# C. [7 _# ]9 p

. c, H8 W- }, E" }* n     } : l9 ]2 G+ M) s4 ~1 m

, @4 J$ @1 v N! E @

. b9 i+ M( `+ x4 \* b) K 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。5 ^/ `; L7 I0 u3 V: ]6 ~

. r7 B) p) A' Q$ S1 e2 |5 r

* Z& f V' Z W3 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文件。 / R$ q A6 O3 X8 ~: c/ W& e% h

4 w) [& Q- w" y6 x* a

# d* o: O" l8 H, D) a, S 漏洞利用0 N% i8 r$ Z% `

* M- j _ H' R7 _4 ?2 ~

$ G) Z1 ]. V' @6 M, Q& Z5 }, X 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 7 m5 w9 v& Y* c8 S; f* L3 t

, C# }+ e' I% Y6 U8 }; V4 z

, A2 z$ k/ ]1 y4 B9 U   0 j( y0 E! q# L& F I5 J4 }

: V! B! G0 r2 l2 } G

6 a4 H) Q5 v4 w) y$ o 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 9 o/ [7 Z- D1 b2 c

0 b( v/ a0 `1 F& B/ {* @+ {- I7 s

5 D5 q% {. q2 `4 d5 a; B' \0 W. N 不过实际利用上会有一定的限制。 ( @8 ~6 H. c- e- i& D( b

, G- Z1 M' H9 `5 y

0 }9 C. h0 ~# ]4 u' H 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。% ~9 A% z" F' F$ H8 E" i# X- \

$ @# E* _5 q" `7 W1 o+ m& @

' Z6 g' d! E5 w% x/ T  0 h4 w7 ]4 ^7 p# I8 D6 \

/ a' ]! B! s, _8 s" Y: O$ f

. H2 h, _: {9 h u9 o 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: - L, k+ U9 e+ j$ [0 a2 m

# N" e( M( H3 B' C. r8 o6 r- ~

: \5 ?7 I: W: P" }. E6 [" t' O 省略...$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 U- |) q% L: b- j" t6 p% [4 \

* |) F6 x: C! O J$ H8 }* b

& p) i, }& e6 e3 u# i, C Q 因此要利用成功就需要条件竞争了。 8 z: P/ y, P( f, Q! L8 N- ?

# P* p2 ]% W, @; Q. q+ C

! j, c4 f5 [3 S 补丁分析 , W& y [% S7 S. i& i

Y, ^/ C3 u9 E b n$ ~1 E) r

/ i+ j5 T) p5 `; b8 O! F4 w  9 t$ W7 t$ [" s9 C9 S) W

1 h% k7 U. \' T% }9 p0 X

4 f( ^; ]$ t; C/ i' N 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: " {7 b) C: j* _

/ n8 b; y' v0 F

2 ^% I3 d9 n2 Z$ _9 C2 X4 Q function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ' J2 W1 ]% Y" E, H! Q

& O# [. s- f/ Q% g+ g/ K2 N

( y1 M; K0 H h, N; r  + n+ a' Y8 z5 j; q: ? k2 ]

6 o3 F' u9 ]) Y' Q# G

4 O. v4 D% ?" G4 [& D 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 3 t) A8 }0 D! k4 y

! Y3 i4 z. x; @

" x6 L2 h, x+ _% S, I6 u/ @* z% @ 在is_allow()中增加对$this->savename的二次检查。& h5 [' Z4 _1 Q: f

6 O% b8 v, H8 k p) D

1 Z( [2 ^: T! B" U# C 最后 8 e- v$ C) {6 J, V: e6 N4 B F

+ b! \) S9 y8 a

! T7 z3 }& X$ y0 Q" ` 嘛,祝各位大师傅中秋快乐! & u6 q- @5 h/ W: x" z

9 V& n* e5 w r6 c

" ]- s' u( }! q: G  " ^+ D& h$ B8 C |9 M& P4 B

: v6 `1 }6 @7 D" f% g* e
回复

使用道具 举报

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

本版积分规则

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