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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 显示全部楼层 回帖奖励 |倒序浏览 |阅读模式
8 }/ o$ N5 E7 {6 N4 Z3 T

1 y/ y0 l G3 }+ Q/ x0 P

1 I6 v+ d/ ]; h$ |& H0 D

; n, x I- I9 t( ^4 Z/ J1 e 前言1 |; ~* v( a H1 u( O/ [; b

' k0 d3 Y7 d) q& o

. E. O! i9 \$ s 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。1 O! u4 v! g, ?, \/ R

\" W! i8 e! J* o5 [4 h2 ^

/ ]0 O( U7 k, ?5 v3 `: E& ?# ^  $ W( \. k' H! m9 S; j' {5 D. m ~9 i

+ a! p% j. @8 R) j6 ~

+ @; l/ P# \6 a: |* h3 c" @, [ 漏洞分析0 R* u# l6 z% c' M' D: N' V

" m. B- ^# U K7 x

0 s |, p/ b& ?- o& s7 M# N 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 3 {. U& N ~+ S) \

. M7 T/ ?( O& ?: O' r6 y/ `

: B% l1 {' |1 { m* h, b   # X/ m; P/ }$ @2 `. @

8 p$ X* n6 X9 U( c& E

- ?7 d4 \ v+ w 对应着avatar.inc.php代码如下:7 L. q5 M' I1 Q9 k. a) |* l

1 y, {5 V- d- I" r4 \8 z# B; _

0 q* u E) Z* r2 c' R% d% T+ w8 V <?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) {) l% i% h- D5 u) x

! h% K* _5 E& n

# O& x) @) |6 D9 g2 W     case 'upload': & ?0 w& a0 W+ t+ H- N

* [8 c3 B6 W3 ?

7 C! g+ J# }6 i* ~0 A, x         if(!$_FILES['file']['size']) {3 I' m4 q$ q: r6 E, l* a

1 P$ e t$ V0 V* G0 M. x" n2 o

% l' \4 X+ a, ^) @             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); , k& |& P7 }) w. c; }. F. T

9 N/ d4 n; Z' p9 k, T2 l

( u/ g- S% P$ i3 R: I- H             exit('{"error":1,"message":"Error FILE"}');, l. t. h) z2 ~1 H5 h3 G+ C3 f

r; {/ V( C% L# r* l

( \, ?% P& S) E& u# u         }( l& z" c' ~8 p3 e) J" `

( ^, b* q% d( b

( T' _$ N9 z: t' P         require DT_ROOT.'/include/upload.class.php'; ' T! F; y) Y6 n! t

2 w: }! ]% N; c- G6 `

! h0 e* v) |2 ^4 r' D- V) `$ V) a  ) V5 w5 ]9 F/ z( ]- ~

) {8 d, `, n; @( A% _$ x) v

/ ?4 ? ~8 h2 H- D7 O         $ext = file_ext($_FILES['file']['name']);+ D' L. H, Q" n" J" x9 u! p( G6 s

3 G3 V) E4 ?: ~

0 |; k5 B, P6 F; h0 B3 W! `/ m8 ?         $name = 'avatar'.$_userid.'.'.$ext; % w) U) L9 [4 [- m3 ]- ^: U# D6 O9 ~

$ b1 K7 S/ l; b* T0 z; O& Z* R( h7 H

5 }2 n: a( E, v0 I2 J         $file = DT_ROOT.'/file/temp/'.$name; / h9 b2 A$ v3 Q8 s

$ r( P( m9 @' I W, Q

" D& G3 _6 L' N1 }   + h! V9 Z8 v+ u& M# t

; E% `6 x( k" T' m4 H4 U- I m) Q

3 T' V1 B9 t. K, O9 S5 l         if(is_file($file)) file_del($file);7 O8 \6 j# f' t5 w/ ?. H, ^

2 O! E/ J% S) h, D4 @7 _" } {$ L

. J7 N9 o% q9 l( T8 n8 M         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');! a* ?+ J0 c: N9 F

+ y; Q+ S' p! U: e8 ^

0 O* ^7 z5 e& Y: W0 N5 R( x* ^. S   * |/ z; E/ A) s

) A- k# ]. c1 r0 \# e

9 J5 A' B' z$ Z         $upload->adduserid = false; + a' j# g# u3 T/ Y* \# f- b

8 r- f! H: Z, t) U

6 B2 e% ?/ w: n1 d3 a   $ y8 C' E% `+ ]

% \3 N/ m& a/ W7 g

4 n3 C6 [6 `) v9 c ~         if($upload->save()) {+ `: V& [, E3 j* n# a

& P) ~( X; A- I" E9 @

' Q+ ?- f' E; e l             ... # \5 H4 j f3 V$ D7 n+ T1 X

8 ]. C1 k8 n5 k

- i. r- l R4 `5 p& c1 ~         } else { ; b, [+ v+ B7 ~1 {' N) ~

$ |2 ]9 E5 |5 X- S$ q- P# ^9 [

' s1 B8 R% ~5 u4 Y- @% l& C             ... # K- y( x) u" g6 @

8 ]9 o; T# a' n

% _1 G! H) v$ U @         } ; R0 r8 ^& u5 d$ C' ?1 ]% X

" B+ p0 W) `, H( `1 i9 z( G! m; P

8 U! K4 i/ g+ J" B" X7 q     break;! ]: l6 v( ~% F% z1 N1 y; X( y

) N" g; j( I8 }

" k- K9 d$ e. B6 G/ p- O! W6 o E 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 & |2 W( G, J9 ?3 h2 k! u

( r! K7 Z. B* R$ |1 E4 E' E( V

* I4 T+ M0 Q _! y2 D6 x v2 m, ^2 W upload对象构造函数如下,include/upload.class.php:25: 7 u0 G- ?, ?/ e

) T( \" _& b/ x. N+ @6 L" S" _

# d9 a& }/ C& s& e <?phpclass upload { - i9 J; b. Y+ ]. a0 k! R

6 G4 O4 Y& j5 t) X

2 b" {; Y9 S, m# i+ [* e4 s     function __construct($_file, $savepath, $savename = '', $fileformat = '') {& H/ Q2 z* n4 x

y1 s1 K$ ^4 B' C$ g0 ^

1 }0 K& P* L" F! b         global $DT, $_userid; 9 X$ t* j5 ^0 ?$ X

7 p' _* d) h2 ~' J! F& |: M4 q

; G' l+ A- {4 M. x5 H2 l* C         foreach($_file as $file) { - t A# w$ S$ [

8 s, \- p7 D/ I9 w4 [

4 x: \; V! }- L' \. O             $this->file = $file['tmp_name']; 0 D$ M( g$ G% W" }. ?) h O

, y3 }1 X; A: d+ Z* U# K1 K

8 z, O- y# o2 f, W# F             $this->file_name = $file['name'];! W) c+ @' R: S

, j% ]' c" B1 W0 u# V2 ?9 c

* @4 m% f8 C9 ]/ w* q             $this->file_size = $file['size']; 4 M8 ~/ V. G* S; ^5 {

+ q1 v' Q6 w2 Z# n6 U

4 D8 @) P8 \. X1 S G, Y& L: n             $this->file_type = $file['type']; 4 }% K% q5 f* }6 E4 C( ]1 S

" ^) H3 D. I) K. ?

4 n2 r8 n% D6 g             $this->file_error = $file['error']; 6 _# A: F, C5 k' Y1 O T8 h

; e3 C. V; J$ u t

2 @& R; e+ F5 Q8 m5 D  - P, o* \9 _4 h# T% q& O/ y8 h. W) ?

: n8 ~+ B% ?5 z% N

# f( A; z T' B, R4 o         } 3 n$ V/ Y& l$ T* G* W" X8 c

9 v4 W2 t" R* X) S4 ?

2 b( q8 j8 E% W5 U         $this->userid = $_userid;6 O5 U: r3 T- L9 n

' d" _$ P+ q1 B o! H

. ?0 z T$ P }, S$ I' i8 D* J$ X         $this->ext = file_ext($this->file_name);/ z/ o+ S0 {0 i u# j8 r, y

+ n3 e8 D0 y, S/ |: _7 Y

7 a5 U1 c7 ]3 B! b/ O. r5 J         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; ' r0 n6 Y1 p0 ^8 O

1 o/ A0 e/ s# y% m

6 [" U) e4 v( p0 V         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;( X5 U- V5 |, L; J& C7 X/ Y

5 l$ W; b* |# t% {( A

! ]& L6 I5 U6 a) Y- Q+ O         $this->savepath = $savepath;- A- t' x. Y; z. d! j

# [8 q7 H& c. W0 |" P5 G

2 T; o( Q" k2 `: H' H, `2 C& F         $this->savename = $savename; " `" j D& J5 e9 \

; o2 W1 i* T, \5 ~; {# N& J6 s

2 w/ R' p) W' @" t- b     }} ! q+ }3 o3 g8 h

0 X& R' ^" Z/ h: j# Z6 k$ ^

- C* Q; E! Q3 g( F 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 7 x5 L% ^3 N, y' u

4 H5 T! _- O5 k

W- d/ r( Z+ ]! z" R$ V 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ! ~9 }$ [% V* i* s g

6 X& q4 v# n4 D: [! }

1 y5 l0 O1 {$ Y8 v- J8 \/ ? $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! S/ x, j& V6 ?7 D% n

+ i0 S* |+ Q# A4 r

; I# F# L! A7 ]! l 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: $ R0 J+ i+ J: f% ~% \. H

: h1 e3 q( c4 P5 p" |

) B1 c/ N! `- x2 x+ w; N: p   - _: o" h. z: m1 T2 R

! z! J7 U p' w" M- b) b3 a4 Y

+ |6 C& I) U, {7 _# z' W6 C 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:7 S& A% g' R/ j1 q& v1 G6 b

! Q# _% K# j6 e {

8 ^, C2 ]+ X. N% g5 z <?phpclass upload {! O, a' j, F4 a: x- H+ E

6 f" x2 k( _/ v# F

S [0 I+ M" v     function save() {" z4 ?7 ^% E% P9 |1 m* x

* o; w' a+ {" [6 J* _% Q$ |4 C

8 M: O9 T e* T3 a. E         include load('include.lang'); ^) h* g$ W. ~8 \) N* D

1 [9 o+ C4 f; U3 t5 ~$ \

, N" {) H$ _0 {5 Q* k) i         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 4 w' _3 Y% G) u" @' j

- R# C [, c! ]. l R

+ K, T( ]+ `8 D. ?1 B  7 e' N9 z4 a0 \

% O- ~* r8 r! a) P% L! u k( Y

' Q( ?/ g/ j% O6 d7 O         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');0 l, t: [! e6 b

8 O: N; ]* z- ]# r( g+ @7 P

0 `! ^$ @* q1 f$ Z, n9 i1 \   7 h$ y$ T" W8 t' z8 n0 _& e+ R

) N7 s$ |9 c% `$ @

+ b% n5 C! K1 p1 Z; T. |, K& ^         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 2 o1 D8 H. }6 D8 l2 \# C, W: h. |

7 s1 E' J4 b; }/ W+ D

9 X& c$ y. J# g5 D  6 R/ T. D/ b7 q) ]5 X0 A4 t% Y

$ r) r7 b# d r% ?2 G

5 r; `2 L1 z" E0 ? B+ w         $this->set_savepath($this->savepath); 3 d, c$ R4 k5 e, N Z

1 k) @) J8 } u9 {6 D% k

5 E j G) r+ y- g$ r. T# ~         $this->set_savename($this->savename); 0 s# b ?3 ^) b: e

2 ^7 L) e) i' D$ _ m/ x& G+ i

( r! W$ l1 \! @6 j( E  7 L8 ^) Q% u; t

: u i* L( M4 Z, n4 I. \

7 c0 p% \3 b( e1 w% u! e         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ' o( R% p* ?' w1 y0 O; t4 b& i$ U

2 m7 w) I& X& ^/ R

; H' H0 |0 C! g         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); " E3 F& t9 N) R) r

! l: P. ]$ y+ |/ W

( l+ }+ C3 q6 C& _# z         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 8 ~8 d9 k; W6 p6 A8 \

8 m9 q0 g( M" m# ~0 c* S

9 R! l5 A- {. N) T. \& E) ~( J   $ O) w0 w, k$ L+ ^, Q& ]

3 l5 Z8 d' u2 H' H9 l( x2 |

# n' d7 q$ w! }         $this->image = $this->is_image();# p- f- I7 M4 Z; l. U7 V

, o2 f) V5 Z! J$ w( n

7 V. M# f- g$ d) e         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); ! h0 p" Q- H( P4 j9 ~

; W( _& F' c1 r0 v L4 J

: |1 ]& S# c3 {- I         return true; : _$ X& |$ z# V

! }7 H$ L+ [! Q$ [, N) C1 u

2 f0 L* x( v! f8 T1 |     }} * C/ J$ d! p; `9 S

0 A7 p. R3 D3 f( e" x

: s' r2 p- T; H 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: . P+ `; ^" w( g3 [' K$ s& ^

+ g! \0 M1 O0 u( O

3 v8 I* D6 G" v' f K& K; o <?php 0 m# [% b# |% N/ T

4 p8 E, n0 `9 C/ `: j* k# M

% {& n3 J9 X. }% T! x     function is_allow() { ! R; q9 B. \" \8 p

8 t. t6 P, o: R, |8 E! o

: K' m5 i/ k% w         if(!$this->fileformat) return false;" P! s* w4 F+ W! ]6 z0 u

7 N- H6 D" J8 ^3 r

1 b p7 ~' N* M( s# T8 |5 M( T, i         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;+ w- y- r3 @. r; G

. J7 l0 ], |" v

9 v! q7 P; @6 p& ~% e, ]         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; 5 W3 _5 R2 \) z5 q/ b4 r p$ f

D$ t! J+ _9 D' S( _1 O- j: b

9 @' u$ N, @/ T" d8 E) i         return true; - ^( S$ q6 C# F" T3 b, |

- a" M! R. t: `, Z0 B8 [ u) q

# y" y; k' B# Z# K. F5 \ U     } - L( X# Q0 M# d0 R' w% C" y

/ }" w9 D* Y Q. ^% ~4 r

5 |4 p, L, F, n/ @ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。; r* z9 Q. T. ^ A% l$ ~1 G

3 J$ l1 K5 k0 S& ^0 Y. d

, O4 k, j; [# W2 \; 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文件。 % b/ ^7 E, @8 Y8 u7 V/ _; Y0 W

* S' }5 H/ M9 Z9 [" z

/ m8 R# ?; z. |) C 漏洞利用 " N4 Y+ o+ {) A) h9 G% a

9 y* b; y$ ]6 |1 K2 {4 d8 j/ s, y

1 m- v' X+ c+ ]0 h. C) s 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 * _( F3 D+ @8 _; J# { U" _

+ W; D6 V @' ~* `

" i) o8 i. J" \& S( h   % I$ S9 O) P$ K1 ~7 W5 k+ o/ I a5 A

+ C4 R0 i( G& R# Q# c; Z0 R

% T' m. u6 H ]4 [0 b5 C/ Y! h! P 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid/ A+ s+ J) V/ o6 S, I+ N3 B

: f" \! V9 j; ]# Y! W

- c" l4 M/ \' H: K$ x3 U0 Z 不过实际利用上会有一定的限制。 ) t! v' n* @4 M1 M& o

h3 O7 j9 i7 K) b

2 }0 E: }# z+ B* x 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。+ _+ @5 o" q( o1 Z6 J

- \7 k, \6 z8 ^3 {9 b1 I l" D

) X0 B; z9 Q2 z  & K# ^7 f+ u3 \3 u& ?" \. w+ P

, j, J* X; o2 ~# I

- x ^# u( G8 N7 I4 Y" i 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:/ F$ a( T0 |% g6 v* a

: }/ X) \8 \/ {

2 f3 P9 P% A; { 省略...$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]);省略..." d3 `. r0 m' G0 L$ x, P) y

# N! U. d/ S3 q

0 }2 |9 L$ e" }: n- g. F 因此要利用成功就需要条件竞争了。1 a" l: ?! i) V' B

7 [, o6 k( K# j6 u+ B! w/ B

% i# W1 F% R5 t, s9 e 补丁分析3 [. M- O% J% E i3 Y% Q5 \! H% n

' b; }7 p+ F1 T& y# ?# ~

1 _7 f, Y6 j3 a$ C5 o' G   ' Y, I; x$ W! v( T

! `9 N) g6 j# u# _& _1 G8 m+ N

2 _: B* L( A( |/ q2 \ 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 5 R0 u ^5 o3 `0 {

1 _) l* F$ Y( }9 ^

. ]4 T5 M- H6 V4 e5 W function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} . I% H. @ F8 @$ N, @

. O1 k% w4 p8 B2 G& X+ f! a v4 w

8 U8 w" e9 V# M8 O6 E  7 C3 g% X; B6 z% q0 ^8 h8 m

1 S! w3 f( ?4 D7 L) v3 j

/ v9 O3 a. a. b. |9 K* j 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ( S1 n [7 [8 R2 m( t* A! ^

J8 |# P3 ~1 c4 }' Z2 |3 K

! X4 ~/ o& @# f k 在is_allow()中增加对$this->savename的二次检查。 & D) H2 R, d: R4 z& H$ @+ z

1 K2 ?3 U* ?+ N* x: G, } S

+ b/ f. Q9 A8 ?! S 最后 _7 O" A0 X7 H

( e# h: I; Q5 H# }4 W3 }0 ]

$ _6 v( @0 w% T" a, j- B& q/ k; o 嘛,祝各位大师傅中秋快乐! & t7 b7 ?4 H# j) I) N9 r+ k& D' j: J

" S7 @2 [( k7 \! G2 K

8 y, [1 y6 F9 n# M3 M- F, G, p   ( T7 L/ b% M7 B$ F$ G2 [2 ^

9 A$ M& e" f& r: g. E4 x
回复

使用道具 举报

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

本版积分规则

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