找回密码
 立即注册
欢迎中测联盟老会员回家,1997年注册的域名
查看: 1323|回复: 0

Destoon cms前台getwebshell

[复制链接]
发表于 2018-10-20 20:13:12 | 显示全部楼层 |阅读模式
: i! Q0 `6 J% W7 @3 n

D3 z6 U7 d0 x' |$ `$ L% i

9 n* A i: }+ V+ ?# @' f: }" k

& O$ ?$ P, D, w# j6 d6 B 前言 6 q/ i1 t7 d% r' d3 n: S

~9 L9 I$ O; a& I

" V" E: e* X& I' U 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 + t1 A1 G% C X6 u! @( \

[4 g3 ]1 ]4 d( ]( J: X6 `- U

: d+ z2 }$ Q; x1 {   * z. J1 I8 R# `' U _, e9 C2 [# S

. h$ T& ?5 j! |# Y# \8 r! K* \: v

" `* |# {+ S7 E9 p* P! d; h4 m 漏洞分析 ; ] v$ R) ~3 n

: W/ i3 l t5 S

5 Q8 K" C2 l7 M2 {8 I% F 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:2 p* K B4 t2 t, i4 c

1 D% v* N% h2 Q" Y: M9 ]

, ?' {7 w. _' u3 P' i6 M   ; {" }3 {, E* e$ Z* c8 T9 x

2 p9 P) y: j p3 M$ \2 p

! M; }7 Y3 R# x: a' B |9 K 对应着avatar.inc.php代码如下:3 ]6 E- a- a) T) @

* t9 ^) B. v* K9 I

% B0 |" s; @3 k" t0 ]2 ] <?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) { ) p- I% S2 L9 p$ @6 S" u

& L! b9 @; w2 E1 S) F! b4 E0 I

, y+ S. r; u6 i/ M' I' b, e     case 'upload':4 T9 f5 F! T- z) p1 h

9 \+ Q+ ]: ?# h8 b0 Z @) U8 t. `

