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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
" C L4 S6 v0 K1 m9 t; i1 ~ f- R

0 ?, i* b X2 c, x

( Y# Z; E5 h4 l

% I8 Y" O' \4 X$ M' J5 e 前言 ?; j% E8 ^% u+ Q! @

0 [$ W9 Z9 g' \3 }5 d

% j4 G. }/ O5 ~+ l2 J0 s 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。3 a- C0 d0 U1 S2 q# U0 r

" @' ]) W7 {! X ^' K! }

* @" v3 w7 S% z   / }+ h8 a! o- M

" V* h6 z) o M" G4 O0 p6 c

* B( L2 c* @* S/ S7 U 漏洞分析 . E$ Y4 d6 w( i5 x: L* `# U3 X

( N0 }9 ]- I. N! P3 p

% P4 z y! z2 M" S 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 2 {# a; L9 {* E% T0 l) r

' D+ C: w' y4 [. j

7 D$ Q1 g4 o, \ _  : W2 Z! L# d% j w8 W

J# G# w9 l% z6 ~

: z3 U x& V! ]5 ]: R# W 对应着avatar.inc.php代码如下:8 B3 @; r' h" a5 B3 }" e% X O+ r! ?

1 n6 I+ R C9 O' Q; C' ~) }

' |9 ~5 P6 W( ? a <?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) {$ s* j5 w0 f! r7 h# y0 S5 a7 q

; a$ l4 z" L" z: J8 D" d

5 ~; ~& b% O6 \/ ~     case 'upload': - w) u5 V' W/ A2 ?5 s( p, X+ F

3 V% w3 w$ E, u3 @- @6 U& Z

6 g! j- S( P0 [/ \         if(!$_FILES['file']['size']) { * L: b: T$ l7 b* j5 ~8 c( _

b/ `: [" v3 R- ^5 a) q

# y& g, p$ l1 U4 f             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);- M0 B6 D4 b ]# _

- `/ ]0 A2 k$ C4 }$ ~% ?9 A( ^

i4 U. D+ ~1 Y& w3 N6 M             exit('{"error":1,"message":"Error FILE"}');& j( t& c/ _, X' h: K$ v

8 p, x5 T/ T5 M a+ w4 N

" h/ A8 T+ V9 B7 `! X         }6 |" E: _; k/ |2 E" e: I$ Z9 m+ K& r

- w1 l0 l% `- j4 V

. {2 F# M5 V/ V! B4 r! h1 y6 b* `         require DT_ROOT.'/include/upload.class.php';! n9 K8 B" c# Q9 S0 }7 j$ G

" E: Y# ?% ^- B. g

& w( X! W* t& A$ w# l, c  1 C5 `: Q1 S* p& ?' B( ^5 h

+ |! I1 v$ Q) G/ d* L$ f6 ^4 C

, q2 T' B3 l# g, t# t1 }         $ext = file_ext($_FILES['file']['name']);' r) C) R% z9 F7 d0 }4 f. _' m5 l, m

2 T6 j# w7 A5 g8 {1 ?! [5 f

) c* D2 g( m! J9 z7 D8 [         $name = 'avatar'.$_userid.'.'.$ext; , Q" {2 s$ w* N0 j

. v/ E6 O" ^# d' k0 ^4 V9 \" P

1 m: o$ v8 B. u0 O! V2 z* m. X         $file = DT_ROOT.'/file/temp/'.$name; 9 o* @- u) U# q9 w- D7 `

0 y" e& Y% D* t, S1 X: |5 e6 M

( x; }/ M) O" z( p/ d6 C* a2 T5 r0 `0 P   5 o# U7 t* T7 N) ~' E: }7 s2 r3 @& Q

0 s$ f m8 ~3 F+ v% E- @

6 e* m$ G1 d! K2 Q         if(is_file($file)) file_del($file); 3 Y% {/ }" Z2 R4 d

6 F2 O3 B/ `9 ]7 h2 t& N' d: M

8 A4 [# h- Q% O         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); # e% t& x* F- K+ X$ @' w. n# U

1 X6 R3 D$ ~4 Q s- N& X- |

) h9 C! B' l$ u5 s* w: M   % a/ {9 a) z5 H1 \: c' Z

- \6 ~7 x, @. R$ m

8 {) r1 L" P" J( c         $upload->adduserid = false;8 `, u8 g0 M! S+ K

+ p9 |) h. i( m

0 j9 g0 n4 f X8 Y9 R) H! z  $ Q" f& J: Q+ d W" e6 y

