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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
: x# D; M& h5 R) q3 `4 \1 S

4 K& a) P3 }7 D ]0 a

3 T7 R( Y6 \! r% a2 W1 n! F) z/ `

% g/ G; S- H; N6 i* ~; o V 前言 : ?, v% ]8 h. z4 r, x

; @* Q7 N0 |% A$ T3 X& N

# F0 B* x2 g. c9 F# j 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。+ g2 a' E1 [2 [+ L

9 k! s% n7 @# J; S9 ?& P

3 [7 e0 V9 e7 t/ S; {) k   " Z0 E2 y# v5 G7 f

' K! b2 W% J" F

9 }4 X- R% v! o# R' k# } 漏洞分析7 \" p% t `' E( v3 j

/ ? t* @$ c6 q0 m) Q

; [8 M+ f% j7 h& }! A8 {' r 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:$ t: ~" \4 b: C

3 @# Z+ O, l2 t6 ~8 W# n& E

8 F, @6 Q/ w% w3 T$ `   $ M4 L. T- p% K( E5 _1 @6 u% `( n4 c% `

% r; q* K1 ~! l- b4 ^6 [, w

5 l/ e) R i4 K( _! O 对应着avatar.inc.php代码如下:5 v' ~: q% ]6 B% m3 P

" C9 A9 @+ q( n& e. e, C

/ A* R% A K+ ]2 ? <?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) {' ~# J, Y; f. ~) F; \8 b8 s' H

( @& t" C* C6 W4 g

5 E. L1 o/ K6 _, w/ {2 c     case 'upload': 3 U$ g C+ P& f9 v6 ]$ m/ c2 H2 K

( j- W* l+ G3 d f& O% n- E: M4 `

} C4 M8 {9 |4 x8 b3 `4 @) p         if(!$_FILES['file']['size']) { ) l& T" h+ W' y& P! {* |4 m

3 F3 |& R+ v+ u! @

6 x) [' ?9 O% q# g1 J5 S' W) b% `             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);3 a; ~# X( w! q" H

$ S& k! p7 h( }& u' S6 x x

8 _0 n- U; s' E) j. b             exit('{"error":1,"message":"Error FILE"}');5 Q7 U4 v& _8 K+ `: B4 i

2 @6 n+ M1 v0 N

3 C( w; f2 P& E/ g/ K" l         } & x l0 G# k3 M2 k0 }- j

0 F" }; R7 D+ i: f3 m9 t( \

$ L( E5 C& i9 o# b. L         require DT_ROOT.'/include/upload.class.php'; $ U4 H) I+ p# f- L

Y n1 {0 B: @1 r7 Q

* z# s0 D2 \4 }  3 |( T' W) y8 T3 e7 @% N6 P W& f! X

0 x- l$ T) Q$ \% Y7 v& |! C0 f

! c* Q7 F9 Y( P, n2 ]9 V( V0 w         $ext = file_ext($_FILES['file']['name']); 5 j* E$ z" J: _1 b+ |" T

9 y0 i; H" I$ h. z/ W2 W

) H2 s6 _6 W0 q8 ?         $name = 'avatar'.$_userid.'.'.$ext; 8 `7 l4 i) s' p7 C5 _: f' k& j

: G9 g! a+ d9 H2 [/ [/ _

, t+ q( n, F# a* r& \$ D9 @         $file = DT_ROOT.'/file/temp/'.$name; 5 B; R, ^( F" o% s

7 D' C6 O( j8 b1 ~1 u

4 _, I3 |3 S7 d, U9 ~" L" E- `   8 J" Q# K6 X' \$ ~; W

1 ~9 v4 a, K% ]& p: ^! V

Y( x3 V6 f$ J" v) d" x- U9 V& C         if(is_file($file)) file_del($file); * x5 B: Y7 a6 k) |0 B2 B

$ b( O: }$ t$ }: S o) n$ D

; q0 v8 n9 L2 N+ m* n1 K/ S         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); $ ~& [3 J* G* E e7 V }

2 U2 A" b4 A' p) `

3 p" e i7 O6 J' S" o0 X* V   - g: G1 f* G. P, B

- s1 H* w9 `; Q- I( S

4 I" I! [4 O# V3 p! [& c         $upload->adduserid = false;! _$ j2 G1 @( J. ?! |; X V

1 X/ R2 `0 ]+ ~+ B2 E" F% _& o

" K t, k9 N% Q+ g' t9 f" {! P7 v1 H% |  . N/ T4 ?9 V) j) B" _& B) `% d# r, s

Z' a: T* Y. ^; e

7 g+ `7 D5 `" l+ j* V4 O1 [; a& u( S         if($upload->save()) { : r8 q4 u/ b/ s8 h7 `0 C