; l* a4 U: ^( z8 W9 M' t' p# a+ S         if(!$_FILES['file']['size']) { ! H4 N' t4 c: O2 a. Y& T

h+ ]7 e( U% w/ t& X$ g Z X

; n5 A( @1 K/ `1 ]: g' H             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);' p6 }- l r- q8 U: @

( f( g* h7 }3 T) _

2 s, O. Z( m* m$ U: m3 N8 B             exit('{"error":1,"message":"Error FILE"}');7 h! q) E$ f& [' n( h, p" p* s$ h

3 O& r8 D- ]9 c7 m- T

5 q* O( L5 p* ]         } 9 C8 K; M a( j( q# o7 X" q

1 j. M) m6 v6 S- n: A

# V) m5 E1 ?& V         require DT_ROOT.'/include/upload.class.php';( [* b' b3 e4 b+ m1 L4 u9 U

5 c- C6 I( G& K" D0 ]5 D+ \/ n

+ G7 m/ b: J" [& F   / u( R6 x k6 t9 P' e, p

w4 {0 C# M2 S) T

5 n" m Q& O& Y- _- C         $ext = file_ext($_FILES['file']['name']); + I# H: Q ?) c5 P, z

' R: O) Q# N- j# }

3 r" q) m) i& T$ Z: G i( s" @         $name = 'avatar'.$_userid.'.'.$ext; * {; d8 C9 X6 W" Y4 S. ~

% P9 L; a O2 f; ], {* O

" E! g0 h7 B4 Y! R+ Z0 [. k) t" u         $file = DT_ROOT.'/file/temp/'.$name; ; e& i. F+ n5 H

# a9 \8 E$ C6 Z4 Z) ]' @9 V8 b

2 P u/ [/ d3 J   ' r6 C$ {4 c9 t! \6 n. d# d7 c

1 W; |4 K8 [9 b

* p9 {2 v, h4 z         if(is_file($file)) file_del($file); . A# c3 o. _% Z. \

* [6 y8 e* F e1 Z M- G

( d9 j# g6 D$ F. I         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); + `2 X. u# z' J

+ z6 O, B5 z( B0 j/ q( R! ^

: G! C( x- F. s6 K5 i! Z5 E+ Q   1 v' u0 @2 Z2 }& v" y

- w3 v6 V/ [+ y9 K

4 L$ h9 @: y: W: f! r         $upload->adduserid = false; $ K/ Q+ }$ G2 l1 t) v5 w! E1 J

! g% X* t6 v$ Z" |% s

0 n- X4 u5 J6 T+ B2 _  * P$ F' A* a. K, F

, n9 o& b' f' O* Q1 [

: O( b, s0 ^! X8 n2 H/ |         if($upload->save()) {' M# W/ X# q P% ]* R% t

2 S) h" P2 X7 D. q1 T5 R6 k9 h5 x

6 k, ]: b$ v0 l6 `! v! }             ...; V5 q/ E( Y, q

3 M4 p0 X, W9 k) M

V& V6 @6 `2 n         } else { 5 H- f7 ~, j) k0 y* Z( Y+ d% }* [

5 q! e4 v4 a1 k5 m; T+ t2 X

2 n( g1 h [) B4 l) g5 l             ...7 q" J: u& u7 F3 X4 [

* k7 T0 r% E C% {9 o

3 w; C K; K! r! B) l: I         } $ \/ o3 K$ ?# e/ r y/ R

4 ]! f0 D( s' C7 ~0 D: X- E& T2 _

* \/ X( D I- u: U x( h( k' l     break; 3 ^2 G6 H# U$ t) v' s2 ]

' r% j8 |0 F) N

, ~- X7 M+ l G! }" d6 @ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。+ r, f, R' [7 N# ?( Y' x

' u& I [* }, Q. u1 I7 C

3 c, p- n: a* V: J0 v1 _' [3 u upload对象构造函数如下,include/upload.class.php:25: 8 @" d3 y& Q7 i @

5 ~, m% z& B/ X$ \! B3 C- [

8 H z9 M+ F* l, a <?phpclass upload {4 F" ?3 I" r& F$ b4 ]+ D7 Q

- V+ K7 w- f: _! z& [& L

& }' A! } S7 E# `8 e     function __construct($_file, $savepath, $savename = '', $fileformat = '') {3 T W8 c2 n& R d6 E; U

4 {, T7 C# V0 k" |- L: a

0 m# n# K( ?0 |0 \% L" h         global $DT, $_userid;$ X/ ~* d2 c3 K5 F- z9 V5 P

0 r* z; |5 `: K

0 \. E0 V# F P* r/ t         foreach($_file as $file) { 8 z) r2 w$ d$ h+ _0 c6 U

E. R) { p" S8 ~: z& ^

$ x* z- e0 A9 Q" v g5 a; z' O5 ]2 R             $this->file = $file['tmp_name']; * A% O: L v4 g" k. E8 G% I F! \, O

7 H/ v0 C3 l1 G/ n5 z

A" J1 i; g' t) w2 p             $this->file_name = $file['name'];. M5 z/ o' K/ G4 t; Z, C6 e/ Z7 _ B

5 v1 M" L1 k: V) C- {0 `5 N9 R( X4 y

: Y5 [& D) Z# j             $this->file_size = $file['size'];& t3 g# }- P' B! o4 L5 E

0 e, R& l% D# K% Y$ \

, ~0 i7 g4 [( Q% g& d             $this->file_type = $file['type']; + W: c& j, O. C& ~& M# t% t9 M

- q: x/ ?: ?! b2 w

; ~( l. d2 X" A ~- d: N             $this->file_error = $file['error']; 3 c4 ?4 H. w+ o) t

, r+ E6 `7 c( R$ S& s- q

$ V' `5 n4 D+ g# C( n& m' v3 g  " Z# B" H0 y x" F

' b, S# Y4 A( \+ B

4 W) A1 N9 ]" [7 C& y$ R! a         }0 [& H. u, N; c' A3 T) H- E4 _

7 a: Q9 B: c7 q* |

4 ^! ]. X! _# j3 p         $this->userid = $_userid;+ K% m( ]* e# m- }

7 z) m" @4 Q$ O4 _7 y! ?

, V. }4 m+ t# G3 ?$ I9 ~6 C" s         $this->ext = file_ext($this->file_name); 9 d6 k A) n4 z: k' Z# }2 d" ~

9 D# m4 [* O% y# K% G

, }6 x: [% \# x( {: S0 n( q% ?         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 9 o( z" C) I& X+ J' ?

' a$ Z& V0 P K7 I

# j1 ?6 P8 ]! w* X4 [% T8 {6 T         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; & u1 |% a' ^- C8 e4 X5 Y" u

6 @' o+ E; L" L* [/ P

9 J5 |+ O" T& Q: R/ T9 K         $this->savepath = $savepath;# x. ^3 c) d/ f; e# C

# ~3 ^1 [! }( e" Y$ W0 q6 k! W) o

% p6 i9 F W1 e$ H7 Z         $this->savename = $savename;8 a* o6 Z* o/ W! L. m4 D( E) g

7 |6 H: Y) V. o% x6 X: H8 I0 T% U

' _& @9 ?1 a" ^     }}) g% k9 a" k6 Z0 \9 r2 a" H, c, }; \

; J1 }9 a- ]* A4 k4 t+ } l: ]. ^

2 N4 G5 m/ r; r5 @2 G 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。' |. L( o+ s9 k8 O

* w0 B$ B3 |; V! E1 ?7 l$ L

( a7 E% j# q/ s4 j7 o9 j) G3 j 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 2 R: d3 T# N% _/ V

% Z2 L& d# R& h/ s( G. r: n

5 t8 ~* H: v5 Z $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 ) s7 I( ^" t% v6 Y

1 [- k+ b: ]4 C% a" e

) K& o- i1 Y' o7 z- r2 N 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: ! A* S9 K8 x. |/ a C

; \5 K4 F7 e* `5 S; h/ c

' c/ Z3 t! ]# \8 }; H+ z" W  * c9 c# X- S1 F; H) g% S- E7 {

( J; \1 p+ q% `7 i1 c* X& j; D

* L) R) u5 W4 X) C; ] 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:. [# M' Q) x, \0 ]5 M# y9 u

! s6 l7 X f) z0 P0 |- o3 {9 o

- b/ I. s) @( E; r" ~- Q <?phpclass upload { 7 d. T9 g6 Z+ j. }. i( m

6 {2 F9 j8 P+ d( I1 t

0 B# i2 e" C7 [' G     function save() {. y$ T0 u: N- O% A

3 P* j1 l+ \4 S

- k+ f* ~1 R1 q8 ^         include load('include.lang');: i; c7 Z$ `$ N. s: ~

1 L) r1 X5 D0 j# r. O$ M

' E+ L& C, i/ u% ]2 {9 k         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ` q: @1 l5 r; V$ E

/ s: e1 u+ V/ q7 z! s$ J" e

" [+ D. V& e% u  7 j+ D h7 z; U6 L4 h7 j

W1 |( m6 K% }4 \

+ k2 b7 c3 I5 H1 a" P0 H9 M6 z         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 2 q" t9 R) z+ @3 o7 p! T

/ ]8 u7 U! F% N

3 V0 m( ?! I3 ~7 d! J) `  3 A/ P* O; g2 C* v0 u

/ F. f) r' v4 E9 x0 [3 `$ X

) v( y% J) ^& d! \         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);% _8 D1 p" x* G5 E) k