7 y0 L* k7 t/ ` U7 j j( Z

# V& Y' |; l7 T) d         if($upload->save()) { 9 F9 g' @. W- Y/ E

$ Q) P7 y# {# b2 y; {

4 w/ _5 n/ v+ @; P. v( N, d             ..." u( L; u( G4 M" t, j1 T

# n# G2 w% R0 C1 e& s. l

# x' d& r( d' H! H) B         } else {: ?7 \- S* l) o' Z

4 c3 c: ^6 h K# X! D4 W

" ?3 g9 Y# f$ s, M             ... ( a0 O8 w9 C" Q0 ^& @6 n

% V1 ~& \ \% E$ r

+ G$ V% p/ t9 @4 h+ u         }: a* ], H. p$ w) x

' t. F' H |4 e% T2 {6 j ]

; r3 L3 ?- Y. x s     break;5 f9 P; ?( G/ @1 {, @% J; D# k

" ~) i- {: T" K0 a# I) E% I

# L2 f6 L8 b- E; m0 X7 `7 C# K2 G 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 / p& }: C- u4 D/ T

2 K7 c$ _; n+ k% u* ]( p

7 _% T$ v5 o& f% j4 ~9 |6 @+ W3 H upload对象构造函数如下,include/upload.class.php:25:9 u0 }2 v+ w4 v5 X9 T. L: w

8 L$ }# k7 c4 V" f* }# X% B

5 m3 t* |8 x* Z# T9 |" A* j <?phpclass upload { 7 l5 F# k& @: |, b' @* `" r" d

0 m6 B/ Q. e( g& d

* | r1 x9 j6 Q! O2 `- S# H- F     function __construct($_file, $savepath, $savename = '', $fileformat = '') { $ {6 l. [! ~) p; ^% z

1 c1 P) r- @+ B) ^7 P, O9 w

* n1 ?5 l( l9 T" k8 N6 N7 H9 h         global $DT, $_userid;. n" h2 X' t( r8 |

* a" h. Y* U5 `; c

5 T4 s5 O6 s; _* ~         foreach($_file as $file) {( T% |5 h6 B. C+ h/ P, y

9 v+ Q: Y \0 j% h+ u; }* n5 P$ l @9 R

, T9 y$ u& p w+ E* X& F: f             $this->file = $file['tmp_name'];( q Q. `. x& m! M. W% k8 c7 @+ O9 ~

0 Q) G8 p" ?- V+ r0 S; x- b+ T' r

% F& _" ?9 `7 s U# O8 ^             $this->file_name = $file['name'];- J$ R$ u4 b5 R' h# A4 b

; k+ o' N5 Q, J# x; v, A0 n

( }# K; e# p; T" ?7 u8 t! t. S             $this->file_size = $file['size']; % Y" Y9 t; K- }; ?3 l

J$ t6 R; N+ M E( k2 |( j* S7 `4 ?- Z

8 l3 N: j. O( T$ r/ }/ K3 e3 g/ ?             $this->file_type = $file['type'];( T3 @& X: F8 C& h

/ ]( a) ], ~1 T- l% G

- T3 B# O, j) i3 }( O7 x             $this->file_error = $file['error'];+ m9 ~' x# d n5 \

4 ?) k3 f9 y1 C( Q

6 |- ]6 a, w( u! x6 p% b   1 y' M5 [% R5 z" F& C7 a

, d/ H; D$ i4 c+ F

3 a) F" v6 z `) f7 ?. |- T0 T         }6 _# V' M$ a3 _) e8 L: ]8 Q

9 F/ j, A2 [' N: c! ]/ {

, S) ?0 d4 a6 y/ r         $this->userid = $_userid; W8 y/ v9 |* q3 U, w. t

f- K D3 D% R6 g$ F2 V- X) \

, M9 H7 x! m. s2 b         $this->ext = file_ext($this->file_name);, ~3 F/ g( A5 J! W% x5 E

9 z! M' q( A; l1 O' @

% V7 R$ O3 `4 K7 V7 T& O3 y( t* i         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];' V( M( ~8 y$ t# P8 M# ]" z

1 N# F, K, |1 Z5 w: g! K

9 _7 m6 h) ~! q         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 8 \1 s, o/ ^7 `! O

5 }5 B) I8 j8 U6 `

2 E0 r/ x1 I( w5 R, @         $this->savepath = $savepath;$ A' \( Z9 E% a2 n( O

, g! {1 f# I; z5 D3 L( }7 j5 G

5 l) F" Q; q. f. C+ Z4 O         $this->savename = $savename; 3 t* L$ o3 E) x' ~/ R4 F1 i

1 Q- o2 b1 b3 s+ W& y

+ T1 I) g' b/ ?1 ^4 W& P     }} % o) r ~7 K* Y9 x

0 G( V( p1 a3 l( v2 W3 g- B

3 a9 N% M* k4 [ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 , z% m5 o! e) ~# T& Y

# I2 {) J! [" J( [! [

& t' ^6 n+ q# _ 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 . }0 _5 N) ~6 s$ I0 H$ Y

! I5 A+ C" M% Y4 ~9 H. ~! b. b0 [

+ b/ K% o& p0 U3 U$ 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.php2 w% Y) h& c0 O3 ~' i0 C, ?

5 ^" c% L% d9 Q# j

9 T) E- ^, Q, F D3 O 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: ; D- {* \# \( A" i4 u

$ i$ h* k3 ^5 _$ N# K$ R& k

8 T2 _4 j/ S; P/ [* o  ( C2 z4 [1 H# N9 e: [" `

+ M( v5 Z% P# B, m* T. l

4 J5 C5 a( b: t. Y M P 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 2 d3 b( U4 C8 Y+ P0 ? S1 z0 Z

% _; ]% k J2 ~% I3 W

& O, j) Z4 ?$ z5 |! i S3 ? <?phpclass upload {! X' r- F+ r, F& @% y: \6 b% j! K: V' M

+ }1 a/ m) W* _, F

4 ^7 o/ Q7 S1 m6 ?$ I6 H1 L c8 S     function save() { 9 A% n. F2 C4 c1 J* K5 c' x. `

! j2 g& ~$ n9 Y O, ~+ p

6 R. F+ u L( E6 ~. P- P: B         include load('include.lang');. V: L, Y2 g$ F5 V; I. o+ v1 U

, m3 {/ ~: c7 t8 e

1 S. I" B |7 H4 i         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ( o P" e. W1 D; S2 I5 \: }: M' L( ?

% U0 V* H% e; C

8 r1 _0 M( i# C   5 I0 ^7 B. O, M8 u! m6 b

6 L8 z9 W4 z9 @9 q+ f0 r/ w8 l

0 P) O! Z# s* O9 k         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');) Q; p# P5 V, W- f; o

" L/ S! I3 J0 }0 ~7 q( ~( ]* v

5 C; i Y- K2 J; f3 f4 }   4 m4 v: r# {( I: v, }

: I/ D; p5 V1 G+ G7 O9 O4 s

6 f# y1 w+ ~8 r0 c" f9 g4 J         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ U) \3 T i$ o2 Q1 \8 X; A4 T

7 H m" M) ]; d0 ^ q

8 D: y, C& [7 A/ ]* b   8 E8 U5 E, c' T8 u& g

5 G4 B5 s4 }/ ]