% w4 ~: E+ v" H! D5 _4 e0 ^* @

: v1 w* M0 y1 D; g             ...4 D; `: T7 z5 P6 b5 l* a, j# B

% |4 F3 V$ T: N# g. Z0 n. G1 G3 U$ f

4 P: s% G; h, G9 y8 }8 d( D! \         } else {) @* W& g5 N3 s! V( P, n$ H

: @$ o* U7 `3 S4 B s2 ?6 u

( |% G$ R: y, _1 X             ... . x, ~" T9 G, A( F, \# h

0 |+ d L0 G; U# q# q- [

0 h! y2 F+ @( \! I         }5 |1 P1 w* }9 ~3 j1 G2 z

" ]& c7 e$ Z6 m

4 `) b/ v) T7 C0 ]     break;' d$ u. Q6 N' V Q( b+ n. n

8 e q4 N; F* J4 h) S8 ?

1 P8 _/ E6 `" t. R 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。' I7 z, [# u9 q

8 R4 n4 X& O/ b! T: l7 Y4 _

9 A+ F4 \5 V8 H# E upload对象构造函数如下,include/upload.class.php:25:0 }$ Z1 I; w) x1 T7 f' d

; t" f5 y0 G% K2 [; v' x* \

! h( N/ Y) g T6 ^0 | <?phpclass upload { # E/ d Q, d! ~" N Q: j

- A* |2 Q# B3 N6 I8 u

) _; C3 u; Y* A9 Q& ^: t     function __construct($_file, $savepath, $savename = '', $fileformat = '') {; Y! D; K$ y) [) H) a4 l: u

# x" B0 q3 K0 k* {; [6 H4 M/ m6 O( g

/ g; u( E' H9 |! Z: Z2 F& _1 m         global $DT, $_userid; + |$ l7 |; w' f6 C) F

1 J2 S& w; p& v/ f: ?9 j

! k* }% S' `7 s' s' H         foreach($_file as $file) { * E" n. Y; F K5 c# V

( E' Q3 H) f: T6 G5 o

: Y* {- ?/ c5 w2 S* ?( k2 [             $this->file = $file['tmp_name'];0 W! J2 U6 G l# M7 u

) f: }" l' g6 q' q4 }

, V2 n1 x6 M6 M0 [. _             $this->file_name = $file['name'];/ y3 G. M+ F# _

: r, X3 }% E! p4 d+ R

3 T8 V0 g( j3 v8 f1 A( w. Q6 w. R             $this->file_size = $file['size']; ; S( W* E8 c6 m1 `7 k/ }2 l

' M& ~( P7 E3 y" B/ ^/ A

( ?4 l2 F- D5 @& a& M. }             $this->file_type = $file['type']; ' ]. ]. O! ?" p) }9 G) ?# H

. i/ [1 q$ I) P8 l; K5 z8 J0 j6 A

- \, }6 M. T1 u             $this->file_error = $file['error'];# ?% u: [' W$ d- ]2 S6 d

) N Z9 y- u0 g! v

& i A, V2 R% Z- U1 z  7 z4 W9 G- Y& ]$ j

" w" h2 t. A! N- K& f& d! E( `

, n+ D- W8 U& j) h5 ~- E; O8 Z         }' u( m) F; v) {* A M' S* w0 {; C

8 [& J# F. |) e

2 i, f& O; r7 E         $this->userid = $_userid;, [5 [1 Q, A l

3 k! Q! C( T; x5 }

" S& k* ~4 d6 j5 N         $this->ext = file_ext($this->file_name);, W# }# H3 W) V) |+ ]7 T

, c6 j/ D2 [- q7 R

% ~. k/ p$ {' O         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];: i" l4 Q0 s I T$ f. B/ Y

& x+ f1 K1 G9 d8 Q# F

2 I/ H# w: j# n; P! G         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;' b Q& R! V- m6 Z2 \

& ]$ Z. S4 {7 O2 @6 Y' y- ]

$ P8 L' g- d& I9 `1 G. y         $this->savepath = $savepath;8 ?/ A r( j$ q- z' o* F4 q! q

* j4 W. {0 c2 _; Q, @9 K; P' [7 f

7 |2 {" p/ D: B! s4 I         $this->savename = $savename;. G/ m8 p6 [* m7 u1 \

* R+ V: j/ g! N8 z( w

) E9 m8 N) X& q- m. ~     }}$ s% }: o- c) x: U

- e& U$ G0 t) J' N" C3 W* Z* [- {

5 `9 p* n+ G, H* z' Q$ h 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。; p7 q5 G- {8 B, v* E

' V2 ^, T( \0 q! U y8 p; R0 c+ L

6 Q; j3 _8 u5 D5 J. H 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ; |9 Q% j( m6 Q1 a

8 } |5 X0 j' Y7 I" @

( g1 V7 s% o1 p% W& r+ ]4 @+ X $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, F3 i( a& } c" M. N+ A2 y

( s+ a, Y5 H+ i5 u1 f9 k

. `2 D+ o: a p ^) W1 _ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:& X. H$ ?0 t7 ]$ g: T0 @; `

2 D4 f6 q# D- ]) o$ X0 L

7 N9 c9 p0 R# @7 X   ! i5 m4 M' H/ ^. T- E" a0 ^2 E2 E% `7 g

: H! y/ h3 o* R) S0 k

0 e( H& y. n9 a* C2 w 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: x! \! I' c1 A8 |/ K

) ?. g* h4 t8 d0 h3 J; x

; ?1 W. w- O2 J" U3 \6 W) V <?phpclass upload {) f$ L3 p5 j7 W

" X+ _/ n) C% ^5 ~0 S

, u3 C) _" D7 ~- L/ i$ I     function save() { ! I7 d: Z0 s4 g1 {( ?

" a$ @1 \ ^% Q3 O7 ~% H: ~8 j

8 U. Q! F1 i; v% Q         include load('include.lang'); ! r* J7 n- R; y- I/ K% ]

