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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
! W! H' A5 U6 D. i- H

@7 T: l3 K5 P6 Q

) g ]* u; z" z

3 i* t+ Z6 D1 l8 D: n) n 前言. f3 L; _3 |# r! ~ @0 P

. [& }# C' Y( R+ n" @, P

7 y& C) `5 K) L 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。. d- a; H" B% a* M5 ~# i; t

- n% g3 x4 F; ]6 S/ ?5 g

# F7 I4 |' H" f4 O; B+ F   ( `2 K5 k. ^. z9 O8 E& D

. V. x b T# J4 ~! h6 ]* h* A% S

6 j# F9 M' z7 Q! W* C 漏洞分析9 _# j( G% ?" ?8 h: }& f. l6 x

1 f+ k( W& ?, u9 A( x$ [* b

' r6 ]% R7 C( A 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:( b. c4 M! [& [1 N/ I/ B$ D( D5 d

: E+ P- M3 M$ [, H2 ?1 v) T& m# k

8 b* K& E P9 P  9 h4 }: h) j$ X% t' H! d0 J/ v

- [# m; ~) ^& G/ X1 F

' a7 y* z. l# E" I: o 对应着avatar.inc.php代码如下:+ v4 e* a" }. p9 ]

" j8 r1 O" U+ X+ U$ I

! y% T9 p- \9 t9 h# v$ c7 e0 t <?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) { 2 c! p* ^" ?* z/ L( \3 S

4 Y* l6 {' I t" r. F9 {8 t& k

5 h$ e3 A; Z3 S- B     case 'upload':9 C! j. G' m, a. i- U

* O1 @1 S: V/ B" V1 _( q. @! X

: K0 O5 V9 T% D4 O/ o* L2 o4 Z; ~3 J         if(!$_FILES['file']['size']) {6 _5 Q# U1 h. j% R S4 V. M6 ~

- c, ^3 C) R; i) `

& u) ~& \; c6 ^             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 8 J& c3 `0 @; Z+ b# z. g

; Q: x3 I, d& v% i; s; K

/ r# l7 G1 Y, K             exit('{"error":1,"message":"Error FILE"}');: y2 [8 n" x$ x1 ^

8 }# Z6 I8 ~* W

) N: Y% `; _+ y+ ?+ J         } 3 ~5 S" \: ~2 f; y

7 F+ k6 d9 d, A

( Y7 l, u6 G+ k# }+ A6 L3 g0 k         require DT_ROOT.'/include/upload.class.php';$ Y( R6 r$ Y+ ^1 L8 ^/ V3 ?1 c

4 k' E# M/ D$ O; I& I/ N

' A1 G* t% g( [/ n& }0 r& V2 {( v   0 W3 M! q' b5 G( Q! |% t

, t% w1 s$ t1 u B5 X

. Z; L. w: l/ ]         $ext = file_ext($_FILES['file']['name']); : |5 O! s4 c+ T9 d

4 f; I1 {& U/ ~0 e* k- ]/ M0 ` e% L

! |- _2 K* H" L* ?) d3 ^         $name = 'avatar'.$_userid.'.'.$ext; ' ^% g7 W5 b# O: k3 b2 j

; F( o5 V) c) |% `+ P: V

1 o) J. b' m7 P& A         $file = DT_ROOT.'/file/temp/'.$name;9 d4 X3 M, b0 {) o+ C, ^

0 U9 [" e. ^6 C& L& ]

# r+ h! p" [+ T$ G, S; l% X4 F5 A   7 `# R }6 s" Y% @+ r

7 C. |7 s! a7 j8 V M6 q

. j: n! x: ]. H; R         if(is_file($file)) file_del($file);5 y Z* S7 g( J- f5 i( W2 Q

; \* F4 k, M: @" G" @9 r

* N0 t) C$ ]( w9 K$ n# P         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');6 ~, A% ~+ {3 e- u: C4 H

) R2 f! g2 D0 k7 Q+ V" \7 G

G8 b+ A3 o: j; |. Y   1 ^0 m2 b6 y+ w. Y+ e$ J* O6 I' s4 ]

& D, e1 i0 z: U! s8 T1 \& a9 g I

) P5 P( X1 ~4 g         $upload->adduserid = false;% k& Q4 F$ o4 O

4 w' P% B) k" Q2 b

* N5 }! h2 V6 J2 ~8 c" t5 ?   / u0 u4 t: N4 t' @4 W

I* m! j- S" r& ]+ i) B

! B+ [1 @$ V& g/ u1 g: n         if($upload->save()) {+ L I2 s7 a) N, G" a( U6 ]: V

- x3 ]( h: _. K# A% N! @

2 F5 }8 x K' z& g* F             ... 2 t, ^+ ]( M; u Z& Q* K. M