^5 \3 Y- E/ S7 _3 e) |         $this->set_savepath($this->savepath);+ B7 L0 I1 E% q: K! U

% W! U$ M7 C: F$ U# d9 v

8 Z$ z' \- r. E7 R         $this->set_savename($this->savename); 7 R" s6 s" ]& i M/ s

( a; U3 C7 }7 G; |# L) h8 W% a& X

% z5 E8 B, @6 k0 e) I  + h1 b) M% `# e5 {

1 @+ o& ]5 \' C

. A: o. E' i6 v! f! g* H0 y+ @1 j         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);% t7 l- K" \2 e7 @

( |' e! f# c6 M( V

( X6 O0 {) p/ P* {- z% _6 c         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); $ g$ ?) v, |% a) g

' _ n0 f( X4 c( H. q- y& d

# J! Y% b# h6 j; J1 P         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); % M$ U, g) Q( W! @" `% u( t

/ w. ?& g4 u; Y# w8 H3 G( R

8 t% e- Y& K" X: K! W5 t) I  0 h5 j6 G0 R7 v6 o+ G8 G

; A @, c+ q; C( [ w( X

9 H% U3 r6 R# J9 Y* b         $this->image = $this->is_image();1 |, X- M* P# l& M

; \9 O* T$ t$ }

: v+ Z, `( S+ }! [         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);0 g3 s! s9 ?( A$ }4 s' g8 t2 u

h x# J* D3 o) q# ~) ^9 @5 J

$ }- j$ i( l2 [4 q& O         return true; . F% g# D7 N6 k) c! q% j3 K4 p

4 d3 m' v2 q, l+ N

$ y8 `8 v/ k& E; @5 {2 K( a1 B     }}2 c# _/ m! |; F7 |0 x

& A, ]. q6 G6 j" I, P

$ @6 |# n, z" b 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: $ y0 \, @0 ?* G3 w+ ], [

4 m R* @. }5 G q a) k+ r# R$ }6 {

: `7 c% V/ G' [' R6 V+ R4 ?% s <?php, D% V3 ?5 \+ b

- Z. ]# g- g5 \0 O# L( l

0 r, i: h- d) N2 }6 n" ^     function is_allow() {6 d# Q( w( [: q, ~& g! G

5 x! C* O/ \; p+ m7 w4 u

1 r A! {' v5 f9 `" C         if(!$this->fileformat) return false; ! o! S: ^! H! ^& \- y4 L3 {

