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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
- z4 d# I2 `7 u& j

" T; b2 M( v. O( J7 ]& o

" n2 y. U- A9 o' c l+ c

3 ?' B9 a0 ]3 v# R 前言 & z. ?2 c1 X) R, ~+ Z

7 z- v: p. ]) ?7 m7 `

$ U0 U9 B4 s8 e& F, M 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。# h$ L; e& `/ g

( S; d, [6 d8 P' H/ y

: [6 @1 e" A% N V- L, V' e3 H" P `  , V4 y% Z+ ]3 K

2 G1 U, x) i* v/ ?5 E5 ^

) i, C1 q# d* U0 W6 E 漏洞分析 + V: J+ b6 V" I1 |' V

! X7 j8 t% x; H3 T( |/ ~, x! s

" O. Y5 a7 b- E( ~& t* c. m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:+ w% h, i7 g: |) }

) X" H; ]9 P6 i" t

+ x0 @9 R6 I2 j6 |7 D   ( _* ?9 {) N( I$ z

: I# L$ W0 X" ~1 T$ F8 Q* p

3 P/ {. T& b% @5 [ 对应着avatar.inc.php代码如下:4 ^1 F& k% `) h8 p3 p' K2 p3 L& M

1 f1 K2 t+ g" n7 y% U7 v

9 Q# ~" E8 N( z- M6 E 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) {( v& V/ S. L2 A$ E

/ ^" V% A9 |4 |( F

( W* |$ a! y& i; E3 b     case 'upload':( s& h; g2 i$ d6 R$ M2 J

, i, e% X6 F. J4 d; x8 L4 D

' \: E* s* p' | v         if(!$_FILES['file']['size']) { 3 L9 O+ Q0 q# F9 P3 [2 @

: [; T! D, F5 z8 a2 a- L2 k4 F

6 e' m# U2 z8 A. k# @4 o             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);8 E( s0 P. ~( H, q" C @1 ~5 ^

7 G2 \. S! H, o* c+ S

" y# f! t" H' J' B+ b: R             exit('{"error":1,"message":"Error FILE"}'); ! l. l) a3 k2 A

% a9 m/ h1 F" B: {" s

4 h: M' Q" n1 _, X         } # Q" K! V$ c0 t% z

: }7 L" Z. E8 G, n( ?

. i) K# d" s5 v6 M         require DT_ROOT.'/include/upload.class.php'; " M! u a# e8 |2 p+ R2 J9 V1 Y3 T5 ]

' u/ P0 I, g& V7 a

; z2 u9 Z9 }% Z2 E  , P9 K1 ^ Q5 l. `

2 U: i, l# Y7 x+ [6 K1 k

3 e9 v" U0 ~" y O8 T! Q         $ext = file_ext($_FILES['file']['name']);; X, N- {& V2 S' I& n) U$ @

/ ~) ~ V4 i5 z; W. z: _! I _3 o

& \! N4 l5 |9 v6 x& \- {         $name = 'avatar'.$_userid.'.'.$ext; 2 E) g# R% T! \( Y/ t

! M# D, I: V8 @' l8 P; }

- V; ~% B( m% v" M         $file = DT_ROOT.'/file/temp/'.$name; ! i8 e; x% D6 a, X# p# d

) M! Z- k9 Z: R# o% A' u

5 P; b t8 t( S! L- a4 m! }   " ~+ K+ A8 T2 o3 U/ i/ a5 F

$ M( v/ U6 [+ r8 |

# v! ]$ I) L1 R+ l2 M: c; p         if(is_file($file)) file_del($file); " N3 L; l3 k4 I9 l |0 m( {

+ m: W$ F; J/ p7 a

8 u7 Z/ o% v1 R/ |4 y5 r+ ^         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');! u8 ^ E$ W, f

; u& h# L7 O5 g4 [ }$ L# U/ A

& {' Z" t) M) v, y   - b# ~) \8 Q' g; S2 G$ p) H0 B" u

. d B9 z3 o. h; P

# H2 {, [: ]. ~, @- g# {         $upload->adduserid = false;* _& }% T" o0 e/ Q* i9 [& @

1 @" \1 B& Y, w9 w+ o

. {* M9 }) ?; d% l   ) B, U; m% l; y4 h3 g2 t$ B

" h8 `) m# H2 C6 b/ T/ [

+ o2 I( \. m9 C+ I$ ~         if($upload->save()) { + j9 o8 Y0 Q& k8 ~

2 k) z- b9 V9 N; B. {

) [! X( v) l5 H) ]6 S# ~1 Z             ... f4 a% h: I) t1 e: v, p/ F

3 U. [5 X$ F$ G

: U9 b* Y S/ e0 S/ o2 b         } else {; V: n2 `! S1 B3 X9 k7 a i

) K7 F4 l6 a: b3 Y

! Q- j4 {* h" k: D- t$ H             ... " i0 C6 I' g5 S( F5 o( f% l

! m7 K, x! `( L- e+ X8 y

W8 \) k( W4 _0 p3 k* E. e         } 9 p1 {( r, e% ~0 d n5 ~+ Y

! O7 Y8 u, b- k: f- b! E9 Y8 g

% H0 B1 `4 }0 ?     break;( k9 u6 A0 p1 |) H4 G# {5 m

, U( i( d" D+ T% F9 ~+ V" z3 e

, z& W* B& k. h u; s) a 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 9 U+ Y9 T; N7 B

9 B2 H& `& U% a1 g% }( `- C

, h/ ^6 d6 E& O1 T' C upload对象构造函数如下,include/upload.class.php:25:: K! K5 G7 V+ E2 N

9 p' b. ]1 B7 l( k' i

, A& Q) [. F, l$ k( L, G' z" C' K <?phpclass upload { + y% B4 D+ ^9 W! y

6 y3 V# k# X1 v! ]8 y u

; x( ~9 t6 H8 u; r1 ]     function __construct($_file, $savepath, $savename = '', $fileformat = '') { * M' R3 m, \7 f- k# |6 n

- G. T2 W/ Y8 ~& L( q

/ S- g% E- W, W4 T         global $DT, $_userid;8 k; ^3 t- c, D

" O0 ?! {( Q0 _; O

7 t% r1 S3 Q. o( Z6 q         foreach($_file as $file) {4 M, s# _; h8 W- W) _+ |, N

8 G( | V" K6 q1 X$ V& G) F& k( \" G2 v

7 X& N/ L7 |9 b# X+ E8 c             $this->file = $file['tmp_name']; 6 ^0 f* a5 A8 x* {! ]. M1 g: o+ m' I

7 c; E7 o6 ~( F' Q

$ b! \7 H! T% z0 O             $this->file_name = $file['name']; - m7 Z" C! R' H9 }. B& ?

2 N( u1 B. F- t8 U# C

6 ]' }7 W9 D% X7 L9 @& I             $this->file_size = $file['size']; 6 P( d5 \7 p8 o! o$ ?; r5 o

: ]0 s, H/ M' o( H L+ ]

$ L* P- Y$ u3 e             $this->file_type = $file['type'];! c* Z3 Q0 b' D/ K

9 s, E- e+ T$ T& \

# Y- F; i% f( b( u7 ^7 _             $this->file_error = $file['error'];3 `+ Z" c3 d" e2 {

$ Q# |- k+ W6 m/ B9 Z$ j" I

2 a0 ?0 C! c/ ], j0 [( Z  + Y& w3 K0 r5 I7 i ~ {2 O

/ v) |' I' I" f- X5 p

- T% s& D# w1 t/ [& A, j: ^         } ( c; b2 k! f- S! [3 E

' c. _. T" u- h: s# q# p4 i4 f: u

$ ]! \4 \/ \: \% s" V, z* ?# E         $this->userid = $_userid; / c5 m$ M' ^: u s8 c( f0 I

4 u" l; D9 R4 r2 d% o6 N8 J: |

5 w8 z, q$ N" l         $this->ext = file_ext($this->file_name);7 f( ^- s* y- g: L0 }8 l% U

/ H: Q5 y* ]+ Q. {+ _% G

! i/ b# e6 b% [% p4 o         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];$ ~3 k0 ^* c" e4 M% k1 h: a

2 K( S C+ n: i, ~; N! |) D. n+ b

' ~- ~8 E0 c( h, I+ L" W5 d         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;: ^2 W2 X# z9 Y$ r+ k G; p

. `( a1 @4 D [. M; q+ S- Z4 p

) a2 a; r* `: n         $this->savepath = $savepath; C- G$ i$ r3 p l3 K, r

* e& K/ p; j" m3 h, ]7 _/ [" {$ r

4 {) W$ B3 b5 Q7 L6 _3 V: u1 ^1 r: [3 ]         $this->savename = $savename;% @; |7 s: Z( |

4 x9 W D: |0 v* @- ?% c `" ^

1 e+ e8 K9 {( R Z# h o     }}5 q' ?0 f+ @4 R2 Z9 z

; K7 [" h0 J. E0 c* [$ t) q

1 i+ X$ H. Y* ^1 i: E/ O7 @ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 ( f* J- J& H$ Z

' J w& ?4 g- ^& e/ Y) i

M+ r* C Q3 L8 I) G% R 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ! r/ N8 ?: z5 l9 E5 ]0 `) k

% V3 d7 k5 w7 ]: u' O, h# x

; g8 A) M! P. m0 A $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 % U4 N& B3 J. R

* |2 t* z5 V# o. Y6 l i% s0 c

7 G6 H- T: `/ n6 j3 o 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:) r6 B/ H% b% ?! |0 f; w

* J; k8 {* D" [$ W) C4 K

0 F. o) O4 Q/ O   ' H( L! C% E1 N3 |9 R3 _5 o3 c

4 e( V! S) H& G2 N

% d0 a8 w% f+ \$ l 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: : R7 p: n- d8 v. q8 ?* l" K0 l+ Y

# X6 \2 N' O8 } Z

( F/ K6 T% O5 T <?phpclass upload { ( ]4 U3 h- V% k4 ~# t

, l4 r+ N" w: S) N9 A" e. Y

; h1 h. y- g+ P, I6 b- s     function save() {! ~$ z- j* ~6 ~' U/ q5 d

/ Q4 z% u; p$ D( U

& K$ N! X+ ]: b7 ]( j/ [ t' E1 L4 H         include load('include.lang');; b6 _$ x6 \ ]) P' V' u

$ u5 n3 x: m7 L5 F+ E5 ]' T: F

. [; M7 N5 h& H$ ?3 B# p; w6 k         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ' a: j0 m+ w9 V& N+ ?* V9 B% s

T& C" X% }. z5 Q

) T9 r Y: _, Q( B; k" {  + h3 V$ X$ l4 c" x) M8 f

, j1 S, s% B x+ t% t

7 J' P; V0 q/ ^! u( k; s6 E9 U         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); " }0 A3 f! J& Q5 o3 P' p

. O; {) `3 Q c: P9 d& V

# x; A& v7 P& W# I  - P0 G' b) f$ s; a0 f5 I& Y0 u2 {

& u+ E' m% G3 E* g6 a7 Q

6 O2 C) n* x2 e" ^( G# ?         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); / u* Q2 b( s0 V5 k

. l( k7 O2 W( L: ]' r

- G4 E, [# y$ J7 e1 Q8 r9 n   / x. z5 ~; h3 N3 i g9 s* l

$ N/ ]7 d$ @' K; `8 I' V+ j* ]! K" o

) t- E, m+ l- Z u7 p         $this->set_savepath($this->savepath);/ a" a) h, J' J2 [

3 ?7 N; ~. S7 w7 A

) J1 @9 E' B+ x( K: \4 a- b( I         $this->set_savename($this->savename);( E' O: w+ b5 n' P

# I. s# Y5 c U8 Z: o

/ F1 f/ T& L% K; l   4 y% b- z, T- D2 m# G

& U; I, s0 {: P

, V7 W; A+ d: d( D1 E         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ' T- e0 h8 e7 m

+ W/ O4 N+ \& H+ R+ X

+ E: _ D& b% [8 b- m( x4 g9 {         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ( s; C2 ^, F s9 o6 s) ?

9 _6 b8 x$ b4 Y/ ?1 J4 j& {

* O% r8 Y9 j* e% j) s" H2 ]         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); : U* @/ k* O: B1 Q& t z: W

/ r9 y7 N( e3 x6 D8 L# u8 }" S, i

( b0 u [- M- G b$ X, d2 a3 ?  : J$ Z; Y: S$ F. E6 x0 C/ c0 ?

) ^, L% n5 ^ H( [; x* S& u

. |- s9 n! y! v6 H: |7 J         $this->image = $this->is_image();% \% q/ L' o3 \1 y) D

9 b4 s) M" B" u. k" F

2 q5 P1 p3 p% r6 X+ G3 ]         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); - h" {8 L1 I$ `% H u8 d4 G

8 a0 q; }1 ]* P3 }) K! e5 N: T

+ U) B: d; r* `         return true; - f4 k, u! U/ V1 x1 g, g- h

! Y9 l2 B( V+ Q7 M+ r. v( A4 r

4 d/ |5 Y8 u) D$ V1 Z: K7 M     }}" g+ s& J# F( G1 S" r* Y; H1 R

9 a" q% u. j3 G3 P- S. y

: @: G! j$ M/ i/ F, V2 E 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 6 A: n* i% L0 y/ H/ F% Y( c

5 X f7 y4 M( o( b4 x, f# R

. Y9 x. Y8 B1 x7 N, c6 s( ^2 i; q6 P <?php ; ?! `& t2 d4 A' F. H4 y

" b6 w H1 x! C! z- a7 `& ^% e% ~

$ k8 J e6 Z; N     function is_allow() { 3 z2 F% Z" w9 U Z

* w( E$ ^( x3 | [$ R) O- S$ E

: q+ y1 X3 N; c! x         if(!$this->fileformat) return false;$ L4 q8 i1 J; U8 c- K: p0 r

, J1 o' W3 m0 m' M

: T2 f. c" W8 U' f R, A: n         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 0 I8 F# m' L/ M) P% K

# S; L7 n3 d6 }7 @$ ?* J

6 w0 f) ~$ Z4 c- A4 g. u9 {' R         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; F0 D7 ^1 I: D* z

# ^6 }$ n# T6 _! }, f0 b4 [& k# W

5 ]. r3 A* d0 M9 G5 O         return true;6 Q$ Z2 s0 u! S' |/ V+ h( [

5 ]. E7 Z4 `5 Q$ T* ~

8 W" \* w& ]7 l     }) g, E5 D1 G" s8 v+ m' V. ~

( [* j3 a/ @1 [. P/ ]. D' ]2 H

& S. C9 O, P9 _$ @ K+ t7 I& ^: e 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。, b4 z# k P1 F

- _) E; }* A- b7 U3 i8 M" h5 Q2 Z4 e

4 T' S+ J) S' o 接着会进行真正的保存。通过$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文件。 1 v' `* k4 f4 v

7 S( p8 n5 O: m; B* d3 l3 X

& V6 U& [; M, U$ H 漏洞利用+ K) n, @/ _: v

3 Z7 I+ h, u2 @1 C% F/ M

, V0 q$ D) [, Y- `# m" A/ w3 _ 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 $ O. W- w( P* M* k

! K. w/ u2 v' O, Z9 B

* w" c- ^" ^8 N$ c6 ~$ P   % a; {, \% u" K D. p

$ f, c! F; \4 a, U$ T) w

# r$ D9 \ ?0 {# R9 y# W 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid; u" ^" Q$ o: N! _. V

9 I2 B2 T* v/ D$ N+ a4 k5 W

, r d- [5 ~3 l6 p) t# J* n% y 不过实际利用上会有一定的限制。' ~8 |7 |% j2 @. C. l/ c9 S

8 `* {3 E' e, _6 R8 B

. R& h% L# E1 g 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 $ u; i9 [# y8 y% }& K5 H

3 `0 K& F* N) w4 Q; Q

* w8 L0 j6 ?' z Z$ k  " f- {0 Y+ K; D- m5 f8 e" a

* k" w& p8 Y- j! N/ K

6 M. F/ e8 V9 J" k8 X Q 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:4 e- k& D1 A& \

. R5 m$ U- q% i! w( x2 L' u

0 @. ] e% C- a6 F/ o& d, n6 R 省略...$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]);省略...* b+ Z. T/ V5 L- _

6 P/ D9 P$ h7 O1 D

' j9 G6 p' d/ X6 R* c3 q 因此要利用成功就需要条件竞争了。 1 I/ W/ m! U z1 z) v/ F% y

" K. P# Z s* L2 G7 x. t

9 s/ M5 Z) c2 u7 C 补丁分析 : G0 h2 D1 w6 J$ x. y# K

4 r$ u0 K ~4 F4 @9 o5 Q5 I

) H9 N! H6 |" ^. K1 W( x   " _1 A3 _( \! z" U

8 Q! V; S u1 I3 L& s/ F9 a

0 t9 {: X: {+ z 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:' \4 S% p1 w- \& }& R2 W

4 f4 B8 V( ?) \

, ?* l! r' C7 T" E) @; w function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} : t. l7 Z3 D! y, S

7 K# Q! F6 d Q8 O3 ?( Q

3 x. q, C7 J+ `! g8 m7 E6 [% L   & P& z' Z- A9 g! W" L

4 Q8 H9 {2 d! M! g, m* f/ J9 n

7 ?' U/ o+ |1 O9 g4 n1 K* w" k I" I0 H 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。% F" u; \/ ~; r: N: A" K

" E8 e" W. q4 u; T3 @# z

) Y6 J! o' C7 K# n: a 在is_allow()中增加对$this->savename的二次检查。 P/ e- R/ n. G$ H8 R) {) b0 t

9 j9 [/ E7 A; P1 n) N/ p4 s$ B$ G

5 i2 B! S0 ?( b+ l' A% P) [' r% P 最后 0 I# F" ?' {! I& J1 o

& c6 G; S8 B9 A' C; C6 z! C

& |2 d |2 x$ H, r2 _) O 嘛,祝各位大师傅中秋快乐! 6 y- r& \% Q7 Z5 o4 R! D. d

- v2 i; u$ E9 g9 M6 b

F( x5 ~) \6 G4 a  % E! L# U; b5 \6 E' W

! A2 [/ S0 P; a" Q
回复

使用道具 举报

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

本版积分规则

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