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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
) I ], m. R5 w) e0 b( ^8 I

0 S3 S) s9 J7 t4 ~, j9 E! e6 `5 `! I

/ [2 Y/ z$ L* B3 p [' W: ?( s/ ~- L

& @! |, M% L% u 前言) p0 {3 c( ]6 d1 ^/ W

. W2 Y8 o4 [, p3 ?) @+ }1 M! B) X" U

V" \0 _0 j9 r f5 Z/ K 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 1 f- {: g' R# z! Q

8 n9 [ F6 J6 j: C

3 c& z2 g# {' a2 T  / J0 ~/ B1 c. l4 x1 ?% x

2 T$ e, I+ s8 \4 c

1 N6 y A3 R# y* b U1 Q 漏洞分析( Q' v, I2 {* n% {. ^' S& a" l

% i E* {. ~3 f4 ~' D

# |/ P9 E1 e8 l# a3 C 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: $ B1 ~9 h m2 T4 o3 G

% b* e7 L+ d/ h. `/ \' }! \( {; N1 p

6 u7 S' y0 o) x0 ]! A  9 W F+ W8 e9 S* i

' a/ @$ a: E2 u

6 j) G6 T2 |- s# u" R; |8 U8 _ 对应着avatar.inc.php代码如下: 7 v1 k& W# p }8 r Y" s* v g' Y

5 a9 t* _! o8 n' J7 m2 Q

; r! m* @3 r! R( Z& F; O5 v2 s <?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) {2 ~ ^8 @; o. O. d& a+ M9 P7 j

' Z" N/ C8 x0 W8 i0 ?- Z; k" U

+ r/ K( u. s3 o, A, w     case 'upload':0 N7 z& ~& c5 L- H/ a* _1 J8 k5 B

4 v# P" o9 P6 T8 r/ J

0 z' m* D' m4 k         if(!$_FILES['file']['size']) {7 L0 @: `6 b! T% \' c2 L% N1 v

8 I/ T0 c9 I+ y$ d8 ?0 l3 `, R1 s

y3 u9 A4 F& }& f3 P* y4 r2 l             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);0 h# S' b/ @$ h, ^) O1 M

' G1 A# X5 p: n* p( O6 f( Q1 J! }

' w# v# L( |1 ^ x z/ O2 S             exit('{"error":1,"message":"Error FILE"}');/ d8 P( p% ~( w7 ^8 G0 l4 N

- O* A& |) S: l/ O8 \& w

7 G3 }7 G& N1 i+ ?% j         } 2 r s' c8 r6 f3 Q3 S% C. p0 p. g

8 I" h& u3 n. Q, G# b2 j

- G2 P; x' d3 g L         require DT_ROOT.'/include/upload.class.php';* ~* d( v" y/ U- e. u/ @

" D- [! \; W' t% w4 B' `0 d o% y

$ E: {7 H ~9 D3 P/ x7 t9 r  ! k7 d% y, a; L4 J% p

4 o+ @$ _% M/ p9 v& _

! e1 q7 ~& |, G$ L) ]: Q         $ext = file_ext($_FILES['file']['name']); ! Q. [- ?4 k' j2 w9 z

6 B: Q; g) c a

1 H; T( t# K* C+ G         $name = 'avatar'.$_userid.'.'.$ext;2 D7 m [) H; _/ \8 k

& d0 o9 A5 C( f4 V6 @# l- p, v$ P

8 u- [6 ?! r8 l* g9 i+ N         $file = DT_ROOT.'/file/temp/'.$name;. V) c0 O# \0 I( q; Y1 `

3 R# o# @4 M/ c. b8 K0 T6 e

* S5 l x* ^4 k, i  7 ^. Z: N9 [2 v! H! z

0 p7 n/ ~$ y: @% |, |- v) a& F/ G

' p& ~/ ?) j3 P+ u r% f         if(is_file($file)) file_del($file); / D; Y' h: _+ Z9 a" P% S

1 _8 ~$ s6 S* T

! E S! K+ A, u2 C: f6 B7 ]. z         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');/ Q7 {+ r6 v( T9 l- W$ a0 |0 v- T. V

$ _' h, B1 X& b4 x* @% _

# L# @; [, m5 a) s   ( A- n7 f4 c! u

3 u3 o* V7 I4 w/ v

- ?7 l# c; ^% q/ Z0 d' _, D         $upload->adduserid = false;. D @3 V" t1 `5 j$ X# U7 \. M

2 ~: w, C" D; I- E, f

: k2 E' v$ {+ A% t   / ?* Y3 g7 I3 T$ N% p$ i

1 W9 R; D" A8 d- e; b% \5 g

" k9 g m" M2 w. q& |8 p         if($upload->save()) { , V9 H/ b% Y- a6 A' u

; U/ G; n4 G7 C4 \

' z+ p3 a) f6 j- o             ... & r& r% Y1 k' t% Q$ ?

. _3 h `0 Q& O' y

/ l3 ~3 x2 l) I& t4 A* e         } else {- I4 m6 ~& ]$ d

5 `7 c) H. Q! |5 G" a6 \: R/ }; j

" v2 F4 R4 \' O* C- t* z             ...% f* D$ n0 O, E/ X8 m( D

4 n. n. }4 W, s! {5 V% P7 J

# s1 t$ l' O+ q5 P) L         } ! C! ]0 w6 z3 L8 u

: u# p0 }/ P$ J0 ?% f

" ?1 W3 w$ t( x4 x, A0 _     break; ' _3 T# y: \1 X2 B

6 v# g' A1 N5 Y e

9 }8 A/ `( z& k 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。6 l9 J* l- n8 M p" \3 n8 Y

# F7 l- ~% G* e c8 o0 @8 ~2 {

+ G- X X3 h [7 O5 h+ ~+ L) D) g upload对象构造函数如下,include/upload.class.php:25: 9 t" I Z! Q: A9 E

; O' D3 R4 W7 z; S& M6 |0 [$ ?$ Q7 @

% ~4 t& @( m% q! L( H <?phpclass upload {+ I; b! R% A4 [7 G- }1 T+ g& T

$ Q) W/ d/ f. O5 C) x9 \

1 Y, a# d! z$ h9 o+ O& [     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ( A, V0 }' Q- H7 C/ ]* b" `

4 C6 c* J* C6 c' A- }

/ f! e: r; f) l4 ^/ l# E( z         global $DT, $_userid; ( a1 H* Z7 f2 |( `# t

