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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
0 _- p) g% }. i! K6 W

0 r! S( r( J2 C2 X* z

$ V r3 }+ I0 Y( W7 {

4 [, v, ]2 ]; I# ` 前言 8 i5 \" a$ l: p; c6 V I6 W! u

1 V' K0 c$ [! p) [: P9 }

: s2 B( E: D' E7 t 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。; ~# o* f' \$ @& L+ \8 `

2 C, S! a5 E9 E' N! v

, l* h% P6 e' K* F3 @/ k   ' c9 ?- m5 L. O; T" P( w+ O0 M& P! y

/ u/ r9 F, [- W4 x S+ |

2 K) `( s6 x( i, N* [8 x7 l 漏洞分析! Z4 F- T- J f8 a. H, [

7 a- v- W1 o' V" v

2 k& N; w' Y6 ]' \9 l 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: # g. d$ t% p0 t) z ?6 f

. g6 r. f& R a" G' Y/ J' y

( A+ V$ O% G6 T$ o* L# G3 }  , s" W3 d* w& H( }1 H

4 J$ {( t" r2 `: u" |+ j. m# r

" A( l5 h9 l8 a& [7 L& @ 对应着avatar.inc.php代码如下:7 {. E7 P( f& X0 |+ Z7 T' I( |8 a

- t D1 _' _( G+ F. O. v0 M' E

$ T# s3 z' X& Z4 G4 g <?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. n: f9 R8 `( L9 E0 Z

3 U2 J$ x; J+ s# Y! N

- M4 W- {0 T' u& {     case 'upload': 9 n& P( v6 @2 y) @

$ c; T N- ]% y) r! ?0 F7 B

" p4 d! ?/ |: U* y         if(!$_FILES['file']['size']) {9 L: O$ ]+ V1 n# j: d F

3 V$ V7 M' A+ y1 i% t2 n

3 L& H& X( F% K+ s0 O, E             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);; n8 V5 [8 P0 F7 {! U( j8 k

: _ _* F$ t) Q

) u; |5 v& d, p( d7 w M4 M             exit('{"error":1,"message":"Error FILE"}'); " G$ }! A0 w3 o2 Q) G5 w, X, E+ h

# x' J( G& u! C/ b0 S, K

. [2 c2 q; ]3 h( a# G7 D9 ]9 U         }9 G- }) ]. u$ q9 e/ d9 q, x* y' t

8 U4 z4 O2 c6 B& \

3 y7 ^; L1 n- W) f- H9 d         require DT_ROOT.'/include/upload.class.php';1 t# H( r" D* {! Z) z& @

" S8 } n G: Z4 A) C

" U: [7 c. S1 V  1 U- r& d( O7 s$ U+ c

* ~; B( D' H8 K

8 `, G6 M n$ o, y6 v         $ext = file_ext($_FILES['file']['name']);; a, Q1 b, `+ Y

2 Y/ N3 O- S& h1 t! |3 L, l

8 ~ F6 |! e, C& c         $name = 'avatar'.$_userid.'.'.$ext; ) [) I7 U4 ?- F( O4 J

2 x ?, Y) b7 E0 v0 D6 l2 c0 |2 e1 n

: f7 _+ V$ P8 j' Z6 V$ p) @! u- y% Z* z         $file = DT_ROOT.'/file/temp/'.$name;1 W9 O) ?) {+ R+ f

3 Y7 G: ]7 {5 d1 I

* D# W4 [: c1 k+ S8 e) T1 e0 j  ( a/ `( }! q7 d) Y/ j

5 [7 {2 d3 l3 Q1 V: F! v" R: Q

& F8 d* r/ c3 q( R! w& R: _. W3 K4 F         if(is_file($file)) file_del($file);* R) i5 ]; ^$ H3 e% B

9 L( T5 k7 x+ @$ B: V8 L

3 H. x: Q: n4 @5 [+ c! c7 E         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');8 V& `. V/ V3 A

/ m2 J, G; n- u( I; P8 a) }

8 {2 w3 Q1 a. z& ^  5 V& a9 G$ E) F7 {8 O

' A# T3 w" e+ z) }. O

: M: K/ }8 k* x6 f4 q         $upload->adduserid = false;' S/ N% @3 e% p* U& j

/ B+ e% b/ V- B

# r: G, K% ~9 W) |+ k( k   ; }# \% ~0 H! Z* t$ a

3 H& v2 f! p0 R+ J6 L3 Y3 R$ B

& k0 N) I5 O- x8 F( Q$ ~! ^! m5 ?1 ]         if($upload->save()) { " ` L. \3 `2 a; M! m

% |3 o$ Y% Y' @

) s+ v& r+ e' ^- _* Q& z U             ... , c" f0 @" R5 p& T

2 v* Y0 d& f9 J

z4 `& H2 G i y         } else {6 l2 x: Q6 J" T% V. k9 u

7 T' K2 \5 z( o( F T. S

6 Y% i7 n4 Z' x$ d5 {8 V: A& g             ..." I- A6 k( N* z: u# J. Q! c

8 h7 v x6 r0 D R. B2 A

; Q5 _& K. q4 d. N7 S- r4 i         }: M" f. {2 g/ p d. Z$ o

7 i$ c6 I' ~$ G- V7 _+ {$ c( h! }1 J

& ~+ t+ o9 Z* @# [9 L C% j! U     break; : @4 i( _; |! K6 }- x4 y6 j

( |3 D# ?( Q$ b) |% U6 n+ V

8 P/ c$ f, v7 Z/ p% K" y 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。2 b4 C& x; P2 z4 ^- T& K

, W$ A) @/ ~4 U3 q$ V& e4 D

" M4 H5 E1 A' y4 I. F upload对象构造函数如下,include/upload.class.php:25:* k+ O: y8 O2 F8 m' i) Q# A

. q" ^/ ~+ l) c( n) j

4 o4 S. D: e2 s2 ?5 y <?phpclass upload {# b1 P: I( p+ `$ R [9 G

- A6 m6 z7 I- y; i

7 i3 v0 W j; a: E% [5 [0 S     function __construct($_file, $savepath, $savename = '', $fileformat = '') {6 ~1 u. N% h# i+ Q/ _

' G/ i! G5 p0 \3 Y) s' W

( B1 |; V& L0 q) d         global $DT, $_userid; . X/ |9 t- b: K0 w2 v; r/ {

$ }3 u! \- q3 `, h. F1 N* b7 S1 ?

$ R3 q, z7 S+ r+ K; k. D/ M         foreach($_file as $file) {0 \. y( y8 Z: J% Q6 s

* O' [% j r: c* _

& O8 P1 C9 o8 V( o- }$ c% T             $this->file = $file['tmp_name']; . s) u8 K# N' u; O9 ?) O# g

" U) N6 g. q% |' s' e1 t% o

" S( z- ^: i; ]0 Q& ~ P0 T4 V/ {             $this->file_name = $file['name']; 2 C) U% N l( d0 L# D( m0 V5 {

& k) m. W9 _, b6 F% g

$ R1 t2 ~7 {/ X0 i) D             $this->file_size = $file['size'];2 f, `; d% D- e0 [6 O5 L

. i8 g3 y% ^' F: _/ g# I

& F4 c E0 i) R             $this->file_type = $file['type'];' V* X* u8 V4 U

6 L! g$ Z! _% c# ~1 @

8 F+ Y/ N7 B9 ]) K h7 _             $this->file_error = $file['error']; 9 z# H" P5 X7 \

1 @, [$ ` w2 {) j, [: e( i

+ P% u9 n9 @* c$ L' s6 n   ; A s) ~2 M$ D* y

% B$ T, Z0 V; X; O# C

R" U' H0 q: H' b$ E# w         }. b* d+ }' U% a: ^/ E

" m/ Z h9 X- S) M

: ?1 I( p$ r A1 g: b( u         $this->userid = $_userid;7 S G. K- s: D/ J

, s& V! @8 v- E8 [6 m

" m. I* o, n, u0 g         $this->ext = file_ext($this->file_name); " \5 S/ g. u) L& j

+ P9 |) q. b. U, r

6 q8 B+ F f9 Z9 R         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ N2 ~. u0 V* r' P+ ~

# S/ t3 G O3 f5 K8 O/ x, r, K

6 h2 Y. ^% n3 z, o         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 7 A3 ]. k* P+ b0 O- _1 o

) @+ C. b; t; `9 `" E

2 J, f q9 [3 L; S+ ?! C8 d         $this->savepath = $savepath; $ o( {2 w u& ^8 w. h. x3 U

/ l$ P i3 w% e- @ F

- j. ]! h( K6 L' @+ J/ w+ N7 ?" E         $this->savename = $savename; 2 D% n- ]2 |( ^0 g. r4 Z3 u) Q- E* o

6 l) y. N6 L- n* R$ ?' z4 Q0 q1 |+ M

& X' e4 ~! t2 n7 B7 Z     }}% ^7 o0 L* w0 T) J

7 j0 ?) F1 z8 K5 s; m" G

: o( X, p P! C8 f, I 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 - B+ Z$ r1 z7 L/ F

, Z4 @' R* Y' y7 O7 j7 P6 y% a" ~

8 s: L9 x5 b+ l K: T 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 4 Y- t% C) w) A2 f" U) H2 o

8 c1 ]; v$ N4 H4 g& K, w0 p

* E# S- @: V8 n $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 ! v4 }$ j6 n$ l8 Q1 { e: y0 d

7 T' T1 X# L" P" T# P6 R

( E% T" b; ?' S* u* x. K' M* k 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: # Q- ?. r3 F. u' n# f

* ?0 g, f0 e# A/ i

# S: f. }) `( J   4 R3 g; ?# n" z) D

+ @3 d) `/ m. y7 X- P3 y: D

7 a& y/ t& U9 C6 Y# j 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:' [/ _1 B! B( W7 h% `+ |2 D

+ ?. y1 g! [; y7 `" a2 _, i6 P

$ r3 W$ X* ^" h/ Q; r& i <?phpclass upload {4 p. I: Q2 R: ^+ v: u

7 f/ y% \$ a* Q' ~3 v

: K; x8 C Z P; W, i7 q7 ~, l     function save() {7 V: X. f h# r/ g1 t

' o! w# ~' u+ @4 O( d# M

+ O' Z1 W% D' Q/ M, o         include load('include.lang');4 @. u) E1 i- V5 i) e b

1 m% t7 J+ e- T c/ r

+ \' ?7 {& }! P" j2 r         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); . w( R3 j' y+ M9 F" L/ V) \

* T( T+ c4 j0 e/ l' T' [4 o) W# H% S, g

' {$ E0 t' H/ u4 N8 D6 J0 ^  " |3 |8 l* L; |# c

! L9 e+ [3 P; x* F. z1 ^8 k" i+ L

5 q) @8 e( p3 E3 b& U1 R         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 7 Z8 a) G _" L/ j

5 [' `6 b; ?- |, v& U/ ^ u1 c8 m( J

! m$ V8 t" O1 g0 G& V  ! m1 R& p2 E# b7 Q0 x" l

% v% f# k s o

8 \& F8 @1 D8 X8 x         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); , \" v* P7 Z$ {

! C i9 e9 @" k2 ^0 d

" l7 Y8 |' R c8 [ u& A  + o& I9 n6 @- p

( {& a* o# y$ W2 D& ]. l

& }- E2 R8 _, J7 p         $this->set_savepath($this->savepath); 9 F3 v$ D! B7 ~, c

4 k! A) n R' H: _+ _

3 {; G/ } c# q+ M( \" e         $this->set_savename($this->savename);3 L- S' S! S8 ^0 S G7 n+ u

0 D. {' r+ |2 M7 z2 {: O

+ x- b* p4 l* {) ]  0 ^* n8 w/ @1 f

1 T6 G5 h4 L" [6 H/ H/ Q+ U/ u

2 |6 @( ^# M2 I( s         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);) X0 ^$ I% x, e$ y' O3 d- Z/ @. X

% {% H/ Z( P! L& e$ a4 X3 h8 ~

- v( r$ z2 u& a8 f2 u         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);6 c% O0 D# q4 X9 ~$ k

e1 [- c/ x4 ~" x5 i

6 \* D2 E f. b9 Q' L; j/ L         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 3 s, [0 }6 g7 A# B! K0 R( R, b

9 l% X0 P+ j6 _" \# d2 _

! k1 o9 o: y9 N, C- U# Q  2 z% |6 c8 G- A) u: C. D M( }' n

' y3 ~ K2 m( k7 A- p" e1 |

% ]& N- ]$ {, V! H" [) r* D) t         $this->image = $this->is_image();0 t% x$ a7 a! ~$ ?0 E9 Z G

0 C5 A2 c% A9 ~) V: x& B% `8 ]

3 R7 a5 o# k$ N' Z" p' c( Y( Q         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); # b( W+ d1 j8 h

% M0 E& u# |: Q; ~0 T

' `* c2 D: G/ l* c/ {. @         return true;# N7 B8 Y6 e, s9 Y( |# o, D1 s7 g/ O

$ U. T; p# [$ Z8 f e: m7 g6 R- L, s

3 p X8 M) l% v& N     }} * Z8 F @. y9 s0 ]/ ^

' n2 ^$ E8 t- m( a

& K- m' b7 B/ M. v( B3 k8 m S 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 3 A4 `9 ` k1 W) l2 t

* ^- x! Y* d4 V9 _) q. _

0 r; w; [4 `) V' P <?php* @$ v9 @* V# q. \1 P

, R4 w& S( a" u y# R; @! o

; u; k5 U( B) ?$ ~     function is_allow() { - Y& q1 J2 U0 Y$ P1 P) T4 i, t* c. o

# T6 S% J Z- \ Q# j8 J1 Y5 N) c: E

, f0 ~- x. T- c+ v$ o         if(!$this->fileformat) return false;; ^+ m9 G7 \+ [1 m. F9 g6 y6 F

5 ?7 G8 G9 u. d; M' J, k" g

+ n1 K: `2 k5 C7 u, k$ Q( P         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;# {: o+ A+ F: z

5 Z4 h% L( t' a" ]

u; G; x/ W2 i( e. K2 i' Y         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; , M+ J2 `6 _9 c! Y9 I8 _9 l- I

5 `8 c; t" Q) ~5 M. k0 s- i

- {; ~; K3 ]! [/ U C+ a         return true; ( J0 { @' M2 I0 |) T/ o

: [& a3 _& G9 |( O2 L

2 Z. k" N- K$ T4 N! r. X2 A4 J     } & D9 x" n& d* E1 y% d

" b. g, Z" n( l% F( B8 K

! o- S* L( l8 J! a 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 r0 N( V$ J/ V" |5 x8 P

$ i. `$ e- W8 M1 v* |

6 Y* L6 j+ w F& `" b a7 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文件。 ! b3 N- U r2 H) Z1 r I) J

! c. ^, B2 t& s

' v6 f* b+ A( G$ g 漏洞利用 g7 k$ s7 A+ A

* u/ D6 C- X- z: r0 k4 N7 ?0 u

: O7 ]) F* i, q$ M 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。$ r4 ?) o8 L6 u

2 l8 T, `/ B1 G, D" } P. t& L

8 B$ [6 h+ x, x3 ~4 ?( q$ b   & e. E& ~6 t6 _- @

- {6 q, u4 I% J+ `! ]" K

* A) |- D y7 E* ` Z5 t2 o8 e% O 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid% Q) ~; C( c# h k' E1 i% I8 J0 U

% u% i5 n' z' v U+ n2 z& n7 W" z

3 w/ L# b( K* b5 X. |8 [9 [% w0 \ 不过实际利用上会有一定的限制。 & |" `" n5 S6 L! B- ~8 [- ?

9 Q+ \8 g$ o7 t, N. o

$ ~9 `& Z& t) f* ?3 t 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。% o7 q2 ^. T# i+ n! G0 Q6 h2 W

8 v7 e1 K/ l; v* F5 }1 R

M' S* ^. G7 L+ s  / L! B# F+ @4 K9 [! N: H* O

& x M( g& l! f9 ^. P7 \

/ a. t$ k% s6 Y& v w* u 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: , z" y; o- J- F# K, Q1 a- L

% f7 p4 |& S0 e: _' d

4 `3 A! S8 \8 u9 b* P% 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]);省略...6 [ _& b/ u/ B% [5 Q

7 Z1 d- Y/ ]1 g+ |

8 h7 a* e/ a% H: B$ {4 }9 {5 C% n 因此要利用成功就需要条件竞争了。 q4 O& }+ o* y

6 k8 J" y' W8 A P- B6 y

# p2 I# n! {: |0 W 补丁分析 $ d8 K# P A% L7 P

b9 L, A4 {2 i1 Z# h% ?; e

5 V- O# j. @" N   ( o( M8 D1 o6 m7 u0 W/ V

! X2 I: W5 K# g& o" P

$ l* E+ |( f' d$ ~3 ^! W" i 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:1 ?" ?6 ~& P) b" X0 I3 l# ~

/ i1 W. ?6 B5 \5 d2 Z, z

/ Y1 D/ x. |4 P; s) R; b S" z function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ; _5 j2 {5 v$ h$ \$ ~6 k: K& |

& w3 B, G" y! `: N7 _* b! }

7 `6 x% q2 P6 u, A  6 T: R% W) }" I" w4 p

" r; |$ A8 ? C5 ?: ]* J( c7 ~

( q3 y1 F% s/ n/ `! p* A8 t* G 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。7 K D' e$ b% O3 R9 z- k' F$ H

7 Q. v+ ?, P) ]/ h8 G

1 G2 B' H- r% t W 在is_allow()中增加对$this->savename的二次检查。, V; N: O- f2 q

- |4 C6 @/ u$ M7 b- n

+ R% ?, c+ l; O5 B! Y 最后 3 a5 F6 r4 s* q, y6 _4 o

3 [0 L; O2 d. R3 D8 Q9 L

0 D' z, g# \3 f, l 嘛,祝各位大师傅中秋快乐! 4 a1 |7 F+ o7 e4 R

; @4 X0 I+ C7 D5 ~- R3 a

$ N$ V2 p" i; O$ l! Q/ z1 P   ! l) H2 _( G9 _6 _( ], |

7 k& S) P6 m8 _1 `3 w: d
回复

使用道具 举报

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

本版积分规则

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