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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
7 O3 k2 Z1 o j5 Y/ D6 W

3 \1 q- a6 O* a) \! w

* t. b! [# q+ T$ d$ x0 U+ a6 k

) ]3 z/ a9 m3 n 前言 T. P* W7 S, }9 k+ q

/ a1 d9 c, V# V; D" `# p

6 a0 G6 q' y* B: P$ m% w2 D 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ] K. t4 J; P0 k% |% q, }/ k; _

! L/ _+ h2 t/ p3 L

$ K1 w. J9 b+ c3 r; X; ?  3 H* L( l q5 |! k. e1 |

+ k" h/ M) `" P5 i

, R, G, q* N2 O- e0 @* | 漏洞分析 " `. s& ^2 k1 G# M

' x: P8 c. x& d+ c! G# U2 @, T

: f- k7 q5 e5 n0 m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:! R1 R; j- }+ B# v X

0 a' t$ ]: Y/ x! K" b

$ [4 X6 O( E- ^9 s. g  " ^: z$ B$ m9 I, _ p

! \2 g1 S; \. k2 U& Z% [ R

. z6 [$ W, E) g+ \ 对应着avatar.inc.php代码如下:- k0 D8 I% k* R" }; O) V- d

3 Y6 d1 r( Y5 v8 B u4 M

3 e1 p1 r5 y U4 p <?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) { 6 r0 w: M# j! H

K" o9 N: \: j2 W& X6 g9 p d+ {1 Q

3 X: y8 V2 w5 l6 E     case 'upload': ! |& |) K5 U' g$ m, P& u3 ~; p

/ b1 L' r8 ^: l7 R0 D

) G+ {5 O" o) w6 M. T, U# K+ a         if(!$_FILES['file']['size']) { 9 E1 X; e) ~8 e% m* N

7 L: O- L T. l# n P% F

8 x L9 W* x+ ]. k$ I4 q) j             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);3 s, [6 D2 U9 u7 t+ g

2 j; N: L) ?# L- F2 A4 t2 [9 S# e

{; n n. W4 m- J1 b6 e) [             exit('{"error":1,"message":"Error FILE"}');3 Q( c# t9 C2 z, T7 f$ @- h+ W

, G. M. n6 M/ x! K! V$ u7 t7 n

8 b4 } q. C, i4 k         }- L* e$ Z2 v# w$ r- Y7 H