3 D8 ~+ B1 N8 V; t3 T

" }8 }5 C6 s6 F5 d9 N4 v% f% n' a9 V         foreach($_file as $file) { + z3 i: ~5 @. \

/ ~' d* J9 O8 x w9 R7 f: l

4 }& h2 G2 |$ F8 A0 n# X             $this->file = $file['tmp_name']; - T6 W/ O0 ?5 |; W: g3 `# q- E% x; ?0 G

7 g8 e' D$ S2 H) Z; Q5 e

+ \ E) s- A7 ?! A             $this->file_name = $file['name'];6 S: S2 u9 w; f$ l! U

" C. b. c- {2 m8 {( {: I; B

; f6 e. D1 G7 T/ H3 d# o7 `5 n             $this->file_size = $file['size'];- Y$ q2 n3 M+ b+ G% S% `) b

4 X3 d& @9 B j& o8 i/ `

3 l1 E2 b& Z# W             $this->file_type = $file['type']; 9 C: F2 g4 f) _3 N& S; x2 L

- S; L# Q) U9 [% |

. X" ]+ v* n3 x! r4 E/ L- {             $this->file_error = $file['error'];6 I& q: s( C7 u, l6 C

& Q( M7 x/ k$ x; H; e

0 U( G1 s0 ^$ r3 P& }0 W; d   ) y @0 C9 e7 u8 [2 c: O

6 t& }* h/ d/ x# M* g0 n: w9 M

( ~% T. O( a0 X5 t2 V         }1 E% L3 p* X, p. w) E

3 ?3 _* O- C" e% I, }( g& Y

; b; @6 ~2 \2 ~6 s7 R) L         $this->userid = $_userid;1 v% t. A1 ^9 W% t3 R

( t; a4 z @6 a* T

! Y# G. q5 ~: V7 s& y         $this->ext = file_ext($this->file_name); . @" ~8 J+ V8 @

' U6 d3 O; A+ d( d

3 ?4 ~0 |6 A& R& c! v         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ D3 W1 ]) \$ p! J# ~! Y

/ t4 D! Z9 | q% I- B. L! c. G8 B3 _ N

8 g( k/ `) u+ H, P; T- r8 v$ _         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; , P; H: q" ?9 p$ q( \2 w N, Z

% x$ U1 K' }4 z) ]

/ q1 Z5 T1 }- R& N; A( c         $this->savepath = $savepath;1 B* z0 p. t6 H7 N

$ c( n. L1 D; A* x, Q

$ N- f, E) s. J& O# I         $this->savename = $savename; . W7 c) r: V( s2 t5 ^

) X& ^2 ]1 [- Z7 _& h3 ` @+ f

& i" N5 A% Q: u; [! _. L- m     }}) T- O2 R* k- y

5 s. B$ U: r0 L# N1 v/ U1 A

) G: g, m* s$ s) {/ \$ i# S2 @& m; u 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 8 P& `4 _# A4 C6 e. H" v

2 l/ u4 u: }1 M6 m