8 z; Z# m; {" i- _$ E# e

1 L2 u8 P1 `2 e. C4 f         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');( `9 ^! F( p! C# ^' v" J" q

# j5 D# g7 l/ h. |

' @0 p) `9 R4 f8 r* V1 x& n. r) g   6 C3 I8 S$ V) l- i1 q9 t8 F* H3 X1 u

8 _' |. l. ]. ]+ `; T: s

, J* g1 z, W, a6 d         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 9 D _ B6 V1 l1 x* a" G& U2 q

+ R1 z( B; K* d/ j

7 }$ y1 g5 E/ B2 i# w$ N5 K  & u0 N5 S; p) h2 N/ g, h

. i4 i2 J1 V6 r- ^ O

/ s. Z D {: B* K- A         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);7 Y6 y9 T6 p; Z$ N, R' _& f

2 P5 \" ~8 \: I7 _% N

' ]* k* z& Q2 n5 v9 K: Y  ! u" j( q) _7 M5 c+ ^. C

3 G1 A2 W, ^) R; F( b

$ C, N/ h2 l X4 X7 T7 }3 r         $this->set_savepath($this->savepath); - d4 n+ h5 h. A4 n

+ Q. n9 ]3 Q5 }7 Y: I5 |

* j# d. {6 t5 R/ j         $this->set_savename($this->savename); $ H. y; d: x3 m! C3 q v

7 g; }% ?2 ?3 C7 N% K. ~7 M% b4 K

1 x" u7 ]' ?# {. g/ X4 I: Q& J  & X6 h* }8 f( D# j" b6 `

' k Y# P6 X6 ^8 a. Q

4 ^; e# B S* v8 f         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" h6 }& n+ i& Y" M3 y

3 j0 T) [/ w8 I% P, q7 G( B- ^+ Y) l

- p! y" n. G# _$ s, b; U$ g         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);: T" r3 w% C' `% f) \

+ h* Y6 n- G6 \* B1 F

! h% N, k( d- j m+ H, m L9 z         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); : C: K/ I4 W$ a# q5 ~8 [7 i

' N- ` l& K% ~; V% p5 X9 w

