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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
* H5 H: D3 f8 y, P& n+ p2 y6 K% o/ h

3 I/ \4 R, b9 j9 k2 S% Z( F8 Y

6 k) c! J& a, [6 X6 N

0 G- `6 Z: _( ]( A! w 前言 1 L* b1 @4 Z/ ]) v8 z

n3 |( y6 R7 q6 m* @

}9 j* [' u$ m# D# Q3 f+ T 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 0 p; m( ^# b2 e+ Q" F

9 P! ~8 s! v- L

9 L5 V- g% i0 h  * p B! E' K; q6 n; V& s' i

. P9 R; ?6 P* Z5 K! A6 J$ ]

$ U: F2 o$ i l0 o" B6 c2 X 漏洞分析 ( D- |& y I7 j) k# u) p% z

! {. R5 R0 V ^0 U3 i0 S! G8 h# U$ @

7 |( W7 q/ `) B' H" _ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:6 Q% U1 d3 N5 m: |3 p( Y

1 o5 g! w( ~) \2 t, R5 _' V% R0 H

. y6 A2 N/ X6 v6 V   . F- e* p. B9 Z1 d; C

7 @6 l/ c6 y6 U t

! H; P' V$ ~: N3 x, G5 R0 Q, u 对应着avatar.inc.php代码如下: 7 \8 I# E2 S* u; n' ^

5 m P( {$ Y4 D: D* t6 g/ e6 N/ K

9 f' s# u" p4 x7 C. _) q. S! X/ Y <?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) { 1 R. V% X: c# N# t7 w

0 T$ f% e$ K n5 S$ j

) C5 K3 T0 T& R% @     case 'upload': ' P+ |( H* o9 E+ d4 b

1 H: ]6 m! m, |: A3 P6 u

* y+ s4 Z% K( r k% h         if(!$_FILES['file']['size']) {% I; H: o% p! C+ l

% }. |; k! i2 S- m( W# N6 e- b1 c

' q0 ?( _% B) r8 j             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); ! q+ D3 c+ B M' s7 ?/ M

0 H t: Y/ o# R o R) I& K

/ S* y, d2 p7 {/ f- k             exit('{"error":1,"message":"Error FILE"}');: \. D5 k% U0 w/ B

- [8 Z: G0 r, d8 t

: K6 a+ \6 Z3 y- j' p7 Z0 v         } 6 G/ u2 j! x/ f" z8 D1 ~

9 `* G, N7 Y6 r( e( T4 {+ R# O9 C

3 a0 k2 E# _& O! ]/ E         require DT_ROOT.'/include/upload.class.php';2 h# ?+ k8 R3 Y4 d# {) D4 o

. [/ u" J4 C" p6 e5 y6 {' H0 C

% W6 R5 ~, Z+ O% N  8 ?1 S0 T; d2 P

n! A5 U5 D/ K# e

: }3 f6 y/ H: _& m         $ext = file_ext($_FILES['file']['name']);8 h3 E& m+ K6 h: e' M7 Q

1 m4 F9 }% L' K/ l2 t( q7 ~* \. ]

! ], X) ? G ~! {# ~         $name = 'avatar'.$_userid.'.'.$ext;+ F) `- S6 @2 U4 s, s0 A& }

: H, ?! p5 d$ K1 m- h

u' t, D: ]* _' R         $file = DT_ROOT.'/file/temp/'.$name; . V5 m7 Q( W8 P- s7 c

/ b' E3 K. S- Z1 j6 q' ^# O2 D* T

+ {; P+ T5 E2 b# r+ a' Z  0 h4 \, i/ o$ N3 q

, |* ~) i5 e6 _% O& ^& W7 i- W

j% y& C z) ^         if(is_file($file)) file_del($file); , e7 ^- k/ ]0 Z* ^. T

) O! ^# H6 ~" @

. ?, E) e( H: N/ d- ]; e         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); ; ]' F, u8 t" V4 V6 c

C& p% w2 X) X# H6 L/ ?

. C$ C* A$ M) Q7 p6 R1 f$ ?  % C4 m3 X) P! \& i' Z* v

' t3 P1 V6 |1 S# {

9 N' M/ d5 [% i- \9 o% p+ b         $upload->adduserid = false; / a% o5 J T* V* l- w2 {

* c* u5 k' G6 }% u: d, J

~$ B# R) A3 n+ O& g  5 t6 C# F& E. D6 {# l, p