& R1 {2 x3 |+ n* e6 Q( W) w; a

/ t# m- V& _. x, K6 D         require DT_ROOT.'/include/upload.class.php';$ z* `0 C! G# a

e1 o& G4 q4 \

7 w$ r! U3 h1 ~- f2 r9 K/ @   , e: c9 `9 p$ i- [1 c* J+ G

1 D! e# ?" j" ^

1 z$ b/ ]8 N8 Q- P* X         $ext = file_ext($_FILES['file']['name']);6 C' j r( r# C6 j: M

8 S- N: j0 t, t" @) k+ u9 y

. ^2 s' Z4 s, [5 g/ A v1 Q2 [         $name = 'avatar'.$_userid.'.'.$ext; ; ]: C, V! I9 R. B/ m. k. X

w9 y, a6 {& m, z' n! U

" c1 ~/ Y& O/ A6 D G0 b4 q" d: I         $file = DT_ROOT.'/file/temp/'.$name;0 @/ W2 t5 K _9 g& k6 t

3 Z- e1 K( H" [

6 J1 k8 Y: }7 u. Y$ a8 G+ }, s  $ k. N- V$ c6 X6 l

1 S8 f4 d3 ^3 c z6 Y, \8 a S

g& Z6 w4 r% W! M) A9 J         if(is_file($file)) file_del($file); 8 J0 b$ E, b% Q* C

7 |: m" q$ P4 K

p, x- ^! V7 I7 s- {         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');9 [! d" Q/ h" f/ O g- x/ c

5 n% v; k' N& _' Q& N' d; s

3 h9 i) t' v% d  % o/ T4 w% e4 }

$ Q" t- q& H9 c) U* j# O$ C

) y- c1 Q9 w5 i: u         $upload->adduserid = false;9 I4 p$ ^3 G7 h7 a

5 P. U: |+ k- x7 T2 U' W# X

' g8 D4 Y4 Z6 D4 ~, ^   - ~' `1 x# ], ~6 x- R x

4 D# Z% W% u" S( m' X

3 X# m6 c1 B# N( `         if($upload->save()) {; T% i" U G/ [! F/ V4 w

1 r7 W0 ?- e1 s/ Z

7 U9 F5 y6 b2 t4 L+ C9 v* q5 v             ... ! [0 W7 a. p5 k4 Z$ e

* n* P& l8 P: M1 V& C2 { v

6 ~1 k( Q! W- W! v7 X9 f( m9 a         } else {( G# k2 {- D. B2 D1 a( C! {8 s" H

' X. z% Z( e2 p# n2 E. H

& P: q, h3 m: y8 H+ _6 `! {             ...) w7 N, q) V8 i* v$ `8 e* U

& y9 y% i4 X" P& a) n

- ~4 V: R2 m+ z* ^0 t         } * x$ A( S' a8 h: [0 t. U

3 S( f% m5 o! A2 O* l. q

& |- X5 T$ z/ ~7 h! e! p' T6 P     break; + t: x) j, S* l9 V

% I( I- l; c! ^8 V

* l" K% N9 h% Q( l& P 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。+ _# D( ~" I1 o$ i5 h5 E# I' ^

! x5 _" v: f" P/ W: ~. Z! y

9 k0 t& X, z3 [- i9 L! ?9 _: D upload对象构造函数如下,include/upload.class.php:25: 6 B/ S6 A2 ~0 K) q' [) b( Y

( z$ p0 X( _" S3 i+ h

) g0 [" v5 F7 {' z+ \$ U <?phpclass upload { ' b3 s* ^ G$ M8 Y7 Y# ]0 C4 C

/ p7 n, V4 j. |7 J; \

6 c) X) `, X9 v( S, }( F     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 5 P7 P# g4 U1 ]! G

# Z0 c$ Q7 v {7 o3 A* p- D) l% F. U

2 E$ C- T' r: r) @; | @4 z         global $DT, $_userid; 0 }. H+ j0 U r+ ^

: A# Z8 W* P9 T! a H: I6 F, R% q

* k. o% R1 b6 O2 R         foreach($_file as $file) { 3 r3 V) k4 J1 n" e$ [+ ]' C

& O) j' f+ O* }& R2 v

, o* @- r/ i( H5 g& u             $this->file = $file['tmp_name']; ! l" N* _* |+ Y$ W- b

; b' A4 d: D/ { a

- k W' K ?& P             $this->file_name = $file['name']; 9 X" F3 k$ X, z" [3 V

7 N$ v6 S3 j& f; r* O, |# _3 \+ f

2 E) J4 G' z$ d6 z# D             $this->file_size = $file['size'];# Z! I' O% f+ H4 `: Y! K \, j+ y9 U- `& t

a$ v( d/ |- E' d7 x; b/ n

1 m# ^6 s8 Z S% V; |, ^1 ?; n             $this->file_type = $file['type']; 8 X; t3 s! ?, }( ]9 ^% O

0 T! M- g, _% L! h; ]

5 c# S9 `; `/ I' H4 t             $this->file_error = $file['error'];6 { Y: v3 X. ^% d# B: |) P

: h3 I9 u1 a* l3 Q+ y: ?( O$ p, Q+ G

+ x5 G" G- ^! a1 g   , \+ } k! D" B0 ?

# y1 V1 q/ t7 _6 l* j

9 H$ A+ k7 w6 R         }! o4 g1 F; r/ `0 v, z

2 S( a( q; O. C5 q3 }" T

5 R- a2 _+ u* |) ?" \         $this->userid = $_userid;5 U0 [! Q0 l5 T9 C0 B! t: c7 [

" t/ A$ _. x# k# i ]/ i

9 {# b" y: [/ I1 i6 W* I6 I         $this->ext = file_ext($this->file_name);1 ~ W# J) z- U4 b2 Z& H$ n

9 A5 _( @5 G. y4 A5 x y/ l

( g1 Y" q8 p$ [. `. N2 ~3 Y- w         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];. E9 ]* K2 w* t& o- y

* c X; u6 w( V6 |/ y5 H: q1 O% v

5 B7 K# P# h2 W. q9 h         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 1 [/ [% L! E3 l9 P+ [: L @6 y

' G* G, n8 S- l

5 {4 l: V6 S+ }- I( e         $this->savepath = $savepath;( Z5 l& L; H5 [2 y' i, g

" e) s* i: H4 s* f1 b; x

A% M" ~' ?0 j, ]         $this->savename = $savename;4 W. @. V! h J2 ?, J' q g

0 }4 u! G. v# F, ^

2 s* j: M% {2 X" |9 u' A     }}2 w2 t) q& t% `" [( d- A

1 D0 }. ]5 s' H

. E/ W6 o4 I8 X4 j7 F* c9 R$ H 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 * e5 W1 A/ j. U1 A0 R! Q

. k6 E+ ]0 ~) a' d

2 f5 r- k: z9 o: p3 B 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 6 X# X3 W$ ^ q y" \$ m. i

6 h7 u& p4 I1 i7 T- o

+ J: K3 R7 f% M { $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 $ }- X1 ^8 A4 P/ r

1 L# f% y% O$ f6 \2 K

9 |' l7 _) {8 Z2 z& } 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:7 t; {: Q! N/ \& Q7 }8 c

- [' R: y* f! @, G

9 p; ]; ?& N# z! ~   6 z) b' V5 m4 g R

5 q e1 n- U! J/ C4 N' X

9 s, W% b3 ]' r1 u0 Y5 J 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 7 ~0 l$ ?! t0 q) u* d$ e- _

4 b/ i9 a$ h2 t+ }6 E: b, f

9 E6 E# z& t3 r: q <?phpclass upload { % |" O7 R5 ] ^0 t a. R& N m, ?

: @8 N/ O2 E5 G3 j5 Y+ e

6 j% }# R- C* R& I$ a5 Z# d0 S) a     function save() { $ J7 ?) a+ E3 T9 y" U

8 O: r6 ]& Z/ X( {% I U

# T1 ^) e4 ]) \0 {4 V$ L7 l! {         include load('include.lang');0 y, X3 s6 D2 @ k. m" x; p

6 e; h0 @- B& Q+ x4 D0 y

' ~4 u" w$ u& h7 c K         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); - P, s6 ]% C- Q$ o6 j

, R" n2 P C- N) Q. P) R2 d

J6 _ B! c1 @* k% m; y4 Z   ) w9 x* C, ], u% F. V: I

1 K" _$ x$ S* N! Q* n

; v/ v2 W/ t* t3 `9 N; u         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');5 }4 e5 a4 T% D' N7 K6 F% B

0 @5 Q" ]* c4 n) @ b

* S2 t! R9 p4 X  ( q2 x& ^, Z4 L( s* Z" x

/ C1 q' D! s7 u5 v: |* @

# G0 K0 r1 L2 i0 J: A: N5 n, |         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);! g* S; K- `! h& e8 Z. @

% |0 Y. |. ~0 \+ O) U9 Y

0 P& e( h5 M8 W( Z. s) o  * s2 w7 J2 _. A: e9 A; t* D! s

5 w- u# n2 z1 L3 w4 A: A: T" F9 G

' U' h- U1 a2 w' n( \2 Q" b/ O& X         $this->set_savepath($this->savepath);% Q$ e6 q4 `+ D4 p" N9 Z+ Z

) b1 B# C# s% I/ ~6 W

; s0 N/ O) \. d4 i( ? \) ]         $this->set_savename($this->savename); 5 i: b" l8 t4 e

2 B( I; R5 x! k0 ^( r: e. X! z

' O1 H* F& m2 ]- }- E! p   # m" ]4 L& s7 ]* ]) g

1 a: I Q; |$ ]- {# ^

* y" ]) x1 v' n$ H         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 9 A* i! b& ~, _9 R3 t

& c* v6 A5 F, M( k1 Z4 s

1 W1 L0 G: r) s7 `$ B. b         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);5 a c8 q7 C7 G1 C. ]4 H

4 U0 |9 h/ a! {% n3 k; ?

2 n9 T! v" M) f7 L         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); $ Y; L' C% T+ u1 P9 U. J. U w

5 M( ?' c0 N7 A) o

1 H: L. m+ F0 C7 A1 L   0 X/ P+ d5 V0 T# N

# g# j* ^0 X/ R$ g

: u, s- h/ T' Z) k6 c/ B. d         $this->image = $this->is_image(); 9 c( t6 _* M# Y& ?+ W' T* V: Z M7 c

& E2 i0 {8 l1 x& u. p

( D H* _# |+ A) y. h         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); . O8 `0 ~! @3 w5 M+ t- F* s

7 d, ]1 B; r. \1 w* r' \% {

, Z0 b9 T! l" x, F% B         return true;: f( r6 W b; u g2 L# [- S

' n! ?2 M6 W8 W" Z/ M

# | p8 R, j5 K     }} ! u& g" x" O0 Z1 F+ p

' a* z+ l. X J0 U5 {

& j+ h8 Y; m* {! t- F6 ] 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 4 G" I6 y$ f, n4 _2 O% i1 b

$ l$ n+ G- }+ v2 l( g9 U

* v7 J% v3 {* E <?php: W, ?! Y$ }: b9 j! L% w

X; {, Z- x v: V% E5 G2 K @

+ d( o, o r9 W$ l) L3 M% B, P     function is_allow() { D. {- G$ `- S, m7 ~

7 r: F; |) ]% S* B/ _

; \0 D" H i0 f- }4 c( k         if(!$this->fileformat) return false; * t; K/ t" i# y4 O; Z) W

@% o& L6 \( S \, O" u% d+ t! ?

( D6 T5 ]; K/ ^2 K* v* g( S3 T4 d         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 }0 Z! p6 W; x

1 N! O" I5 w$ K

S$ E3 K# I3 L         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; ' R8 L6 o* S$ e

4 [9 l w- A7 ?7 w) O

) a7 h2 G9 K. t3 C: j8 w/ O/ {         return true; Y9 I6 Y N- |0 A. ^

- o4 C' Q% x6 d1 {- d

; K& ?" u; [& z/ [# A     }/ ~5 H, o1 T) X0 D1 }& U

! R1 S& Q5 h5 c# P* R

* d9 c. K8 S3 k 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 : f* y* l3 Z% `& N$ U: k6 q" I4 d

+ w; M" W, Y! p% U1 D5 R( m( d% Z

: A: Y( B4 ?9 S a% U& m ~! P 接着会进行真正的保存。通过$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文件。' C4 i* k2 v8 ^/ P/ h

1 N& K2 D( f! u) v

9 j1 H$ L% o+ c0 Y# T 漏洞利用2 n% f: l4 {7 i0 i

' D/ Q" P8 _. ^0 n7 H: `0 Q

; d) Z/ X" f& M 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 . O/ D% U% ?3 S/ o! K4 B- @4 H

- ^. ]6 j3 _1 l H- D2 M

& e+ {$ V9 s8 ~& q( J   6 r/ |" P% `8 p' A( h6 C

4 `& v7 J/ K, v& l4 _' |

% s! y: g8 @. x: b5 ^ 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid9 S8 m- A( Y# v8 `& l& i

) k3 Q" E( n( Z9 u3 l: e

! n- G4 x- d2 m- G 不过实际利用上会有一定的限制。 - S c Q a& u' e1 c9 m9 f" ^

1 V% [5 V2 j% n9 _: S# _2 ^

( B+ {7 e& \9 U 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 ( U, Q% t' I; }' H" w R

0 h1 q; Y$ R) A/ {( J2 q

% X- z0 i: ]9 U& n   $ A O* v; t5 B) [ Y! {

1 e' [- K2 Q/ J5 E3 h" R

$ O$ W! T# m! |/ Z6 G# W 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:% w1 L' p8 C1 X; ~: A# w6 @6 u$ Q

1 k/ i3 D5 ]/ t: j3 X2 u+ F- e- G

! U0 l# ~- R- L5 D 省略...$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]);省略...% v# g* M) M+ A, V2 Q! k( X

3 n& n) D3 s3 |4 L, m0 F$ X* }

" N5 J; V$ Y6 H6 M 因此要利用成功就需要条件竞争了。 . C4 K8 H$ T; g/ ~6 H% E j- I8 c& o

& B4 m, Z: V9 h, t# p

" d6 B% z n1 T 补丁分析 8 [1 V3 ~- Q: n5 q" M" B5 r; x

/ @: n' b" S5 E- I& i$ {

1 Q" Z# Q6 k& @/ K+ k9 L! a1 l5 u   0 E6 K2 n$ i8 w3 P+ G! V

' A# ^: _0 o4 f# c" G$ o* g

! u" u w3 |3 w N& x 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:$ r' @9 _; r# u, D

* o% J4 r8 v" [. b4 |/ C$ W. e

8 |/ `5 f$ X& t |# w) K8 f* ~ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}0 z- S6 a+ \3 v7 c7 {# ?. @( N

( J$ `; B( \1 ?6 m! Y- I- F+ ^; U. W$ H

) O) a( S2 v2 h" z) h  * W' f3 o+ k2 {' u0 A/ l0 D

6 |& f4 u: W7 n/ h9 {$ \5 z

) q; g# w& |3 x- J. }" J& f& B9 O 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。7 ?, R/ X6 Y- y, ~# C1 R

! D1 F& }+ K' F( W U8 y. k" ?

- m! V5 _& a0 \, W% } 在is_allow()中增加对$this->savename的二次检查。! Q& B( G3 _# q3 f1 Q. ]( b$ }

@; l3 |) D! y$ @( D9 r( `: j

% y& s% P/ x* \& \ 最后# M. W/ o0 e6 m: s( M+ s- V: d4 L

5 e' y3 i$ ?( J! h4 C

% D4 g* ?1 v2 O0 Y 嘛,祝各位大师傅中秋快乐!5 g6 o* S% L5 e" M- W Y/ w0 ^) Y6 I

8 K; b+ O: q; O2 F

: D, P. m) a1 Q3 G" x. U   2 Y* k4 Q. \# ]# k$ M7 O& m+ r* h

0 j+ |3 @% b3 ^% Q
回复

使用道具 举报

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

本版积分规则

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