3 d! o3 x4 e5 @+ S9 t! b* ]9 w; T  7 V: G+ @! P( s6 S, I: N

+ z1 r/ Z- H' g! F( B

# e- \* G3 c- g4 K1 S9 k         $this->image = $this->is_image(); 5 m5 d0 f" j. U5 }2 H

: w( V( v$ m+ \4 T8 V* j6 P

" p0 J: r4 I+ W% _" d: v0 s         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);$ S: a" T3 w8 |: m" M$ {: |/ N$ _

1 g V5 Q+ e7 k# K* @; L4 z) V

Z4 o' y U& E, P) D1 R         return true;- A& J' z& Z! C! D% N

9 Z. o5 y( \5 i8 L

* e* y6 j1 t: P5 }' `& s+ B     }} ) ~9 F% W8 k% G: P3 V h

2 L6 h0 x* `4 k5 G$ m& d

" Q. k; O. ?; @% L) h5 _' W% g 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:! l+ S4 R7 U/ j

2 P) o; ~9 ]# F6 S

1 J2 C6 H9 i l: R* ^- ` K4 p <?php: |0 J+ u# P; c& Q- ]* w" n

- ~! m- |& i8 |" X( h( E

8 N8 a9 r$ O, A3 d     function is_allow() {1 f( K6 o3 ^2 W* { ]6 W! A: {

# c% Z! }, K4 b6 l# W9 L, `& z

" i6 `9 s i' i1 B0 Z% |         if(!$this->fileformat) return false; ( \9 v5 y+ Q: W1 w, t

/ n! q6 n, A. w/ |: E( i4 X

* m d9 J/ N4 s         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;1 W' {; E" R& N9 c$ d

8 |% c( U( e& O9 ~& v6 E. }

+ T8 t- B$ v+ c7 J1 u* Q; U         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; 4 p3 U e, _: B' ~

% W( ?: }) u% n8 `$ W( w; w9 |) C

$ m0 @- ^7 l0 a z, a         return true;- t2 c: G# Z' G' B

$ T _: ^; K) M6 n7 t9 Y h

- @! ~. @& O& P A     }! l6 t6 m0 k2 u2 d

8 u) P, S! N, I1 n% H* z9 d

! e6 l% W; q1 P& b/ V1 I 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 4 e$ A( t8 ?3 G! ]# X

1 Q( q" z1 R$ l

9 `% p+ L- [9 {% i7 \7 R 接着会进行真正的保存。通过$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文件。 6 D- z4 a I# P

, F# J9 w+ r5 E* s2 x$ R

4 @( |. }& ~0 C2 w' S G7 U 漏洞利用3 v& {5 }6 j+ W; `- d' B8 ^

- x1 o m3 V* d4 v# Q& J

& C% O9 s6 }* b3 ?5 a% Q+ h$ j 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 + H3 a0 c& B4 d* f% ]5 v

6 t* O9 _ |. q* u4 j$ H

5 ]7 c* z. q6 B( U) y   ) E* H+ b6 R. o2 t/ z

2 u3 P: A8 P0 u/ g3 R/ D8 K

! R) e6 I! `5 h% @2 ~3 Q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid$ H5 l% _# T1 D8 D. t

' ]: B/ P4 O6 j; \8 H) D; Q4 G

! @ }6 I" T' t+ B/ d+ { 不过实际利用上会有一定的限制。1 @9 k2 F; T$ h9 Z7 A9 R z5 M2 T R

' I1 D, I. G! ?' d- b2 _. X- W

9 s0 x1 E; L; t% b* I 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。! E9 S/ ]4 C0 c) w4 {" b

) c$ e7 W0 P t1 o! }9 L

, }) q3 Q3 Z/ D0 N& w p   ) Y5 `6 w& u; u

$ X7 H+ W+ I2 a, w

a" \& j& K) [+ [% q. h/ }$ D8 R" r: y 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:" ]- [, m" Z' L

. ~) g3 ]3 J; G+ \% q3 B1 R

. i7 z. J1 t" {& ^! 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]);省略... 0 x& W0 X; y( @( g. K

7 m$ h E4 l6 V6 [8 q; r9 I ^6 X

- b1 K, f! d- _+ B$ K 因此要利用成功就需要条件竞争了。 5 f# n; V, x6 g& \5 J

& K! H6 D8 F# I" e: \

3 B7 g. U, n! C3 X( c3 c 补丁分析 ; e2 a+ H9 {) p1 l/ V( `

6 T# h( M# _; P1 Y' a4 S8 Y# t

0 t# A! q" R: ]8 P, b4 c  7 U5 `) X( B7 E3 W5 H- b/ p

/ q. l! D1 P; L

3 x0 w8 u" y. B* C! r8 Q" h 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:( f8 q9 X+ H+ E' K+ D

2 I. b1 e: t6 g9 l

* G7 N' R) Q" M" @2 g- b- h2 U9 |/ a4 \ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}( e0 g2 ]" o( x) A# y

5 U7 [1 O" \2 T0 p5 ]4 Q

% T! i4 m, F' P# s, }1 e  5 |9 w" M! R; Y0 R$ }

2 ^6 i7 S% ]( g$ t9 W9 Y

" e$ ?, V ]. Z) W+ v3 C- @ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 + O9 i3 m( R) r8 S& M

1 T, t- o7 [0 x7 N8 Q% A

& e: ^6 @+ \7 k; F 在is_allow()中增加对$this->savename的二次检查。2 f1 o# G( W3 N! P6 K k- e

0 j3 ]1 |4 w* b7 u# Y

J, ]# J! k6 T; l$ q 最后 - T: v0 ?$ \# O( V* X2 M5 d

0 l$ d) i, ~5 \1 J6 i

4 E; e$ o0 i1 D. w/ C 嘛,祝各位大师傅中秋快乐! 4 m' e- e3 ?( i

* M3 i* M# o, f1 H$ r! v

! I' i0 K! Q! X  , N7 A1 [7 \' Z$ W/ i

# Y7 M6 z$ T! B0 I& i3 z7 H
回复

使用道具 举报

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

本版积分规则

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