4 j( ?4 H* C- b& f3 s. ^7 i0 Q Y* M: O

2 {% O+ r! O# i6 ~3 a         if($upload->save()) {3 K. Z& }! ?2 v8 i1 C8 ]( {7 m% R

0 C" Q% F+ p- l) e

* l% [6 @1 |' y l             .../ I9 \( H) X- H2 i2 N

3 j+ X, C; y. v' K5 H5 R

% c( K7 v1 f9 y: n! l         } else {/ h: a& m2 p+ _; E) _

3 ]% t' f9 Z p, B4 T4 z. O* J: x

7 H/ L) q; N' {' G/ m* n% Y             ... 7 ~! h+ w6 R2 f4 L

- O) c$ X7 L8 V" g; a4 p% v" a

+ Y5 v0 I& H3 G& Z         }6 C4 |/ R4 V1 t+ W/ d9 S9 _/ c+ v# @

' K" F+ N, W) `6 t7 I

1 K/ Y1 y5 V- g9 F/ r2 ]2 U- x1 U     break; , E) k6 Y! N4 A9 N& l8 w

8 v1 C, N" k; L( n

5 b" h; D: E) a/ c 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。3 }/ r. A* h. P

" S# _: T) Z# i

0 J C4 g) Y) E- P: [3 g upload对象构造函数如下,include/upload.class.php:25:% t4 T7 I4 W2 {: Y% Y! k8 K; J

" V2 r% x" x# S& ]3 X

3 x4 A, m7 ^* }5 Z) A/ y <?phpclass upload { * T! f7 ^( K1 j5 \! ^2 g

8 I+ s6 V. s5 A

: G* o) a' R# K     function __construct($_file, $savepath, $savename = '', $fileformat = '') {, T$ g- \5 m" g/ M

* l0 f0 {9 m9 m# z: M

2 u @' [8 n% N8 X3 ?% [         global $DT, $_userid; % }3 E7 W( e( z# q3 d: Y- r4 L4 X

" j% f& L, [4 {1 B

3 D" ~2 H7 w+ Q& K8 I* ?         foreach($_file as $file) { Q0 N+ m( a/ W6 w3 |8 L

- T2 ^7 V ?4 o$ O8 p

P& F: v& _7 E! Z8 G/ z2 b3 A; u             $this->file = $file['tmp_name']; ; M9 d4 J+ n8 W X% {. j) H

5 [5 e9 V( }; k. J) S

! e ]3 ]9 l4 U) v3 b             $this->file_name = $file['name'];* b6 P" E2 a2 r# Q

* d6 {$ ]/ k- }) G" K* D4 g1 ^

$ w O& E) E: Q, M3 t             $this->file_size = $file['size'];# l; h: z3 u1 i* N

, S5 k) F& T' Y7 J0 b( _

' p+ O1 K! [, h; J" g& B             $this->file_type = $file['type'];/ l3 R1 V+ \9 W/ Q) L/ f$ `# d' n

2 c R* ~% V- j4 y1 M# F) m; G; Y! Z4 i

! r$ a# \6 d! A3 Y- n9 b# F3 `             $this->file_error = $file['error']; + N: K/ _; B0 H4 a& f

