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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
" _3 s. @7 Q- p9 I, a

$ n7 M1 D# `0 @5 y: H

/ Q/ `- Q4 m1 O6 I

* U1 I. n \9 L 前言9 x+ V, H& n2 B& k

/ a$ c/ Z2 E, A+ O( m* |: _

i, O$ I9 I# A9 {2 S8 x 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 4 A2 g1 k8 j7 r% r% {7 g8 r

& t Q1 ^1 q& y; E# h' ]

5 n3 r" q% S# i# u8 Q  ; o5 v. B z l- P. u

, }8 d# {* v1 O8 I: @5 B: G

# O' H; v# E1 Q; O 漏洞分析2 g# j' T& M0 @$ a5 A0 W% j% M

" p% b y! G5 L3 D7 k* J

% N$ _7 G& F3 I+ _ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 3 A. {) O1 n3 z8 j" p. D0 @, i! P8 x

7 M. ~4 o- T+ p7 j5 n4 Y

* u" r" ~- h$ {$ r6 Y   g0 }. p0 B! t* S

! B, x& o( L( u) F9 ], b5 n! ^0 N5 \

* u8 z+ B1 q$ q7 p/ q 对应着avatar.inc.php代码如下:. C2 b; p2 }7 t2 F* }% z2 }- J$ V

% u3 b3 q% u$ X; @7 O6 b

0 `8 N' b, ~8 V5 w5 ~+ w <?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) { . s5 f; P5 \2 @- a& P

7 |- f5 d: _: U0 I1 z, q& i

6 x8 G! d, R& O1 H2 @     case 'upload':5 }9 h m1 r. Y

