找回密码
 立即注册
查看: 2150|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
6 L# @. O* v( t* D1 b) l3 o

7 U- w# v& |5 ]7 Z5 S. z, \0 l

2 k7 d$ S' T; l/ ?9 Z3 n

0 [' p) l' Q; _$ D 前言5 x# \2 P/ V( E$ A- F( ?; D

; M. t# K3 n$ V7 A6 T2 @

) b/ z. L4 P2 E. a+ Q 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。3 P% ^$ ]$ e# o) I3 D0 h i& x

* B0 O3 y- y% x) O

+ B+ J+ p4 Z5 b0 H. S5 V   " D) d0 q; |* G, M

1 @3 n G' b$ L1 r

# A4 Q: n# Z: L' n6 A$ A 漏洞分析 1 V' v9 d J% A- q0 R

# X- [* `% P# Z) W: {/ P0 y

2 [; H0 I! A& {) U0 o: C 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:% v/ e0 k5 |, q- v# w* {: c

- Q% x: V! }1 E% A" j6 I

1 q* K y9 [# u# U! q   s( T- ]0 A, y, U) K

8 n& E' g. E/ d" J

4 V: Z* b# P( [ 对应着avatar.inc.php代码如下:6 y3 O$ _) E8 G7 E

: ?+ S# h n2 [: _

% [+ ? z! H; X4 E/ H7 j# A4 ~ <?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) { # H8 w' a& y2 Y! H6 r

# ]7 Q$ r7 S* |" q n

% Y0 E$ h. c2 f     case 'upload': : t) R! S0 H( \

/ M, A& m& J- _, ^1 j9 B

" v* x% W1 n- q$ g; h         if(!$_FILES['file']['size']) { : d! W& R9 f# S: }

7 t; }" e, Y9 O

8 w5 G0 R' F8 ]4 H& C             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);8 W' g M0 `* ]. N

- a5 r" z" `3 y# Z7 {* Q

7 ]3 X0 ~% r" L, W( f8 @             exit('{"error":1,"message":"Error FILE"}'); 4 [0 a+ ~" ]" p9 C7 M u; L" P

# k2 C) B6 r7 P( W0 t- D

# r. P+ m& f& U5 f% ]         }* f$ T b% p) Q$ ]. \

0 K9 @$ b6 S. o2 m

