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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
6 g. c! x0 _) B2 G

% c4 v- r, ~4 W: `& k

# e) m3 p% _# a/ ^" |

+ N t: r/ x! |& J! B9 z) [. I 前言( H4 {# b! B! [

5 c6 {* P. E, m+ `8 H5 l

1 b7 |& f6 H. a" ~" X- v; c( D 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 : a5 g b+ ]- I: i# l0 A1 L0 @" C0 Y

+ X( C* o2 z" a+ F, n1 k! o2 I

7 o7 p. @+ d4 M" Z7 F" a  / f Q) `/ \( o, ^2 g- d

: |. B6 _7 m' E- j: F

/ A0 g- \7 N) q8 C4 f 漏洞分析* U+ V7 g4 p! m

9 v7 c+ C. q# |6 K2 ~: I+ w4 Z

* v. E+ \7 p* `: d 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: . w9 Y' k: ? B: G" h* L. ~; t! H

: L w; |4 P$ t& ]

8 A( T6 D. L& Z" r8 o8 q/ o   8 \; ^8 h% V7 v3 n% [% V; Q

3 u, q7 ^7 O/ c/ T: l

6 o& H) Y5 T- [ 对应着avatar.inc.php代码如下: 5 |2 p% Y! ]9 E$ H9 f

2 O- A0 V3 @! C+ \$ I% @

7 r7 h; G$ ^/ R! f# O( `( F <?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) { ; l. e) N; B1 j j( j* [' E

3 B7 }1 h5 {$ }8 k, T

I; S+ T! N3 @" |' J5 s     case 'upload':+ e2 L& \/ l4 Q3 T: I

# ?' t0 h0 z: N% l: i3 l

1 V' B; J1 D0 t- ^1 K1 A1 n& V' y Y         if(!$_FILES['file']['size']) { ' o5 n: |8 ^! {! S0 M) d+ n

* n \" r, [( P" I! Z A

. l* j k- \' M) R6 u/ h! ~+ }8 k             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); $ z3 G- o( ?3 W% y# \5 R: Q

2 Z J* N3 b) W# Z

9 @0 l" l7 o$ I7 K) R8 Q6 k             exit('{"error":1,"message":"Error FILE"}');6 v0 X; q1 Z% [

0 B( k7 R Y9 ]6 G; ^9 }

7 c# g) {' \0 i( X N         } 8 G2 q* d1 O2 j5 t

4 e$ T R. ]8 D3 l( i- Y

3 A+ o( O2 ^. [$ N8 j- L         require DT_ROOT.'/include/upload.class.php'; 6 ] u9 G1 z' I" k7 f

3 e6 H% a4 s! f' `8 n

' C- k7 e( c4 L8 }5 _   1 g8 I1 d3 |2 B k* y* i

+ F. J" S `4 Y: f

! _5 u: R4 \# @& t+ l2 `8 l. r         $ext = file_ext($_FILES['file']['name']); 2 I" J" p: i5 u% d3 e0 }

1 m/ |" @( m7 i

. l+ E9 U" X( R6 T$ j         $name = 'avatar'.$_userid.'.'.$ext; ' z- O! S# z: n

+ p/ }3 s' x+ \/ N8 k* v

5 {6 B6 ^. Q: `         $file = DT_ROOT.'/file/temp/'.$name;% b" K- n- ~% Z& j

' X% K2 `) {* A+ n$ X! }: m& q

* S$ R3 w6 W6 j' G- D  0 b$ x1 P% ^( _. r2 H

% o" Z& o" Z. J9 l+ q

% u$ G3 y) K2 _$ [2 r8 t0 l7 {, v7 M         if(is_file($file)) file_del($file); 4 L2 \0 d# y1 Z* F: |

- L' F/ i2 c7 M l3 l& L+ v q. Y

( T) U% t$ W+ }, Q/ g4 L         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); * e/ A$ u$ S) s6 p3 p) u3 R

. j- j# Q3 O" J; i- z0 ~

3 \. O$ l' O3 l" Q, g5 n/ F  1 Y4 P& _* p& `

4 w( e* z# p( {' ? v9 P

5 k7 }: V. s- U9 l3 j         $upload->adduserid = false;; J- F) K* y: @. D6 X2 Y) x; F$ b* _0 Z

7 k# t- E- A7 P" v7 M1 p

' _9 V( ^# l* G# C* I   6 i# |# R$ z" v$ ~, B

9 ]+ o' P7 h$ _. m9 `0 v