$ L" E+ ^7 h4 H. | 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 % Y. A) z' l4 S8 \0 i8 C0 j

9 x( u2 j3 C) Q

) m; a6 R0 E7 o. q/ 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 6 r2 J8 E' F: b* e) W

) s7 @: U0 e0 e2 }2 N, ^: |! z

, A1 E% C0 c' O" k% U! x 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: # b$ v0 [3 H# K2 _0 M) v

) _6 L3 {' o+ a9 Q0 ^6 @5 J

0 P+ b7 V* l$ [* a' L# B   ?2 R0 h7 k3 R' e

: h K$ I* q& y' d( h

6 @. w5 d& y' H 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: $ j" ]" v: Y' s3 B# [

# b5 f$ T+ F( x( S

4 i& l& B$ F. z2 |6 T; { a <?phpclass upload {3 |6 T4 m2 g" Q' v1 R- ?! y

- y9 X, y6 A' S1 _( c7 g6 F

2 V; z" A9 M+ T     function save() {' M. P3 x5 Z! a8 m* l3 {3 {% o4 Y) ?. Z* r

# J7 D( I6 b. v4 B

0 \; R7 `$ D& Z7 c9 _+ ?/ ?         include load('include.lang');/ Z G4 ^2 s+ f$ I9 x( D

) q, ~% x8 C: n: b' X8 i

( s$ b6 O. X; R" S' N3 y) J         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');+ Y3 m( a( O7 J- \2 t- ?8 z! L8 L

f% J! V3 l, I! g4 q3 V! M

$ Z2 M6 ^7 N" E3 J% ^! g+ l   v% j: J+ W% v

, z+ f: ]' l# g7 }3 t( j+ k

& H" u, H0 x( `# N! Q" z6 w         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); / d' V4 \" N) @7 G$ J/ U# I

' ~* J6 T. f x6 z9 ]

* m4 x% W" q5 m  6 a) R4 z! B6 F) R

. }# |/ \! y: _) V1 C& n4 V

# Q7 j1 z. @5 C         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);+ A' h9 X. }7 f. ]/ d

. D, @1 i( W3 Y8 s9 k, I5 y

9 K1 U) R) V3 P. q0 l' l# s) b% p   ?4 o& ^2 k6 b. R" V( J4 ^

! M/ L- h7 ]1 s X/ i. O. H/ F4 m

# S9 ]. P5 @! s! G         $this->set_savepath($this->savepath);+ g* H p- O' A6 R

A6 l. A% Q4 Y' ]3 z) e

/ I! I) n0 U* G! M4 n& H X& P, t         $this->set_savename($this->savename); ( U2 Z8 H# P0 w. I8 s

/ |+ J, Y* M( m& o2 `% {: z8 O

$ m S$ w+ P/ x6 v  5 I, `' o9 U }( G1 S9 ^" ]! D

4 h/ i4 s0 X$ k/ t

; w( T* ~! E& U( ]' l# Y* `# N4 }         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);# V* Z5 F: k, r5 S/ f

6 C% P5 H; c$ z) F

0 S4 N# Z; N* h6 P: [3 F         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 4 \5 V: K: |! Q8 V/ s

+ M# d }' j* f

# T/ d& W/ L/ P8 ~         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); $ F3 R' Y! q2 L6 z6 Y+ q

2 o2 h6 O7 I# m! X& f4 X( |

% e/ b% a w; L) c/ D7 M' j6 V( d   + ^* T8 H, W1 V* F/ o

$ w) y$ e% K3 S3 f: l