- l: ~; F5 \# a( G& C9 q% @' f

+ e5 V* `. i/ R0 e! \( e- v  ( k6 \( x3 O( b- q6 {( k. Z1 ~8 v" q

0 C; c; B. F' N& V

9 n! V8 L$ E9 C6 c$ ~         $this->set_savepath($this->savepath);5 U0 t B$ c# w# |* s. |3 H1 }

" P6 a6 y5 o2 E/ ~4 v8 U3 z r

0 W6 t% _* P- N: J& y- q         $this->set_savename($this->savename); 3 [! l% R% A) B& Q: J7 }5 y- P

5 V$ V+ F4 d' `, j: P5 G% ~

! r$ D3 ^7 a+ v5 {2 _& h5 d  : [9 _- c8 c% p- D

3 N/ O0 P# V* Y) {( b

8 Q& F/ _- z0 X         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); K- |: Q: l. e- n( f7 }* I4 |6 Z% @

+ p3 Q; [! D! L$ n+ `9 O# F

* [3 O! b+ _0 b' H$ G         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);6 f; ?, @# r! v+ w% U" r

( M( H2 l, s& F

$ \* L: K3 P" v6 W# ?) F         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); L i( C5 A' ~5 z

! h" f+ ]. }% l- A: W; t* A

/ k+ B4 s, W2 k! @1 d0 f# d) \   0 n) m3 F* o O' I8 G* O& y& B3 P

. K5 [& f4 ?& R: f; x6 }

" q6 [+ I, g, |# K         $this->image = $this->is_image(); 3 E) }" t7 M6 k% T2 i

' F# A! O- C t3 E

# o2 H0 N9 r) s, B6 M a         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 0 y, N: S: k! y) l* _% ?

% U1 z5 P& H; y7 q3 I

( O5 K3 d- f, X4 y$ e3 q+ y         return true;+ h* l& H0 C9 _! {+ E

! D8 b4 G1 U5 b6 s% d( G% n' m

7 b! C, E+ v7 R     }}2 A W" g$ `8 I7 o0 }$ M( w5 a

- I2 S2 z" h# q- Z

" i0 @. p- s- n. a, m 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:. a) m2 N) @( F

& i' @/ Y! l7 b- G- H Y$ y0 |1 R

" i2 { r0 h2 K4 L" z <?php Q+ m# `; n# t8 i8 R, ~

- o1 j$ i. Z; |! o( n5 m

: x, m# Z, O2 q) e     function is_allow() {0 r. J& `- d& s u

- A/ y. ]9 i9 o7 c7 g- P' x2 Y

$ P/ ]( r" ^) w         if(!$this->fileformat) return false; ! {6 @) e4 E# Y9 S# i7 _/ b) `0 w

2 m+ y# R- {$ X

, d' ?7 x! ^( {         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; * S; }7 ~' i& a# N

9 ^+ y2 V) a6 V4 h

- N! O8 M6 P; @ ^& I; |& Z! n4 S         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; 2 v& r" y1 n# v4 {

. ~ a' n/ a$ Q9 \4 m

4 n7 X1 U* a: L4 D! q2 _         return true; - y" D2 g1 o9 F- K( o

/ T# {. N$ h: I

& z7 F. E0 ^6 Q' j     }! i1 S( \' X5 t8 S+ p! {, d

6 @0 J5 z$ D5 t/ I* h' ]; K/ Y

0 l; _! d: v4 K 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 ) r* A: o5 z8 H2 l% f- Y