( Y6 u( Y" H. q7 C' {

r" k- ~6 Q% D7 m8 j9 E  $ p- k& [; |+ j" Z

5 q/ p. F U8 \" W

( Z( Z6 |1 x: Y         } 0 `: ^* Q& m1 y6 ~; [

/ O9 ?8 a" h$ E$ _$ E( X

& b9 ?1 ?* d% N } k+ C% a) O         $this->userid = $_userid;0 {; L5 [/ g/ O8 c9 [& L( q

; d* a3 J5 U t* [1 F

6 |" y4 L/ U" ?" ?         $this->ext = file_ext($this->file_name);$ D4 J4 ~ C! y$ n

* O( a. H f3 ~ H1 B7 f! y! \

! O8 U' l5 E' V5 O. [# p         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; & ^$ j( X; d) m1 X

, ]$ b4 o7 S4 C( j- J# A6 B

: {. E m a! g* x         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;7 e% u6 R' J7 ]0 x! |7 H/ }

% u4 f7 c7 q) A4 t# B4 O

3 E3 d- k3 F' D+ `0 G/ H" M         $this->savepath = $savepath; / ?: ~! _5 ]$ y

# [! I. ? C, x; Y( K& M! ?

$ s7 _1 Z3 Z0 F. o ~ J         $this->savename = $savename;6 X7 f* e7 {& h- O# h! z- Y

# M; [, S. G8 Z( r' f) U

% z6 s9 Z3 g$ l9 v) I0 ]6 M" j     }}! I- z+ l: l0 ]9 ` {$ l0 E

3 R7 C# |3 E5 ~; R, S* q

0 B/ h1 G4 M% a; v2 a 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 : K* }8 h, D' k) G

f, n: A b; i+ `) _* L

4 I# W5 b1 z" h$ r$ f 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 6 a% f {% W# e" F

: P* A: |8 e6 K

3 d8 z4 B& X5 p* M4 D& f! g$ @ $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 ) G7 Y d( a! p: i

- M; @- S W# j4 E

. Q$ b5 F( r) z 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 2 o+ P$ l: g8 `' |4 z/ d3 v

8 w |" P t5 A( U! `% h

% y ]- H# `7 w   * p! Z! I8 u: c" ^7 S! \0 l4 a" G6 z

) k) Y! {8 h' C" b+ W2 ?+ ~4 q

9 n c+ e* M/ O# c* U 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: ) d$ g: X3 s; ]% ^4 K2 W: \/ g9 ]$ G

* I, ^6 L( T: U

. i: u E% b# D. O2 h' V2 p <?phpclass upload {% l6 a: K0 D1 ]% q; b

) h9 L: |! w5 w

3 Q0 t/ {+ f, w1 K* r6 [# o     function save() {5 w/ a% M# P- A

0 @2 Y& [+ c" q: L9 d* X

7 r, h% |: _: l1 h' c         include load('include.lang'); - b( Z9 }4 x, I

1 V5 x4 r5 N/ e. }9 y

: \" X) s; L, X" [# v9 M! A3 L         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');3 k+ `: n( `% K- y3 n

' G7 S5 V0 U D9 a3 ~

( @0 f# N0 }8 P8 y8 U* E4 f   . i6 N/ p0 C3 K& ~

9 A% q+ R( x4 P: `8 G& n

0 N( N+ R8 ]3 c9 |4 Y         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');) J" t3 e+ t; O$ J$ }

, O( P, \6 }0 ?/ q4 g/ ?

4 ^, a# ^& m4 j) }2 R4 h   & W! O2 z, m3 {. J j9 P; K1 P$ ?. m

2 t* ^, E3 H/ U. T7 ]

' h" c% U$ {/ f( K1 _7 v3 D( t         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ( u+ e8 a: B. @: o. _, x) O

2 p! v- l `) y; d' }8 [

e. g- g3 h/ S5 G3 A" Y. I4 L6 C8 S   " p. x' w8 M7 B

0 Y* v0 [' ~5 Z

/ e) f, `0 }7 e5 w) S         $this->set_savepath($this->savepath); ( @9 L" q$ p/ `6 Z) X

g/ J) }1 F; b1 o, S0 E+ i

0 @/ ]. A3 p7 D( A         $this->set_savename($this->savename); ! j$ w# t+ i3 T! x

4 G+ q* c; x5 w

0 b; J! G( Q6 b0 D" j' D  2 E$ E# y- ?) R1 S: K7 {6 ^

5 E, ?3 b2 {" F. x: ^5 W

. E f" g( f/ m1 R) w% n! N         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); / A0 X. g6 m" t; T# _

( D# L% ^9 P" q0 o5 M

4 ]/ i4 Z+ M" y: e% d2 Z, k% _' f         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);# |. w$ ]3 B8 _0 O# j' n

/ T! V- V/ M: C! S1 p) A" L% |

0 [3 x# _% P9 X         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);$ R F+ [5 U7 ] l* p

1 p0 N! l4 H' k K" @& Z

; W4 }9 d# \" G! t" z. v   % b+ \( ]+ h2 M4 t

; k9 p K0 X! J; h2 F9 E8 \

+ r, W% I1 U" o/ k% S% S$ E6 P         $this->image = $this->is_image();7 h4 R. n4 u. U1 k

7 X3 X; ^8 P9 q$ g% V, ^& B

& j2 {3 g+ c/ C6 u         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);& r6 g. `2 d4 S' d6 c6 G

4 Q! s6 |" B: S0 o& E! ]

% E* p) G: x) e, P$ F         return true;" k% e( a U7 h, i1 X

8 s; j: Z" o& R9 z/ |& ^

$ W0 @/ H& s0 T9 V& ^     }} 1 h1 Z- [8 A! S& ~. c

, M3 P& Y& b2 F. Z4 }; i5 O4 h

4 R- k2 r8 V- ~0 B 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ~: Y/ e* M% r4 B; ^

! ^5 Z( a! Y1 ?- |4 ?' T

& p( N h6 {3 G <?php 4 M4 J$ Q% j' j4 X! J( e* H3 }

/ N8 ~! K# ~3 F( S

( h4 B# @8 x3 B, z7 H/ K0 v% `* D     function is_allow() { 2 d' ?# s& Z% A6 u& @/ N

9 E4 v2 _, o$ e! b7 Z" y* b& D

. Z! K' m) z$ X4 \* z+ T         if(!$this->fileformat) return false; 6 e0 Z+ S6 }" I# h5 e: C

9 ~2 a- D- e) y2 N b$ t' x

5 f0 {# d) @" {) l" V8 S9 q6 W         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;) Q) f' ^( l- _; a# n; }

3 N3 [2 k b6 n- k

) B N5 I% t) s7 C8 ^2 H         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;8 S$ p# t1 u2 T- `1 p e- o ]

2 B( j& h: j Y3 f2 v- _

# w3 q o- ~3 w' w8 }0 W- l9 q q         return true; ( G$ t0 h# K! @1 E3 s

! @9 L' ^# @ Y6 l1 y5 n7 ?! h" ]$ Q

4 m8 v( h7 m& F# r( m% j! _     } ! o4 G4 w1 C2 H2 U+ q

3 r4 G( c5 \1 b5 _9 H

! z, G- g, q, _9 o4 q" s4 P* {- J9 B7 o 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 [1 E1 b0 u& J8 t+ F* _& W

& q& h5 Z2 g' L0 U

) ~# s; o" G$ S) j1 x$ J- k 接着会进行真正的保存。通过$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文件。 " J$ R7 E! Y# f. ~& {

) Y5 ?. a9 M7 A8 \' ]8 N

3 W1 a9 f& b5 ^8 Z 漏洞利用1 t5 l X# N6 j# u- z: ?

7 S- ]3 w0 L. _3 ~/ P1 H/ q3 M3 L2 Q

( Y- R. |1 a' H8 N# N2 X+ F 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 & e; S# ~& m* k8 o+ w2 ~) h+ q

& S8 ?+ P8 |; n, _- j, g

6 A' P0 H2 v0 P2 q   4 C& d% ^& B9 |

5 `3 p' [" u, L1 l8 h

9 S- v* b) v' R+ `; J, g% s7 i& l" U 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid " N7 x3 Y L7 a& u- U6 F3 W5 p

! I0 Y+ z e7 R3 R0 @: e

, @, o( t9 S% y 不过实际利用上会有一定的限制。 , M5 g* [% h0 M- {+ Y3 C

7 {" U/ I0 d. R+ W" c& r: S: ~

7 Z' N$ N4 \1 W( p4 D! r# c 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 0 j5 n& _4 L }- d# K% S

. b9 }% i* i+ o- F+ k0 B3 ^2 ^* x

E5 @- T0 g- p* d& m4 R   8 l$ H( S {/ e

, p N! r- g$ L( e2 a; i- b6 r) O

' `3 s" t& F" S0 Y+ A+ E Z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: b. h* m" J) @% f

: n' b3 L C8 A' }

6 |5 \0 r$ k/ r8 Y; 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]);省略...; }( b6 N! [, ^: f4 P# Y

$ ?- m1 ~6 V& {" d4 n! j

@2 i3 b3 \# Z& I' h( d) j 因此要利用成功就需要条件竞争了。 ; @; D. o* K+ K/ I* r

# Y, w5 J9 N- J! K& F3 v3 z

0 O+ x# M& [0 u- Y 补丁分析/ [1 }5 x2 @1 w, g, ]2 L

: x) S! V* X `& O7 i. y2 x& @

6 W9 j0 C# k4 {   . J, V) Y! B: d1 M1 X

3 n1 P7 r. w8 _

! R, \1 u, g8 w0 l: G% ] 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:6 a2 q) |: {( w6 z% @4 s

+ K# L' o8 x) O

/ p3 Q6 M4 r/ {+ v+ Z& e function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}" I3 C. K$ o ^" P7 b

5 U- B+ \' Q. l

. y: }4 a/ Q( s& f, s  7 g3 x0 C1 Y/ R8 `- Q! _

, X! K# Z; o7 d

! d4 h: y. ]4 J; P 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 : v" O0 {, n3 b

" z6 s# U& V7 t6 W0 y! \. s2 E

! x7 c, p: n% y A4 s4 n9 H 在is_allow()中增加对$this->savename的二次检查。& D0 D! z/ N( S* q7 o

! n- i3 `5 B0 r/ f. [6 Y$ ?" s

8 Z% r( Q! y4 W4 e* T5 F 最后 " u3 _; m9 T; R2 b! r

, D, |! ^' ~6 o

; T9 C. `4 _$ T" l' ~; g 嘛,祝各位大师傅中秋快乐!" e$ _9 r* U# O5 a

2 x1 Q9 g: J7 q- ?

! }) N* |1 y0 ^$ g* U   C" C$ g. _9 K& c# J" ]

7 k: ]0 c1 L* g$ s. K( J4 u/ M. o) H
回复

使用道具 举报

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

本版积分规则

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