5 t; O: x& {4 R2 X, {' D* x, e         $this->image = $this->is_image(); 8 M/ j+ w/ J& _& N( W1 z, h" @

3 |& _5 `$ Y9 j: e+ ~2 T

2 R! U! O* \2 C4 A2 d. h         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);3 g' s) z1 D/ G' m6 e7 d

9 H; b" V- f( H- p; a s

3 ~% V, K* P# J: L: w7 D         return true;; U0 t6 H: M1 ?2 R& }7 N. O7 p

4 m, y% p! }8 Y5 C9 T

0 z+ ?* B$ N- s& }6 P, D     }} : [/ \; R6 L6 i! }

3 L$ p+ O) j) @ |

3 W4 T" [: W; |4 w% c" d+ ?# w 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: * j# R/ f( y) J) b D

9 p" [6 u. N2 W0 c

1 ?" H6 W4 I% q. L( r6 V* c <?php# b) L1 T. }/ S( a7 x- B

$ R. D; p# [( y1 O$ l; V

$ N: P0 d; J: k     function is_allow() { 6 t. k# S* x1 k8 C, \# y

- B( V9 v* Z2 H; S( ~: B: k% p! Q

0 R, p! `% E0 G4 A0 M         if(!$this->fileformat) return false; $ m+ m# x+ K/ [& i5 o. S: N$ x

6 N9 z4 u2 B( C- N) I

+ F$ H/ t s* a/ v, C4 Q' \         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;' Y8 D& S$ S& `* }% t/ `+ @

' C$ ?; ?' E# F' c

" A1 k. Q; B; {8 n( D         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; / z5 c k0 v+ W& U

4 O" z" ^! p, K3 `! m

* [6 V6 Q* _2 {         return true;6 {7 I" {. ^9 B# P. @* Q

9 {7 r; ^5 o9 X8 {% y

0 f! ~# M# m" z0 h7 ~     } # j% w% O% _" C# s+ M+ |- r

2 w$ N/ ~# |( X2 q4 ]5 H4 ~! {

7 _" F- J y: w& k 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。* G' s) d4 m4 d4 l V* A8 j% l' b

5 @. t9 o9 V5 C

2 C1 w3 C: t/ Z- w/ Z 接着会进行真正的保存。通过$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文件。" C. I3 L: F0 B

( {, G+ {) f$ E

9 o. I( E/ T! p9 W 漏洞利用8 p# W$ X J7 o3 F

" n1 e6 ]8 j' `: X9 [2 O+ k* M' p

3 L) m6 O5 A F- n% e 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 + p# U7 _2 @( b" ]& v5 D

A# X% V- A$ ~# \) U8 N: H) C

7 \! _( a2 v- v" }- o+ e; c   , X, p, H( d/ C) I" r5 Z( Q4 L' E, B

6 @! z [# O$ p

. v/ D" n& V, e( p 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid: w/ _2 I' O" C' [/ c$ J7 p

) r4 [# @; z6 i

" g# m/ p' b. a! r 不过实际利用上会有一定的限制。6 n. R5 W. P4 [) y, p, ~3 a3 e0 z

, s# { N$ g! ?3 f: b/ v' K

- [9 H& r- g5 b; k% q$ T1 v. N 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。/ D3 L7 y+ q& C, z$ T" x

6 V" [8 J) Y- k% q% Y0 k

/ V! ~/ K7 G8 T1 P: h   1 U6 k0 O8 W- F1 y6 \" W! k

6 H' f# K2 [2 a) r3 \

$ a, y% M* V* C% L 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:/ x0 i$ e* o7 f5 p% }

$ O; H1 i) j' v* Z, J4 ?

, b$ q' {' P% Y+ h 省略...$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]);省略... 1 R1 S9 _( @. r

/ Z; N4 C0 B& \/ E3 Q7 L. i+ U3 R) [! f

) o* { d6 j/ {: ?7 \ 因此要利用成功就需要条件竞争了。 3 g, g- F! v! h" q" }1 h

# O/ j w( i! Q+ B" G, J6 V

4 d3 y+ {% C) C( {9 v$ E$ h 补丁分析 8 k; V. k' t) Y1 `9 Z- F9 t: f

2 D7 T: h- P( K, n7 b W" Y( Z

4 o1 R8 W; ^& b9 h* [4 h$ J* G   - b) w! k: Y# {' D6 j% w6 D

5 V- u: O% O" ?6 { Y

$ @, q/ J- Y& `8 A1 ` 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:- C7 M% E0 S2 D R9 w9 e

, r C+ j; e |- `$ |* o

, `1 F# o2 E' P; I. A5 _5 t/ M function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} . r3 {% N/ H2 L% W3 H+ c1 k- ~

% U/ O* y# g ~3 _+ B$ j

! r5 r" c! ~( Z6 D& _  * R! x o8 Z7 r7 z8 g3 t. @* A

8 {3 g. A$ v D% ?

/ G- ~6 K. a# q& M! A e: o% ], r8 H 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ' O, w0 `5 F6 t2 X1 F

! z4 O* }0 \& p

# P, F! M1 l2 B% p 在is_allow()中增加对$this->savename的二次检查。 , n" @$ j) C/ J

9 X, H# I' `7 C( E

7 C9 `* U) l0 r. l) G 最后! }& P0 z% C1 w& v* N6 F. I

1 v8 V9 C3 x1 V% m7 a6 O Z

' F, K. p6 s+ p 嘛,祝各位大师傅中秋快乐!) f4 u! D7 g7 o k; A) a5 n

. R9 N$ V) r3 ~; W

6 R: q9 T) t; `( N  ! d( Z) D/ \" o

- W' _. ]$ ^: p# K$ Z1 f4 P
回复

使用道具 举报

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

本版积分规则

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