4 r3 T4 y" t8 u7 {) [2 U, {

4 c/ ?0 I, w& g, f) N4 x8 _         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 6 T6 B+ y7 V4 n8 c8 Y" r/ B' U

1 G6 _2 \! v: C9 b

' n$ e) Z: u9 y1 t7 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; ' D/ l& @: D* s' z

+ ^2 G6 F% j/ H1 r

( `# g( C/ x, V% G         return true; $ T+ z& ~4 _1 D! H: P) l

) k2 y- g9 l4 n

, Y; F: z+ T5 o* ~     } : t+ S& I0 G% ^2 C( k" \4 ^

. k. X2 S# G( Y6 F- M( ?4 }

: O8 t i |) V" n2 X* | 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。6 X$ _& G- c: e" c

$ M1 S- n, S. o# R2 y% K% ]/ t/ b+ t

$ ^7 a, W. |8 M) W! ` 接着会进行真正的保存。通过$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文件。 + {, F5 u& y, J6 J% Y& C/ A9 S; E

* i: B. \8 H: I

0 j' C3 a7 Q2 ^ 漏洞利用: ~4 ~8 n" d' F4 h7 O

; B3 J, |$ `8 h/ G: c

9 d' b$ N8 D: Z& h1 c' s/ W 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 ! s4 J! @9 b7 k

. k1 @% `, {' |! z

) T) G+ K0 `" _" e, g3 E- G+ S   9 I% T y5 a% E6 \$ I8 W4 {

# Q* y9 K- }8 r) g u1 c& v

# L4 e1 q- T, T1 o3 ? 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid- ^' i9 R4 d7 }' R. z, E8 j

+ t' T& A4 v1 I2 A1 R5 c

a% Y3 u1 d7 d- V- e$ Z% X 不过实际利用上会有一定的限制。' N& O; O) O% V8 r& z

3 M' C: s" R% d" L" W

( Z' g6 Q2 L, `! s) h* I 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 / B5 v7 t+ {& }7 i

% B; k. U7 L i; ]! g# X- l6 T3 L6 G

2 j! t, J" A$ c+ j   3 z2 S$ g* e' E- }& }/ H

6 {" j6 y/ F! p' B E8 }, M

7 J" n, ~& E8 @' c/ K# O- I; @ E 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 4 M; ^9 M7 Y! D9 E8 A( L' M0 m

% ]! h6 M e# q

4 S/ d* B9 B' n! G. T$ @/ r: u( \ 省略...$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 d4 k" F3 ?6 h4 c5 N, `# v t5 `# D

7 i, N) o0 E R# a! d8 @

3 c2 w" n9 J, r* Z3 P) z; h 因此要利用成功就需要条件竞争了。 * y' D! d: H; n1 r4 m

, ]/ z; ]$ U6 h9 V

7 l; p5 v6 G: j- A; L 补丁分析 1 |# { R# E- u

2 V! E, r, [" m5 i1 d/ ~/ T; u* G* g

# S/ w: M6 T% j) v4 q# h  ( W* Q- X8 K" ? J

/ A2 {+ r' N% \$ q! m

8 y( q8 D- W T) L4 u8 U) _ 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 9 G n: Q* l* y* [9 _

; L+ }: k6 M. G- d8 d# `; w$ c

; E) L0 S- n& N& C' ?( t; t+ E function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} 7 x r+ Y; D- N, J Q+ d: }; a

# a: u! d4 G: g& Q

! E) t( x- s. U+ I; p! Z  ! [& k1 p8 d* G; y; f

) K6 E$ Z9 p9 \/ F

7 P# Q7 I; W! Z, z 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 % Q5 D8 K7 C2 [- f# |' k+ h% G

1 ]* Q v7 G6 m# B

4 [ g/ q Y% m+ |& Z3 t/ ? 在is_allow()中增加对$this->savename的二次检查。8 c) O2 \1 a7 J1 \5 v

* S7 Q+ G6 I" r/ C" A6 |

6 d x1 t! u' F* @; m( f- R 最后; B9 }. i* m* X! T# o5 C

3 b/ r; \, E: N" G( q

! P' }% Q ~, o 嘛,祝各位大师傅中秋快乐! 4 B+ M9 w, Z& [ z! y2 ^

5 C' Z, y0 P) S; G d

* f P# y7 @/ z$ w) r9 V  ) R0 [ {9 M4 U% ?7 \; I0 h2 _" c+ @6 d

& ^# }5 R r3 _6 q
回复

使用道具 举报

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

本版积分规则

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