6 C$ ^* N7 e# q4 l

- M: o+ a( w2 |6 ~ 接着会进行真正的保存。通过$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文件。 7 C' g6 R% b2 J" ] Z

( S0 A( y/ H- @+ s' {" k2 H! W

- f% k4 {" z1 c* R 漏洞利用 2 Y0 K0 G% x1 X: I

1 J) j- k/ B' |6 `' [

( R) k3 J2 ] t; \* V$ e# c6 I 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 6 S( w4 Z- e1 e1 S- N+ Y" u2 G

. j; _8 P2 y& v9 Z1 R) x7 _6 L

* l$ m8 k+ X) a* Y' j$ |   ( }) g/ j* h5 K0 g$ A8 D7 b

" x. {/ }; F- D6 }8 _3 H

9 T8 ~) Z! m/ y5 ?) H8 E 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid X, l- _* b5 \- b% }3 x z$ g. `

* ]# l5 n9 [# J' n6 z

2 I6 w k0 v! }- Q. g5 h7 Y( {3 Q1 m 不过实际利用上会有一定的限制。 + c! }9 ~1 e; g

: ~) u7 [' |, i4 }1 `8 ~* q

% B8 F0 |) B& A( f0 E 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 8 f L7 y3 u. L1 |$ i

: Y. u/ R, C; j# ?" ]

% N/ @6 G6 U: ?! d) ~, O u  # ^3 l+ G1 n& H: i

. t2 l+ `" E! o* K9 Y

" F1 d L1 Q4 z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 3 |: e" C/ w7 s: {7 F+ c

$ u5 R! c. a8 ~

$ P2 v9 {& T. s1 I4 z 省略...$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]);省略.... o: S- r5 ^1 }

+ C8 ^: j7 ?3 j/ u3 y3 w+ J

( m+ W" X- \8 n5 m 因此要利用成功就需要条件竞争了。 3 U0 N) Z* g6 r; H

( Q2 X# o) I6 n) J( c

5 o8 J8 E) Z/ j# Y+ S _8 x, i 补丁分析$ m, `/ T1 j& R* u

+ t, {- W) X3 F0 Y J3 w

4 l7 L- e( i( g  + P# n, N: M3 Y1 ?

* P0 t# P- Q" }& B! W5 W. T

8 }- {; D& Q m- n8 W6 u 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: ' `, H2 u, S6 ?9 g G

# N* @; F6 V8 x) X# E4 A( h {

% U% {; e1 R8 Y, x function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ' {- ?- B, Z0 F5 B: F: j5 {$ H8 J

p) ^4 a4 ~7 l. i7 x3 m5 q% r, V, d

% `) V8 ]! O' U" F! w: l  6 \7 S) L0 t1 e* ~9 B

) r$ T0 r; W: s+ D

9 T5 b; Q) B3 U! L# M 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 4 ~. w2 h5 ~3 K2 Z. T3 Y- ^

( q0 \9 C7 O- r2 u1 q, M. D+ f

% F3 r+ o9 o2 c: ~ 在is_allow()中增加对$this->savename的二次检查。 ) Y: j6 w9 P( E" f3 R2 _" L8 }

: T0 r( @" t/ _& A( ?/ w

. N( n# N. B% a2 c/ Z 最后7 ]/ T$ _7 [5 Q" x

Y2 z2 i9 ~8 L) g

n, z3 c) L* ?4 k 嘛,祝各位大师傅中秋快乐!, ~/ E$ S4 p: m2 T* {. D6 V

+ m5 O/ A7 L6 Z

* A( r j; |" g3 |   . m0 a |, O9 ~4 M: |

' j) n1 B) q& |* v
回复

使用道具 举报

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

本版积分规则

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