, k2 r( v1 U9 C5 ?4 z6 L* U         require DT_ROOT.'/include/upload.class.php';" G! J; i8 q$ y

5 }8 ?% Q5 z7 a( Y

% l4 L1 r1 c( K( s9 D  2 m( q$ X L3 N$ g

8 j4 e0 f, _' p6 v+ I. E; @

" X7 `/ F- d! I& | i# P- P         $ext = file_ext($_FILES['file']['name']); ( ]' A% Z. O; r' _( ^) S4 S

$ @5 q& r- F; f* T9 Q* X5 N3 N

9 x7 r/ q# C7 p( x         $name = 'avatar'.$_userid.'.'.$ext;/ N$ R+ }1 _: K( O. ^7 a

' y& Y3 w, o9 Z- v

5 X6 M5 \6 @$ {: Z z" @         $file = DT_ROOT.'/file/temp/'.$name;+ k6 \) s# O1 E' U# [- _- }( r% K

5 ~; S4 ]+ n3 y3 T( b( ?

5 g O+ ]* N+ a4 d0 H! q9 m  + b- |2 R4 Z# f

9 Z' e ^* K% U; E

: _* H' k, \2 ?         if(is_file($file)) file_del($file); / _; S0 \/ n" i$ I# n5 t: c/ p5 p

( P* V7 P8 B3 o: l

' I$ o- y. J9 K' G2 _# }         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');$ |3 r1 M* G# f, A( x3 [2 [

! _/ K0 q1 L6 c& J' r

) [3 {& |) o& v0 {  7 V: t t$ P5 V% p

; N" d+ L# q% \7 k

' \4 T8 }& n5 [- ]2 e0 o+ A+ s         $upload->adduserid = false;: i2 \; D$ K& L) Q/ G3 m1 f

T3 M7 D% I2 ?) A& O3 T

& P, O# V! ?4 s1 K) A3 d   2 W6 J2 k2 I' }* |. _. ?% ?0 s

1 ?+ h) {% W4 d: d' l y6 @: I

8 x' I+ O, A2 T6 @         if($upload->save()) {$ t/ }5 E" B, k+ B5 j& K! k' a

- q3 V* {4 N8 b" U4 A

1 A4 w3 d M& g& D             ... z0 r! @5 c. u

: S9 L. h8 r- G4 B6 E

+ ]4 g, P' E+ q, }( L- l         } else {! v" \" ^$ m( W$ h! Z9 f! C# }/ S

# D+ @; r% z- D( Z9 J% D

3 e7 X* [5 s; z& a) k1 } h" ~             ... - Y) _3 p% S, u9 i

" }. ]- N1 Q+ y3 V; ]( t

4 ` h) C3 @2 x* x$ n         } 0 W5 _1 z# v1 ^4 ?5 a" R: r' o; l

* i. e# O0 G s$ u# J+ s; V

% j/ a) I, x0 Q! \+ B     break;! w ~, K5 Y' _" A

9 V( t+ [, p0 U! x8 c

# V, W, v3 C. A! \, u 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ( ^* H+ O `* V7 {! W% X7 d! Q

, P4 v1 O9 f) G" V

, [2 a; k1 T& s4 U" M/ H: ]. H Y7 e6 s upload对象构造函数如下,include/upload.class.php:25: / y" \2 I: y$ J2 @) ~

* `$ F8 } z* u( \" i

0 V% r+ a! C' q6 t, ] <?phpclass upload {/ |/ l# }: V3 s& A

8 b9 B+ s+ F6 c5 y5 l

, O# s* A( v: k     function __construct($_file, $savepath, $savename = '', $fileformat = '') { " N1 _4 j, {# o. ]

$ A7 `% L5 t* G; F, x9 D

, `+ v& E3 y; b M# A6 }         global $DT, $_userid;/ K1 [" N# @$ L9 j$ l8 v" Y

5 w2 B: p( F, e+ H; k8 K

3 K1 m) H J& n4 Y         foreach($_file as $file) { - a' U% }, q8 r2 Q; n: o0 m5 q

- u& [8 f8 ]. R5 U {

( G" z4 q A+ {( c+ z             $this->file = $file['tmp_name'];# Q5 T" I: w% ^

! p; W# h0 l$ |6 A5 [$ I

3 B+ t8 L1 F+ V* F) l# p             $this->file_name = $file['name']; 4 w1 h+ L6 U5 Q7 }8 w

" N8 ^: O; O4 y

4 ?4 K" e9 P3 C# \             $this->file_size = $file['size']; & u L$ X: p. }

9 g6 m4 ?. F# @% V/ _- t- Y4 Z0 I

, Z7 N0 A5 O& x/ j6 ^             $this->file_type = $file['type'];# A ?3 ^1 |8 H" Q( d6 h% s, r

9 ]3 C9 I& r3 o6 [+ R

6 M4 w- C, G9 F- \+ b             $this->file_error = $file['error'];( l, C& V4 _: h/ m5 |: }

i4 H O! E( n% E8 g

% o# L* j% \9 _( W3 j# Q) i   ) \3 F+ E2 A m2 C* a9 `' y. \

" ~) \* f6 M) \" t7 s( g

& @7 S4 ^) l% D. \+ ^) `. ?8 m         }9 l3 G* \% X' M4 A# e

& ?. ?# S, V& e$ _. s9 M

* h( h+ C$ N" \" Z         $this->userid = $_userid;! v @: z6 D# ? S- X7 r

; ]+ m( @+ n" P+ R9 y

4 z* V1 Z. M$ r4 I$ _. U         $this->ext = file_ext($this->file_name);, ^8 x0 v3 n, h

7 Y3 ]7 A2 ] Z/ d( p

g# B6 i% J' X" P* r         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; : } d% [% L+ O0 ~

9 W# V$ E$ [; S/ w# R& x2 @

4 C& b# A. j2 o! I' {( U3 g         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;1 g8 O. Y9 i1 E% ^9 B

4 [- P1 ~9 ~* l0 I- J: `

3 j: f$ {, [! [& V$ c* d         $this->savepath = $savepath; ! h8 D0 Y7 n! W: A

4 A( x# c: o5 M$ ]8 I( W

& k; d! R* ?& x. j. Y9 T         $this->savename = $savename; 3 o# e, w) K2 n5 b( \

" `0 T+ }1 l* |- k" P

* F+ i% D( N- R9 _( O     }}1 V* X- [! M4 i9 M5 H

7 a, U7 A i+ m5 l

0 s9 m( v7 L0 q c+ Y 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 W' d$ K- a+ b4 I: I

; h; v, F$ @4 T! O

$ Y9 R+ v$ X/ m! ] 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 - P' T8 n* Q5 f" r2 i

7 y, C: Q* n! h& v0 q. |

' d. P3 ^3 a4 [+ t- Y $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* l7 @8 W6 l* F+ X# u1 S

! s1 `0 D9 u! R: L' i) ~

' S9 p& R4 e: A" b6 n/ N 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:* K" ]" O; s4 V- K6 ?

/ v. U( }4 T, p1 g* `# K- o

$ J# {! o( O0 A' |7 K' e9 W  ; r; V4 Z3 B) i. {% [

# }7 Y4 O. }$ E4 ]# R

6 K1 W, A0 d$ t% u9 l% H 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: & c Q$ u7 ~6 g2 m/ K* J+ R

n% I1 ^( r8 w

3 G, o! p8 Y7 ]# [1 N: w <?phpclass upload {) y1 T1 ^/ S. }5 O

1 r( ~0 q9 S; k- e: U$ s/ t4 d

/ v' j) F4 t# D& r* X! x( o     function save() { 3 b: F1 z1 g: x/ h$ H8 W( i# Q7 Y' k* a

( e, J' C( ]! V3 o: o& \5 `3 g

) F$ c( ~/ u; g1 S+ w$ m0 @         include load('include.lang'); 2 s+ H/ j, }, J5 x6 d. O9 |

( T# C+ z; ~: t0 I

+ T( X# b$ ]( i! E8 L         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');, {1 _) X, u, o

- o8 I" g! r, _2 e# e

( f. M) C& b1 C# \+ F  + I6 t) X( j& d" m( y( n

+ v, j$ C @. f/ S. J, z. N B

3 u- ~9 h7 W: J9 O5 m! v         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');+ f- F0 {0 v- [- a5 i, t

2 [2 K, {& s8 l

+ {2 H @; `/ ^. Q) c4 T   4 R3 U- \4 h" W0 t/ x0 N

3 z4 J7 D. o) {* s

- C* }3 ?' V% k( g* x         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);% y9 x9 a- r: e

, Q: W8 g2 O, u9 |+ w

& k# E0 `& k7 F n1 W8 t! ]; T6 \   ! k" I7 [1 K O# ^$ Y

" ~2 z7 t& t! l% R$ b) \

) B8 x. h# F8 P         $this->set_savepath($this->savepath);3 p9 Y5 d: n3 c2 e; L

. \8 s; r" L7 S9 u" {; P

, A+ {) k( p+ N4 e Y         $this->set_savename($this->savename); ! T' t& _0 D4 _& t4 w3 B

' S' e3 ?% m+ i$ E

* y8 u1 ^, n7 p1 z. l! c  * y, I! m5 n9 l$ z

' ~+ l% c0 @6 ~" W; s* W/ ^1 B# l

' M- q4 H# b0 L6 @: W1 @+ o8 c3 V* O         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);/ H7 J9 G4 {1 J5 n

5 b; H4 C" N" c( Q, j

! T6 Q2 m4 j7 h         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ! h7 [) \* O" f' o( a

% `7 n: K. G* y. Q8 w/ n

4 g8 p7 t( j& t8 h0 r         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 4 j2 H7 D! v9 n9 J

' j1 u+ S n% S; G

* h: S( Z$ j8 U7 @6 s' o2 ~  / |5 @8 s; p& z2 u

- ?: ?' d& A9 t+ e

- L: a/ a/ t+ t         $this->image = $this->is_image();, Y2 c4 h: r' o* T

! R. J! v! J. {/ R; G2 v8 B, A& K( h

r. I' I9 d0 G# ^4 X1 M: O         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 5 C$ i( O2 N( K: J) h: ]+ ~

) k# d, w( H q

% y4 f" c, P6 J$ g! }! ^: Q: f# M         return true; % y& |* E6 N$ |

. C% r2 r' A, B2 ~+ `; o

7 g$ ?7 Q/ M/ n     }} " _# Q' p1 B1 a" z p4 F7 c/ k

7 [6 V# D. c. _8 F" x

$ Z9 q0 m: m. W' l( l9 c 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:) I2 V9 s, }/ h$ U2 y

0 [1 n5 K: i1 E/ S: @. ], M4 p

' `% s" z# _# i8 N( i* h <?php r7 `8 p) D+ o

( V+ R1 ~1 b6 b) Z+ y% H

7 _2 Q2 O: \# C. b6 u( s: T     function is_allow() {+ R# C7 |: n* }, @7 p

( n H3 J F5 Y" f5 w, S

]' m) k0 n8 R' Z         if(!$this->fileformat) return false; " \) T: d7 K2 d/ I$ O0 q, U/ b# u

; q7 S% y# J6 s% e

# h# n+ {" d( S5 N1 {" }( {0 H         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 3 }; K0 h1 n5 r

9 R8 C2 Y( s) d, n5 f" q, d

s2 S; J" P. p3 T9 G: s% g3 R+ G         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;! z8 b# T) u) k& C

- ~9 C- ?9 ^. O8 o J" K

6 G4 Z2 Z$ G# n0 n$ _! I0 T         return true;) l: ]$ O/ O4 P/ V: e1 g" L. c6 ^

, I0 W) E6 L6 D" w/ D/ b7 i

/ j* e |) S- i* v     } ; r4 @8 x: C! x' j8 f, M

& [. L# O8 C7 ]. {9 } r

, ` u* e$ i0 ?. F5 O$ t. v 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 `5 X% H7 B9 R" I# w" i

F5 o; Z' S; A) V' ~3 A S" R+ N

: j0 A) c2 X+ G 接着会进行真正的保存。通过$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文件。; s) U5 T) V" D2 F' M; {( {* v

. v: U1 ~: k# y: A

8 P g% Y4 w* C9 J6 } 漏洞利用 J1 e, f6 A/ j' k0 T) v9 R

7 ]3 N! r& }9 ?3 q* Q

" }; r2 S) V4 y 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 ' Y7 |: |" t3 `

! S! N/ S# ?4 ~6 J3 P& x

, r- j, z( S3 g# n$ i   8 T7 x# V+ u8 c( J) o

) D# ?- x/ Y6 u

$ }8 f' S7 M- Z9 R3 a% u: ~ 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid) c- i! X% j1 e9 |

2 g* N* D$ I5 I! |" k; m6 V

; a; U: D! o- E' @, S 不过实际利用上会有一定的限制。0 \. ]. L7 B) L. }* s

4 j' x) }) ~, U6 A

4 D& |6 N, A& S. K @; e 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。2 @; z8 ` G B; W' t( Z

9 k" B O1 e: y% [) w: u

' @6 D& V* S5 I6 X* ^0 O) u+ m   / q( S4 }0 S" o2 Y

$ `1 ], M$ a# Y# T$ M

/ N I D& L/ q k" t8 z( N, q 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:; J4 j4 [ B. Q# s( n

! n2 M; B' g. H5 s; N

& Z+ t' e* l" H" \8 i 省略...$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]);省略...0 T" E' x- ?6 R( _

: N$ s/ m( M) F4 M

8 @3 @3 \8 s. l3 Y 因此要利用成功就需要条件竞争了。. s( k& i0 c; s- x$ N

% u. R6 E% d" @6 C+ ], `

( i; q* g% G" v1 _4 x3 R 补丁分析: b7 c* E5 A% l: k$ y% D0 K6 p

& d$ ^* {6 F( x C7 O9 A( D

5 N/ f: I4 A3 S# j1 p+ T  2 ~$ X$ E3 X# m* N+ O

& K* Y" q( c- p* g+ r: {, f& V$ Z6 ]

# P; l& g8 `* N9 N( P 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: . J! x# J* F' y* @- t/ ]

& c0 y9 \' q: B8 O* G' z. f( g

! D, I! P& T: G5 `( Y* [4 S function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} # u: L% I. n7 t; k

+ f2 ]- h0 U! c! f( q6 ]; c

' j$ p* e r8 M/ y R8 f+ ^ ~  % e' e% X# z; i e

! o- ^2 \& q: u9 P) p" n

' r" k7 W. f4 q$ _8 w+ D( R/ t* Y 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 2 m/ W1 e8 h5 h# f( }2 y

6 S- V' J0 X/ Y% L& B. Q

9 b" Q: C5 z# \, L. q, L5 @ 在is_allow()中增加对$this->savename的二次检查。- W3 W5 ?4 l$ ?! k3 r w

8 U; Q: d# b3 k' x% g, U V5 I. x2 m

# g3 D; a( p2 f. ]: v& x- ?% m, y2 b1 U 最后 0 I4 c2 `1 q' U

9 d0 V$ _% y7 X

1 `/ R5 x' f! ]" x4 L! G 嘛,祝各位大师傅中秋快乐!/ |6 x& u! C6 \2 X5 n$ I* H

/ [7 g. I; q# K( E- O! f$ N

- I" ?& p9 Q2 d  : Y# f# a$ i% \5 @7 s8 |

9 g3 ] f$ h2 M6 z
回复

使用道具 举报

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

本版积分规则

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