: K9 J. R/ R; ~

8 d8 k5 w8 L5 j s, q& Q         } else { 6 M$ ~+ D; z* y0 i5 _+ W; F

3 K5 d+ j9 q% e" U

3 k; w: V9 n2 S# c+ Z* ]6 w+ {             ...5 K* g, A' @6 n

5 i4 m0 A! w6 C# _( }/ s1 V# ?

2 i8 G% G& f3 W( ~. T! _3 P; ?         }; G9 d. b# b/ C9 F; J# P

; f5 t7 o: W: P1 z1 i

1 R# c' e/ [- w+ K     break;# Z6 d( z( ] ~, U# P( @

4 R' E1 z" z1 ?9 H5 F% Y2 x" r, G7 c

8 L7 j( A% l3 S, j. { 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ' I( m q- E! ]) v3 g' D( s9 W+ z

7 {% ~- R3 g) U+ y+ v/ }, r8 `* J

8 ~! I* K/ P( I ^- | upload对象构造函数如下,include/upload.class.php:25: ) {% ]" ~ `4 ]# d1 ^

5 ^: S" o9 h/ s

% K* L7 G% o- @ <?phpclass upload { 0 v. k9 D! C" m+ v- g2 D

/ |( K; X+ K7 C

. W0 r/ b4 g& S+ \' }, k( l     function __construct($_file, $savepath, $savename = '', $fileformat = '') {" d8 C. j% z, e. w; z* a$ [( w: Y6 i

. d9 ~# q5 i* X f2 L3 t2 ]4 b

! P' W. j& K+ d( j         global $DT, $_userid; # s7 m/ {" ]. u1 y* V

' M4 g4 R# g1 I, R: p- [- Z& e

( _) x( Z' y$ W7 l1 E; L; B         foreach($_file as $file) {8 Q9 {, j8 w8 ~ J/ z5 ^4 C4 p

' u9 M2 ^: R% X' r T6 E6 s

* V7 y" j S" W C& J3 V C             $this->file = $file['tmp_name']; * f) t" s0 W: \' k

' ?# ~$ f& u1 S& @% m( ~

7 ~1 d$ I6 `; h4 Z% S5 ~) d) F             $this->file_name = $file['name'];0 I, J! t; Q W

% o% A7 X: W' k& O4 W2 e( Z

+ Z5 `0 _7 D+ H' h             $this->file_size = $file['size']; ! t2 B" b/ e% y# q1 F" _# S, k

& A( h& G( B( t: v+ ~& @/ m

" c2 o6 Y# X6 A V ^4 {( _             $this->file_type = $file['type']; ! U$ T) A' Z" {+ G5 u6 _$ b1 c

3 R& C+ {# @+ L8 ^# }& _% M7 a( t7 ]

) _ C4 @( i6 t% {5 @             $this->file_error = $file['error']; * K, U/ g# }- k2 Y

8 l a/ P) T' }* v3 I

+ _- |1 ^- h, t   5 u5 ]; N: [1 G5 X' d

* `% W/ z6 \5 W, f

( D4 `# O {. z         } ' p$ ?* y% l1 B6 B

/ R+ L" @5 Y5 T

6 ~5 d$ q# j/ [% @1 c( W! l         $this->userid = $_userid; . v( y9 P% | p& U! o6 D

7 ?! P. b) H- U/ m/ o

+ D8 ?# k7 f: _         $this->ext = file_ext($this->file_name); # e" B6 a3 p" ?/ ~( b! R5 D5 U

e: Z/ L' b8 q8 l

/ s, d3 v( H, ?6 M$ x5 k         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; u* H0 [7 R/ ?( X% ?% T0 M3 ^

0 v1 ]$ a ~$ k' t- l% i6 D

# t/ N# n3 F. o$ `7 N( ?         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;6 G1 h& @8 L8 @ ~1 F

+ ^3 n6 m7 [, K( L+ V

' N" e& g* p% x. p7 `3 K         $this->savepath = $savepath;6 ~0 U/ j) Z8 M# \' Z

$ _& H# i" d$ u4 Z; x5 R

8 Y4 M5 A; ]5 |$ K; Z" J         $this->savename = $savename; 6 z7 D7 |% h+ M' L7 y

0 f" l1 ]* @: U& O+ z+ D5 Q7 \; c. e

, l) C2 e$ r( w3 W- ?     }} 0 I! C8 t+ V# z! f& X& `

& G- x4 M/ v9 t* E* V$ h

7 R. F0 E r% \( D! @ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 6 A5 A+ j/ V: l6 @$ V1 K3 [# D

J: \$ R2 f* O4 M0 G' \' T

# T6 [1 S+ m2 Q9 u/ Y( m% U 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 # F. b/ f7 m/ }/ a9 h/ t! T" U+ I

% p: w" ?: p `4 E

7 A: A1 t5 r4 U% S/ s) B0 M. S $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 * P. p3 `1 Z: E) r3 d" X

5 t) M( V7 F& K, i

; d; i6 p- ]$ L! L) w8 }6 y 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:; O8 j$ W9 ]( U q

4 b" W. o+ k- A' k4 ^

8 A0 J3 Q5 J7 J# A8 [9 s* O   * j3 a0 o0 e5 L( a& a: ~

: F3 v, w. h! V* i

% Z) T- e. G, t; A+ W6 e 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: # F A2 N# U# p4 `

4 l& M6 `$ n6 y+ ?, C

8 j I( m" t9 e! t7 ^& {' p <?phpclass upload {" R& }" m: H) h1 a6 i3 H1 d/ i( J

: w$ t7 M: t, G5 O

, D# `4 f. ^. h0 H) j3 H9 {     function save() { $ Q- d" |+ B' r, z0 q0 }. k: d" b3 t

# S/ ?) R N0 f2 c

2 Z9 \) U, O' ?6 w1 D' e( M# M         include load('include.lang');5 {5 {9 t- p5 O

; B5 M1 [$ D2 a+ C; C% v i* K# B

8 m- K' i) v* C v) o! g/ ^/ E' H         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 4 Y- c8 [7 t6 a b! U2 n

" N' o3 M, W6 P6 ?. [: {: s

* P7 ^* O1 @! x' b% ^6 y6 @: `   / ]: M0 g7 N z. K

. M- P9 i5 {; s9 P+ u# f3 h2 I- @

9 _, Y6 Y/ g( O v! p         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');0 W" [ Q8 T' X7 ~/ e

& \+ `1 d. S, b6 `

Y) W$ T' v. K! t& B! {   + Y, F* \; j4 T2 B/ y0 |0 x

$ e$ |9 W% c6 l

8 Z2 ^! W! C% s# Q8 C         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); * F% N- T6 @9 G) P9 u

1 [" m, p. k" U( Y

) e4 m% @! f, R6 e- f) S3 }  8 e1 i" T/ ^+ y% u6 }/ E

' D2 ^) p/ \2 U6 W# q

1 Q6 H9 S: ~* k' s         $this->set_savepath($this->savepath); ! W" a. g6 e7 V; j1 k' R9 d% _

. D/ v: j3 n& b& T! x& @

. _8 y$ s# Y9 o8 v& E4 {6 Y         $this->set_savename($this->savename); 8 v# {' }6 F' s5 D6 \4 ?; E

; G* f& @) ~7 Y/ R

5 C+ X9 ~, R3 V+ r   - n: J" w {* V( l

* h. m# u$ W V5 ^

. W5 _9 W# M" n3 o3 f         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" s7 }5 z# @0 l- h2 Q- X) a, Y

! V" O- s& k3 v& a

6 y7 `, p `- D* L* M/ K8 Y% s         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);# X1 c a' H0 T( X& q) \

5 j* X' D6 `6 {& d0 d; p1 I

% c& R& l( b" Y* t1 d+ e         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); ' F1 D- A- N7 w; m l4 P0 G

" \* q' N$ F3 t( v2 `

& T2 ], a* Y9 u) {3 I& `5 v7 S   n- a$ w( Z2 q! x2 n

9 D) n" T$ n+ c' z8 V! j

]( A1 d4 @1 W( o. S. c6 o         $this->image = $this->is_image();4 ?4 z$ ^3 D) y$ n1 X! P5 p

9 @, I G( W6 P' ]

' H; J) v1 \' }7 g+ w2 n8 D         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);# h. q8 ~; z' L' ^) f1 W

: H+ ?# Y; J$ ^/ R

8 p. o1 p6 y& o, m9 ]2 Y8 g         return true;8 a9 A0 g# J0 |, \6 Q: I6 A3 Q

z4 K& e2 h6 f# k$ ~

0 A* U0 X; A3 n0 N& Z, ]     }} ! O0 {- c- y/ i/ A: J( r( I

( p7 V c, {. i( T7 |

8 B6 O* W2 z7 \ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:1 C* _. r9 s, T& A

# I& g4 ]8 C, w" g

) i6 O* `- H, E e9 { <?php 6 t0 w3 M4 @% E1 D% r

! ?, `" \2 F) k9 @

, S! n4 l+ i% s$ Z     function is_allow() {, H5 }9 `) }; |' P" d; X' c+ n# x

" s D) ^9 {. z- b; k* A

% } L, |( j5 q$ b) P K' O* A         if(!$this->fileformat) return false;! Z- G* r5 Q9 Z, O7 Z

; I- {: [$ K9 v

7 ~4 n2 o" c( P* Z         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; $ U- P* v# T- O: U S

/ E; n6 U n9 n/ d

' O3 M3 F9 q% r0 X         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" g. l* M" n8 w2 r* h

E+ w1 J# f! C L5 ~* W! Q. ~

8 O" j) F9 t8 L+ _' a1 |         return true;" k d% P+ f" g6 ^/ Z' `

, Y& }! N* M8 R; t+ @

" c) {& {! ]! a5 t9 _( N! \     }7 R$ c; }$ ?+ l$ {6 l H

2 ~" `3 N _( o* ? h

h% b5 Q$ s \+ G) K5 w 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。4 f: @, F& R7 ^1 d" m) ^5 k8 r

6 |$ [, z. M( @- B0 o( J

: r* B0 T/ |7 F, @1 b5 c 接着会进行真正的保存。通过$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文件。+ r0 N2 X9 g1 S+ c

* i) P8 p2 J5 N( T8 ~

+ F7 N( ~ K& i, A! E( G2 Y 漏洞利用 4 I- k) N- L. F7 i) Z D

; l( t n# B7 V6 V4 w1 J5 c5 R

! o, C+ z. ]. m) g: k 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。- f/ F" V0 ?) a& l, f

5 @4 B% h9 M U2 E$ y

* t" [5 B) }, e& K# F   r' P3 Q4 \ [) ~5 Y b

+ W0 R a, R; | c5 K

5 }2 R }2 f: A. U! h+ o0 u1 R 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid / M, S4 S9 D" ~5 t# \

: o. j1 w* {: F R4 i! x

& R6 `# o8 i5 U- z+ S. e/ G 不过实际利用上会有一定的限制。( \! X' L& O) d* z% {. M

- s" ^! ^- }# K, V, N0 b

% J6 G# ^* {" a3 V' `7 X' B' B 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 ^& D+ X/ m: j: q$ J; G

- _& P D& W! S$ `" }, N! C* C

1 F' z7 O) B3 w, O4 C, U5 Y- R* N' H  ( s! x4 T3 N4 X; U

$ b0 u# R! J3 @2 B" x

3 B* U1 S% t* c 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ! J9 }" p/ P# r1 |$ H+ r) U

6 D( A3 a1 s% U

' l7 H4 @* M, |+ V 省略...$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]);省略... 8 Y+ Z& V$ }, V' V

) `; b* X4 H; N1 Y8 L' V! A

0 L& k: h3 ?0 e @1 e 因此要利用成功就需要条件竞争了。 ! `8 e/ p+ i) p9 {$ B- y! F

$ K- H! k& f( A2 I

4 E* }5 H+ G8 ^: X6 {5 ~8 E5 N. e5 B 补丁分析 4 `# ?4 a6 ]8 D b* F. r3 _9 V' g

1 ]% _ b* C2 C- S; t2 e

+ K% o! P5 n, j% o9 Q+ C  9 C- w' N% l. K* _, w2 k3 ]% N

" ^# U. L1 A |: x9 l; [; e$ w8 p5 {

; P6 W: _* T8 c$ {. t! j 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: % q4 i! p0 \) X9 f* r: b7 _4 |

`; h+ O: w, U0 v+ I5 a

( Y. H; o0 J$ A% q; F# h# n function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}* }# c& u& m7 \1 |# C! N8 O

0 ]/ Y6 b# h+ H1 R, [

{( C' {8 \$ G! k8 E1 G# U4 S) H   0 C4 d$ ]: X: W

8 W- y, n: I7 o" j+ {' E$ D! X: w

9 T S" n( D N 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 2 v2 Y* s$ E5 T4 C' S* b

& _2 j% e; L& B& T# G" O

1 H6 j- `# S0 R$ G0 R b5 E 在is_allow()中增加对$this->savename的二次检查。% j8 b' g" u% g/ D

6 [) g8 n; ?: f B2 p

( w& T3 O* h* F$ A* h0 L- ]0 } 最后 # T: k, h& O) \: c

% x; }* t& G4 K5 S& b

% v5 [% R2 ^6 K- T4 k" S 嘛,祝各位大师傅中秋快乐!1 b" D2 m5 G5 e- T/ c$ K5 p

- Y- }1 c2 s) h5 x. W

8 y( ?" X$ Q9 z. L  + V: ^9 u6 q. x/ E2 L

9 }& R3 D# ]) B+ _+ W
回复

使用道具 举报

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

本版积分规则

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