; ?. i( ?/ p" O K" A         if($upload->save()) { 0 G" C/ {/ d% f) b) f

$ w/ s2 u4 Z* y4 c% A

( [ ~2 o9 p d: n5 M" {             ...: V$ B* w0 k, u c5 T: v

+ h0 W# j7 S1 a d' J, L: `- m

: N: r' ?8 X/ |, {9 K         } else { 5 q ]1 D$ t) q" f0 O P

+ L/ {5 }+ T9 W. b: B7 Z% j

- J5 K4 P% j5 a" P# E* ~             ...% h# e' T4 J2 X% Z. Y# v: m

; j0 k+ Y5 B- U. S3 [/ E+ F

7 D, Q d5 Q2 J& b3 A/ Y7 E         } + L0 c& j, x0 H/ l$ n! v# l+ U) I

# O) ]" c+ Y- ~8 e& ]* E. I

' B! J5 {7 X! Y- r6 r3 F     break;! l, }. e5 V$ I

5 l+ u4 F" V" h0 v( F, c- A

3 N: m1 d1 m# h' h, V 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。, Y4 x7 ~3 m, j& j

3 H) _1 p% Y) R- y: N I

1 T) v* i% w+ B upload对象构造函数如下,include/upload.class.php:25: @3 b& z) E* R% o' Y/ j4 Z6 b$ F

$ Q# p! z( B n9 R& z Q% z0 `

! q4 h$ t0 g; q { <?phpclass upload { 2 F) L) d. f i" ?, e2 b& K

, L+ `5 p Q8 y9 O( V0 ]) q

' H0 o# D2 l6 e4 {6 x# l     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ! M' I$ D) X5 D4 u* M

, F2 g/ [6 { A/ s; D5 A

0 @% k, T7 B8 p+ M( M J         global $DT, $_userid;* F8 R' O/ x' T3 h) l% R8 i* I3 a2 k

2 f% |8 l; a/ S) U$ n

0 Z- P. r- ~( |7 k6 u( r8 o6 p         foreach($_file as $file) {9 ?9 H" T, a \6 S- k& }/ @

* ~6 O7 Q+ O" A1 B

1 c g t9 z( t7 j/ W             $this->file = $file['tmp_name']; 6 x. x& T+ `5 \5 L0 T' f: g7 @9 A

9 i% t# u; e2 [6 v9 ~) }

; g, d {$ ^2 r: n& }$ p! m( R" [             $this->file_name = $file['name'];: V. o5 e, l2 }0 d8 D+ h2 v! E

; H: j# R3 J: r1 A- I. d

) _4 y" `, k+ s; N9 A, U             $this->file_size = $file['size'];( C, ^# K$ u6 t+ Q

5 p" f0 v9 k& Z

/ v. F! y s3 {& m k6 V             $this->file_type = $file['type']; # C C( L! _* X) M6 o

+ A( ]3 F4 g3 q0 d

( i3 w6 i2 d' {             $this->file_error = $file['error']; 6 j' U5 E0 U' k# b- Q K

3 k% u6 l. S0 ]

; a# H V% }2 X* }/ X I  + a0 y) E+ m4 [: l4 R5 W

$ J+ W9 P" s: D3 N7 o8 M

4 ?! _' V l z0 n         } 8 x8 f6 `6 a7 G

. x' G k4 V6 T- Z& x

, H1 k( S3 M4 k, S" _& x2 @         $this->userid = $_userid;* x5 O" t, X! v6 z/ Y4 r2 B

9 m5 [1 v, z# C7 N

- F q; w* c* S% g) Y' v. y         $this->ext = file_ext($this->file_name); ! f: _$ i8 I, I9 ]7 x

; G7 ]5 o, {# a& k: B

% W: Z# I O. Q: v; N) L6 @         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];3 }1 ]- {6 {7 g: H

! J, G: B7 b/ v% a# W

0 B! c0 J: q8 {         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;+ g, ]# M4 T: j9 W8 |$ a/ I& Z" L

+ l9 I! P# X3 y2 V0 d3 k; q7 P

& a5 l' i2 i& K. i6 m/ O+ F         $this->savepath = $savepath;* N% [; E6 E8 E8 |+ y' a7 z- o0 f$ }

* E2 R, e- @4 K) @

& x/ J. g! t" B/ d         $this->savename = $savename;) M9 K6 h0 V, C

* Q `9 ?/ b$ G4 F" }8 B1 v

) z4 [) V3 O3 q6 O     }}7 K5 E; s# v0 e& g7 V

( g/ S6 u5 L( d4 s; b

9 X. p8 s7 P. y0 [' O' S0 O 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。# v* o0 w! G- d% g# P

7 w+ c& s& y6 v. W8 \

+ ^- G3 d8 @0 x1 o0 o 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 , V* {0 O# q8 V& L

. [, q* X, d" K

- _" @8 V! H) p& ?% O $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.php9 h! c8 Q5 x4 i) j

8 o0 A+ L9 Q0 r# G0 E1 n* v0 R

+ b6 G3 G$ o! L2 X ]/ J$ p# }3 T 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: * n7 z7 g$ }5 `( z

) F3 t9 [; t) @* v

1 h' v* B5 v8 E9 D  " c& C5 B* p6 s8 K. b9 t& k- g

0 |, T+ _; v% Y

) g0 r9 e( H' S5 U 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:+ K0 |6 R# a1 G0 y% K* j# C0 S& P

& H h9 p( r! g4 K, N( U& H

' E/ L/ M1 S/ B) G, y8 @ <?phpclass upload { ( V. v5 j8 E7 c# N+ K7 Y0 N

* n3 }+ e \! u4 j- Q+ E

( ^+ {4 ^) h+ v3 q     function save() {4 |$ z6 c3 j' d: ]1 j; }/ X

6 H( N7 K7 J8 n* A7 h6 C9 e8 Q4 ~

/ T: W. b/ s* {/ b         include load('include.lang'); & D0 Y& {4 T6 O0 k- ]

2 ^5 r! k! v# Q4 b, s0 |% A

# Y) y+ _0 I; j F* c3 k# s7 t         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 9 u K j- I+ u. ~ u

; n4 Z/ ]1 @2 A6 W: V# ?

+ E* d, R& _7 Q. `  9 N$ {/ Y* u9 m! W: g

7 {: V P& K: H C: t

2 e# k( i) a' \. Y         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); * A# v% c V$ ~/ t& O1 N

* B" {. P! l2 [$ ^6 E

6 S, d$ f: l' B7 M6 O9 f; D% ^  , s: `% L( ~8 D6 H$ G0 D5 S" F

; f0 C7 B4 I' S

' z: g ~- l$ q6 b# t         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);3 a: _' _0 @& q. y7 |

* [! @9 }! H6 r9 ]0 u! N4 f

" \; R! F9 n* n: U E   6 r0 y3 H7 [$ Z7 T/ d$ j

$ h. z, C, o: S0 r) [( C% R

2 k7 r0 N q. g$ S3 @         $this->set_savepath($this->savepath);/ V7 ?* D& V# W- R0 H$ y* ~% a

5 d8 G8 v1 g8 S6 U

0 i0 P6 c) m S$ K3 b2 x         $this->set_savename($this->savename); 5 N- E, H5 j: Y0 E# S

8 f0 s& z0 Z7 L: I* K( s

4 X; x" I$ D S' m- V! I  $ ^4 J% v& v f( L% o; g( |9 }/ m

9 G) A* [4 T: O9 J4 e5 |

0 a9 G: ?8 y' r& @ J% \         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);# s! S# |, I8 h7 w" n3 Z; e

4 Y1 l5 @8 Q1 L3 J7 R# E

/ S& h9 @7 @. F# S         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);% A$ Y& t( R; U/ T% X

l1 m" \9 [7 k2 ]9 N7 c

2 G c- d9 s% b( h+ [) U         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); V! ^" Y9 U$ Q% f% r$ B6 {" ^

- {# |1 h/ A- G% K& @

5 ?% f' O* O6 E4 \, N( B* _; k/ ~  5 R- E% r N6 ^8 |- v

( j* q$ u4 A3 R! {7 k

) _5 B" o# Z4 I& L1 f! p         $this->image = $this->is_image(); @6 `* D" W+ C

" |% ?4 l P. b) W2 ?& ^7 K

) a+ w' M& @' @ A         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);" o& s: [2 n4 a5 ~

% o/ s/ ~6 {# e1 b4 B# a# G

2 K8 v5 D: K/ g% J1 b* y$ Q/ T5 i. c         return true;" o( G, V+ t0 [6 V) L

n$ p7 T) J3 v J

& |3 m! i- v+ B; I4 L9 d     }} 9 C7 B) ]: E. q: r, a' q

1 e( W. R9 |" L6 \

7 n; K3 @$ x& H2 e2 l 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:5 m+ Y( y" h/ P) y( K! p

1 v2 s% u( G& W! e" |

' ?7 Q) y& s @ <?php4 J3 H5 l8 \3 U& p* q, ?5 g

6 r( C8 o+ P E: @% y7 v

* M3 I* Z# N: L3 S# M1 [1 w! S3 u     function is_allow() { ( g3 N `7 x6 w8 P( e3 K) H

+ I0 P& i; o: n5 P1 N+ a4 R2 s

! n! c# x( @: O$ Z) e: ?! D         if(!$this->fileformat) return false;8 Q* \$ v7 T0 y5 w9 y3 H0 ^

: d4 T. I- B* T

B. Y# ], Q4 o8 c0 v# f! e' C         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;& [7 F$ W/ S2 X

2 P( N! R- @4 p! |( ]' P5 C+ k

- Y7 v6 q% _1 G3 Z         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; % {0 P, h: ^% M3 R, W1 e J

( ?( b) ]$ V) ~, O: T

* W4 t$ z4 c1 c3 c         return true; : O6 i) P# u, Q% U% ~( d6 c

9 v8 u/ Y; z7 @

N) [4 F" V9 J     }( ]9 n( ~8 Y. ?0 B$ Z

$ V' s7 Q, J6 @

; {- J5 W$ s* D 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 9 p# ?" F3 m# Q; D& g+ Z

7 K- i- u6 ^) O/ \+ q7 Q6 N

) ^! O4 o6 I+ Q5 X 接着会进行真正的保存。通过$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文件。5 k T: t {8 w+ e

6 ]" o: B8 l, t6 ]5 I, S- ^( F

% H8 T [' z. H' }1 {% o4 P 漏洞利用 8 W, p% A( Q3 f% s/ b

. `1 \) \, n( p. I/ f5 Y, z+ _8 Y! q

' q2 o7 s+ V8 f( u$ y. G 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 - K; x [) @9 g" `' s1 L4 o

+ X- ?2 `0 h/ B# v! O5 m

[8 Z. U" X6 G8 D+ P, R1 k   ) u5 m/ ^" \ ~# Y& l* q

9 L$ J k0 Q8 |5 p

& B+ o% {" g% j 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid k$ `: C; u; ~" t

, Q& l2 X1 w7 [

5 _! J( P+ ]9 u$ k$ t3 I- | 不过实际利用上会有一定的限制。. I# P2 Z; A+ m4 ~, ~1 [

" ^5 n. i% x7 ~# t7 Z c3 D# I9 c7 X

* j, ?, E `6 o S Q+ E" y 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 : B/ y' T6 ^, B) l' [7 L$ W) `8 u

4 d; E+ E4 k- b

1 C/ l' x, o2 k  9 ?+ J* }6 O% }% l. u

4 ~' H1 B; v. [; U

6 I0 L$ ` H2 w3 |$ ] 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 2 W/ p# s( O. ~) X

: O. G( H. O r) J$ K8 r

& k' K n1 Z8 k6 \7 c; G% ? 省略...$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]);省略...- d7 l9 ]# P& N6 e3 e5 `

% I# L3 n& z8 s4 \7 t+ j

& U+ `; q( j$ O' s4 W# n6 M 因此要利用成功就需要条件竞争了。& a8 S! J7 K* w# |

( ~; v" C* C* I6 b# L

+ m" B: W9 m& f, O8 R( Y 补丁分析" W) P7 S- F0 t0 z0 K: e* d* ^

1 S u& m7 N* r1 z3 G

" a9 j+ b3 @# b2 d  7 m1 a3 Z. m2 M C' Z& Z$ [

3 S& D2 x* i* Q" V

+ s- m; i2 P" l. k2 \1 Y 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: " \) L3 n% q6 m9 E) [7 n' v

9 t& q$ y& k1 X0 p& e# K

. _- _' I8 Q9 S9 {' [ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}1 V5 q: {5 t0 i1 y+ E

+ ?4 |) Y$ `( _+ |" v6 G; j' q

; F1 s& _& K/ |- ~, ?: A4 i   0 e: l( D0 }3 R6 M3 S: n% I# ~3 {

2 f! s! K* Q: V7 y& b0 m

9 J8 c) R! k( \+ D( `" ]8 D 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ! K8 }! g: S& c1 }' w$ G# ?0 p& T

/ d7 A0 U7 v6 t7 P: {

: _% Z* j! i' O* H- d9 g 在is_allow()中增加对$this->savename的二次检查。8 I# T3 _" {& N- a+ E

- U" P* R9 a7 j4 ~5 n2 V4 i" h

- O! N( ~7 _+ F. ~6 O/ j% T H! b 最后 ( c" s' D+ u2 s1 z3 y

0 s5 \. T) P1 g8 b( d9 [* p! t

1 s; g' y. F( b+ @4 ~. h; Q) g 嘛,祝各位大师傅中秋快乐! 2 V1 b' F$ h* P3 v; x* i

$ }7 R( H$ U9 `# z4 A

1 I* a/ X* H; x9 x. g% m  / v6 f) ~9 U2 h9 [0 Z+ b

( x+ @3 S# R& F7 B4 f& A {; Y- d# e
回复

使用道具 举报

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

本版积分规则

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