! U5 N2 \4 v- } w: r) f( \' Y/ q

. o& U% L4 ^9 |7 { V         if(!$_FILES['file']['size']) { - _) f# ~, S) V3 Y& a, C& O2 N% l, o

. |5 `/ D% g& `4 I: C) b

* C, [4 D! ^7 j3 c% _" _5 l             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 9 E) {: o. I ~0 K# R

`# g2 u- G' t3 P5 m4 b

4 z: z0 |5 ?7 s: l" t             exit('{"error":1,"message":"Error FILE"}'); 0 l% T0 O' U/ P2 u4 s+ T: Z. _

6 ^* n" F' V: a4 v2 V" ~$ L

2 X9 ?3 W7 j/ `! X( ]         }( e& h9 B$ b5 v* v3 z: @

2 q9 B+ b G$ Q w5 [4 }/ F- A$ O

* G* x* k9 s( M4 [# a         require DT_ROOT.'/include/upload.class.php'; ! o% B; J' R; B1 O) j* R9 N8 M

* X8 p; r( Z: |% J0 Q4 u3 L

8 ?1 V9 ~# e/ Y& C3 F  2 U2 M. F7 |% D

1 }) S( j4 Y( D1 O- l. ~' Q7 `0 t& \

$ S" ?5 \& w* K2 f         $ext = file_ext($_FILES['file']['name']); / W* M4 J u& b T. X9 Z

" D8 ~" }8 ^7 P

4 Q$ y: K+ d" P) Y% P& q         $name = 'avatar'.$_userid.'.'.$ext;. b" N2 \$ _5 Z, o9 B7 Y2 Z2 d

$ M4 C) ?& K$ l% W

6 B! G5 W! n6 I* L& a% J" z         $file = DT_ROOT.'/file/temp/'.$name;- ]( m# Y g' U0 y. I/ A/ S2 ?0 u& ~

8 D' E) t. r; i- E% Y

+ f* X2 `( q, m( c- V# B   9 i. k0 e1 Z5 \

( Q5 {0 u2 w0 T+ x

- S5 O$ Y, x9 ]$ O q         if(is_file($file)) file_del($file); 8 x4 o& g* C: I z

3 C4 @# t* t+ [/ \0 E$ `0 W

; z6 J, Y+ @/ P \4 x' d+ Q: m         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 9 b9 `$ q# P2 x+ Q; c

9 K' p5 r1 i( K2 s* J

& b6 q+ o# H- b. S; T+ J  , f# L8 A7 e5 H4 E2 ~4 T: ~* n

0 v$ d9 P7 b$ B2 F; o

: r7 ^6 F' C8 r* X* b4 Q( Q9 N         $upload->adduserid = false;0 d! S; U2 ]& W" Z- P {/ t

$ Q S0 }% G8 H' {

4 a/ R: P! p6 T/ H  & f; `- g; g ]

- |: X6 Y0 P5 N2 w7 s! J

! |5 d/ h. U/ _. {0 }- l! K         if($upload->save()) {! ?# O$ |7 n, z. `3 P9 m1 M" G

. M* y2 L+ Y0 S9 z6 P

1 k! H. g' a) @* w             ... ) j) @) x) t0 L! Y

4 q9 \& l8 c8 U* y

. _/ V8 r: `5 B9 s         } else { J9 T( J- Q% T- c

, z, t6 V0 R E& Y) O

1 x4 ^: _" }6 z& s3 R, z             .... n# G0 c' }( e9 ~7 T/ y

3 a) s3 h9 L: }

: }/ N$ K. r( L/ u         }* ?0 l, T/ `" L6 ~0 ]( D- i+ K

/ [* V) y1 t& r

9 _" S% T6 r6 o1 F) Q$ @     break;% G6 F. }" r% D2 F% p; w9 U

- ~4 a; B9 t& d) d/ A8 d

' o9 i1 d7 @9 m0 n# d/ v! K 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 7 I: L5 ]# c) N* ^$ B0 O" o$ n5 ]

9 y+ d* _, e0 b1 Q! f, P9 O7 k+ e

9 t7 P$ ?4 N' ]# p8 S$ o5 S0 H upload对象构造函数如下,include/upload.class.php:25: ' j% r+ a3 J1 x1 J# F) _6 @

, j8 {' V2 c( ^' ?# d8 _/ g

/ j+ H; C+ k: U/ ]# h. h <?phpclass upload { 5 J! D& m' j( E7 H/ v k

8 \; L# |) G+ \3 c( Q8 S

- L. @6 l1 Z5 m2 W; x* X- F6 P, k. f' Y     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ! I7 c# v. h5 R: J! U, g

9 o! W; D' U i' w+ X: x' i: x

8 @3 {6 D% n. E8 d2 _8 w' U         global $DT, $_userid;3 J: k% U3 r% F8 O6 _2 d# L

( b, t# }5 N( O* C* }4 c

6 t0 V, Y) _% O' G3 U8 W; H- ^0 w) a         foreach($_file as $file) { 2 m' s s P% Q3 I0 [7 N1 A7 b6 @" x

- u: H2 _- D I+ e( K; p, d

! o" A- v# n1 X8 ?5 E- R& r             $this->file = $file['tmp_name']; % i3 R0 d# F2 W5 O M

$ V: \2 h4 |7 Q$ g: g" e; g

& R+ C4 r4 l& M+ W3 N% o0 K# X+ t             $this->file_name = $file['name'];; b! m: W( D. g% B' w, M

- g$ f M3 q( ]; Y w! v. X5 A

' Z# m( G) n* D. ?             $this->file_size = $file['size'];# Q0 P, J3 `* M8 [7 m* ^

) F0 G9 T" @6 j% P. Q1 d

4 p Y1 {# M9 V             $this->file_type = $file['type']; " ^9 e; h& ^7 X- z$ m9 N `

; u4 l) [; Z; l

1 ?- R1 N1 k$ X5 E2 `8 w4 ?+ n             $this->file_error = $file['error'];; Y! k( Y& p* G

' c! c3 ?! M- h. ~# C

# c5 z- _6 x T! v   * F3 |8 y) R9 e9 n. N

5 t9 M5 R. m! F5 _% J

5 h7 g* _0 ^0 z% A         } 6 U6 S, c( K& q2 ^$ _. ~- v

$ O4 x& Q- n: U& O V; ^

5 S2 B6 r. d( g2 ~( F         $this->userid = $_userid; 4 _2 S5 b. o- f5 D2 D4 ?4 v

! f: X0 J; V* W

5 \: N4 p8 w3 {( K         $this->ext = file_ext($this->file_name); " l+ i" v6 Y' K3 P! D3 ^. B

: G' `8 ~: s6 I& I

# H' |% J( O6 e* i& ]         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];* o1 \! [* H# o+ S

1 X4 E: i6 Z8 X+ ~' j

5 B- f9 E) h' E$ |9 r# X- w         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;5 q; c; ?/ T3 p) t2 v. D* n& i% `

( K" D; N5 ^) k' ?; S- h- @5 q

5 k% a3 Y; J6 _% m, F! U         $this->savepath = $savepath; + U) F! V% `/ @& x* t) x

0 U" M9 R9 O: [4 x& k8 s; B

5 e; C+ C* M$ b& l G         $this->savename = $savename; & d3 [' M: J; Q( m3 C. G) r

- N9 q) [4 T [! L

0 g8 Y, b, h3 x1 C% i     }}* C9 K% o0 B8 g- }: w5 z- @

4 a* b8 C" f5 D& `

! U4 v0 v( b5 I/ \, ` 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 5 E T/ o% \" L

/ w N1 R7 Y2 v& J

) J% w( k) i/ c" W' Z6 D 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ! `/ h4 ~, c5 Z1 ~; R

3 X5 o% k( ^0 ^

3 D4 O7 m1 D5 f $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 " {1 U# S; V' v x6 X8 y- m. x

" U, u% N9 `/ j8 R) D

5 B4 ^, A8 f% R9 F, N/ Y6 F2 h 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:+ i" J+ {: J+ [) E+ T

5 n0 f; V/ G7 k

( h+ H1 ` y: P8 f: ]   + P2 ^7 F5 ^/ k9 B3 @7 c3 z* i

; H% M9 T3 m- h8 _4 s( P

' R* [ Z1 z7 w6 m1 x' c( Z 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 8 h' C# j+ u! ]" y8 \6 e' x

" H/ o7 f; w7 F3 b

4 n( v4 n2 k( i* H7 `: S+ ~ <?phpclass upload { ! i8 @% Z9 e0 Y

7 P6 _$ n. {2 G8 P3 S* J9 ? z

) y2 K" b5 v- c$ E9 e1 v     function save() { 3 `' g+ X' p' P% A! K

) u' y. U! L5 e

: {/ z* Y( R3 h7 K; S         include load('include.lang');" J; ~% o5 `) P0 A

7 [: K) ~" _- ~8 p

( }3 l, X6 {6 S6 \- v( s+ G$ |         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); - P7 w w3 |% M x; u% f G

q6 `- p/ {% z, b8 j4 B+ S

0 Y6 k( e5 _6 ]4 k6 x5 z3 J- m8 c   ; o# t$ m0 L0 Z, D2 o

5 c4 [4 W1 x' C' m9 r0 i8 c

7 u! K6 W' o w         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); ! D+ D( t/ u: `7 F( M$ x

+ Z3 H8 ]0 v3 H1 i

1 Q5 G* N1 }* s* H# a% r# ` y  ; i7 J$ G7 v7 d1 Y8 l1 b

( O4 @& o/ r8 A

* w5 Q0 J4 f4 h, f: A         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ |4 p- Z$ z9 ? x( m/ i

4 Z+ v# ^8 L# g6 F* y% }

" S. R% W2 N" H( j% }, A   ; m6 f0 I$ p& }& b. T$ Y' y

) L- u( a; G7 `& P; u, g: Z6 V

6 E$ B% l+ D l5 n         $this->set_savepath($this->savepath);, _+ w$ ?! G; q- ~9 T% y1 l! o

' Y" Y* [; ~9 s. f m$ i' K7 _) L

0 u0 W( r! _" G) i: D, _& ]+ I: s         $this->set_savename($this->savename); # \ f" e$ O! a# Z$ l' P- C

; g2 Y, Q/ I! k

+ l7 s' M+ H, x* A% w+ f' o( F   ; a7 m: m3 r# ?+ q" \+ I d7 ?6 H. O

I" z+ g/ Z' d7 }% ~

5 R1 H2 k/ K, M+ W         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);7 n. {0 L% p6 g

' S! d( V6 l" f; `, C$ e

( |1 G6 a$ K" n         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 7 r5 p& w5 o& u% ?+ M

; H1 w- ]5 T' j* x! a8 i

- m0 j: j& U7 `" E         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);) }3 [8 S( }! \) o) s5 Z

$ B0 Z" E% O+ H/ C% v( n0 I, S" G

6 E' h: M6 E0 `$ k7 r% S  % m6 J) H; g- q: f

J: ?) `9 z3 e+ m7 x

/ @" g* E8 Q z2 O# K         $this->image = $this->is_image(); @; k% j+ n6 O% ^, I9 B

j, s: V- w5 n" h5 b' i

2 p! Y! }7 m; C         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 3 C0 l% ?6 A, K: x

: P9 k# x% V) q& H. c

. [; S. J& |5 i" k g' @, @9 o# g         return true; 7 P) E) J* a/ F- J0 {

# X9 A! I. k3 @2 F

8 Q& k0 ]1 H6 I0 {) e0 j* q( B     }}$ {0 D$ T R8 s# F

: Y: F t" S( }& X9 ^+ h

3 }9 ?: k' O" e9 z 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:, b Q: `4 I0 d. ~* n- q

& {' F1 m9 ]! ]0 ^: X0 u

1 L% h! [6 q! W1 ^ <?php ) n9 a% l+ @+ J" z+ }* o3 x. Z

$ c4 T& u( e. k6 k

. m+ F7 z& n# ^3 ?/ D) R5 l! c     function is_allow() {1 t% b+ N0 v3 H/ L. ~; v+ G

! c$ \& R2 Q- \$ u4 h

3 w/ m' J* n& b& M1 P0 r7 h         if(!$this->fileformat) return false; " N9 R5 T- e" |& e/ D

C8 E1 X- h1 s$ W# `. i

+ q; G* K3 j2 d4 ]- l' j         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; & ~, S* m/ S! Z+ h$ ]1 k( u

* S! a: Z: G6 W

* Y3 c! [- r' a. ^- U' o7 C         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;1 r* U6 m' X! U

6 W" q6 N2 |9 v' ^

# H( I. r s5 m         return true; ! H4 j1 h6 C4 Q. m& A

: A( m. h6 E" V4 k3 M

4 G1 s! u% {1 L4 R9 R! K' L% i     }$ M- `1 P1 }* `6 \

1 Y+ Y* ~: m/ C" j- I

0 M: v. i1 x* c1 ]( i 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。4 C- P, y0 f! ~1 A& }2 r3 A

" l$ V6 ~5 ^6 J

, v! ?* d1 m: A8 E- T 接着会进行真正的保存。通过$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文件。+ k6 M: I u- a9 m; H# m) Y

) } I2 l+ V, s- g; x4 E

: j4 O5 a# U6 O& m0 @7 r2 K1 }2 V 漏洞利用# w; h2 C- L% o# B* b% [

! M! C' @1 C6 C+ q i9 c7 w

- |1 [) o' K3 T- C v1 v) D) z& H. j 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 9 ]7 C( B% [* F! d, q8 Y

2 o3 ~5 r' l) u6 r7 Z. f

0 N: k" b+ R# b   6 q+ @6 i$ U) ]8 B

% S' R. l/ X. M) P( {) ~( i

: ^- x2 F# X: [' c3 M- f, n y 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid # w) b; f( a; e# {

/ N! U2 D. q$ `

% W4 M' \" m& H' [0 [% D 不过实际利用上会有一定的限制。; Z/ u) }6 n4 T T4 V8 d) e

' `+ c1 j( R i+ o* C

; d$ z3 c& y4 s9 ?+ v- \* \ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 8 q& [, r( T! p& i8 @' J

" Y5 d3 _% o/ _

5 j8 W: u! `+ s& U# z. u   ! q1 Q I& ]- v3 i" B

5 I3 f+ {+ u/ E2 S4 j. j+ E

7 Z+ x, x2 L1 M, S( |* g- N, z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:9 w$ k p+ Y1 J! t

! A0 H% l8 B' y

" I4 I. x! X3 O% B* O$ ` 省略...$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]);省略...: ~ I8 I' G1 ^. Z) j

# D0 o! j& R% {$ `8 W) D, @0 Q) e

7 d: ?; ]6 Z8 J' ` 因此要利用成功就需要条件竞争了。 , H; C2 V, V! B6 c- ?& U

: x- J5 } c$ F+ I9 C2 l

7 B; W4 |4 t U9 f6 i 补丁分析 Z& U0 ^6 M) l% u( q' I6 Y( h) F

2 m9 f5 c3 L/ Z. z$ a4 z! V

. E" J- H& T9 S$ Q$ W0 w' _   - @ g; U" _- a9 _) z( r- K

% O9 M" G; E- O2 Z e% o

6 c2 E* c' K! d0 F 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 8 `9 n3 x, o% @2 D

" Z) b, w# r# W X

. n$ M5 n* c5 h- I5 t% x function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}& u& I# i4 ]1 p3 E6 S. j

# U* T% K; f4 U

& A8 a6 Z2 e/ s! \+ I9 {   r v# Q ^" o& H, o! _

4 p+ f% k& r2 r

. o: y" u9 a; R3 A3 } 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 1 y, F" ]: c9 w5 m

; n" F! D: ?- D3 s4 m8 n

/ Z- G# X+ c" Y. b: X 在is_allow()中增加对$this->savename的二次检查。, |0 m' t: _; Z. G

- C6 v `* ]- G9 o5 e

6 H# k" l0 ]; ]! k 最后 * [9 I" f1 q) \; b, ?4 `- k

. [: v, }8 y- D: L6 Z, }) t

I) w2 q5 k9 H; A 嘛,祝各位大师傅中秋快乐!: W% h: W5 \! [% c

, P* G5 s% c, s. m7 D

) k. Z& a. L' s0 O3 `8 h   r* Z& _! l0 ?' {( b

9 v8 A9 ^" _8 q, ]7 W* I7 C
回复

使用道具 举报

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

本版积分规则

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