中国网络渗透测试联盟

标题: Destoon cms前台getwebshell [打印本页]

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
+ k+ F. u* n3 }

0 I' ^* P& s3 d V0 H

" Q. n$ O4 j! B; h- G. U

9 o* d* c9 _, [- K9 A+ ^& T; U 前言6 ]1 }+ }( m' C" U7 N" ^/ s

+ o% T& y* X9 N- \/ K G. {

, p8 O! R) |7 ~$ t 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。2 l- {6 L! p3 L( K1 P

4 g! E& V: k; Z9 b" B

/ F* q1 ?" A6 Y" p# j2 c   # m, q4 s. c+ T" L, J

1 e- H' X H+ D' `

7 h+ w( I# v/ V* ]6 X: D2 A3 D 漏洞分析3 v% @3 H G& u; G; Z% Y

7 N9 Y: M* \, i% S! C- _

+ _# v- H4 ?; Q( J 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 4 b3 Z' y5 A" c

0 Z' }- k$ j: `3 t5 L1 M

) N; f6 l& _, w: Y# h' F. J! Q3 s6 K" N  # D3 O3 p3 S' l% Q6 [/ q* l

1 O7 E6 h4 K0 Y" N) |+ H) z

& }% j* J4 \# u; i& ]; H2 n 对应着avatar.inc.php代码如下:- }5 K" @/ y, ?8 F

& o- g0 ^+ g5 I7 n7 g

3 `. E1 e3 j K7 C% g2 m" I$ ] <?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) {/ H- D. ?: y3 K

6 V' v; ?6 o' T" p/ }

+ d6 [/ W3 ~* H     case 'upload':/ o% F4 l! O4 W

) p" w8 g/ M3 w: f2 W0 y. T; k

8 H* `! Y, D8 d) o% t q9 D         if(!$_FILES['file']['size']) {4 X( [4 b& H4 K, ~) b; C5 }

9 {# T3 `: }/ a8 l- A1 l: Y5 {

9 L Y' g' D, K. r0 E             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); : d/ z* g5 p1 w

4 m' ?0 C8 D% m9 ^

7 W- y8 L2 j/ Y# {! Z" C ]1 K             exit('{"error":1,"message":"Error FILE"}');) j8 k7 _# Z6 B9 v, l, U; ^

- i) n" @0 t9 k# e3 Z4 G

. u' O0 }: i/ U4 k2 h3 Z         } 6 r& U* v6 s) X! r/ K6 p" y

% {/ P3 C2 Q8 {. h# `) h

! t5 q2 V \2 ]5 N) i         require DT_ROOT.'/include/upload.class.php';- U% ]- m3 w1 g3 S. _0 I: S+ a

, d/ h, e: @8 y; _" O" p+ B4 I0 R

# K; y$ S$ Z. d! ^) { q   9 W1 s$ t6 F* o' L& \

: C6 U* M' S2 x* Z

* \6 d2 X' Q# x+ x( J4 z# v         $ext = file_ext($_FILES['file']['name']); 3 t* |) d% ~9 V. c X

" `: Q5 V, G! i2 x/ [: Z! D

* v9 ?/ Y2 G+ i& P6 }, U         $name = 'avatar'.$_userid.'.'.$ext;( }; a) v, o* ]) Y% f

* j3 K, D. ]+ C, w

; V- c$ r8 ^3 V) v9 N         $file = DT_ROOT.'/file/temp/'.$name;$ @+ t. R# _: b- F) N, C" y# u

3 U2 a; U% |% R7 }1 \3 S

: J/ K. [- t% \. I- ^1 n9 v  9 f5 W( _; W2 ~( J r1 `

( \( s; e/ _% g& [

" c* t/ k$ N' R" m         if(is_file($file)) file_del($file); ' v9 T( t, L9 o; w7 a

& g5 t: B% z* v- @7 F' _$ A

- ^5 d$ }7 K$ g) R- d V& o         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 1 T% ^1 }# H$ k/ y/ }

1 m/ O* P# I3 B! V3 ^

- V# l& T N" I0 o1 S4 K  # i' Q" Y0 @# k( l6 \5 J6 u

e- `7 i# e. \# Y" Q

& Z) @/ h, g* Q: |2 W- g. `7 R! q# G, X         $upload->adduserid = false; 4 R* Z8 @4 H! }! x) q3 _+ I" e

, [4 z" M+ q H1 w

6 v" t s4 Y' p( A9 P, {   " C) |$ L6 H: M, ~ a8 S, n

z% U2 W; }' R

4 Q) |. O0 W1 @         if($upload->save()) { + n* K% U0 i2 |- I- m9 N9 C9 |

+ w% V) Y j9 i: J/ c: m

" w' e) r" y# h+ F1 {             ... + y$ _) p& \- c# h- W/ p

2 D- T, W) R# e) c1 z# J

a, \4 i7 |# a+ w. z O4 P         } else {2 g3 M3 R8 h3 g/ O) E, i

3 E: o/ Y8 |3 m* u0 R; S

: A' O6 k% H$ v# W             ...; ^, k% ?3 R$ K; G$ x# H9 k

/ k% o+ }2 i8 B5 r+ x/ F% a

+ R# r8 c1 o' n2 F4 ^         } 1 x( ~. X9 z$ V! t* j

" }: G, p% F# W% b) ]

" K8 `# s& ?. N% G, i# p     break; 0 w6 T: r \/ l, Z5 Q A/ S

- J; `6 U9 ?& _, o

/ x8 j, X l# U: d1 F0 } 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 * L0 o8 i- P: p4 D9 U! B

. d8 j0 ~/ K" F0 i" ]) P. ?* V6 H

4 P6 @& y0 S1 q- W( p& j upload对象构造函数如下,include/upload.class.php:25: 6 F8 v, P) }$ }/ F, f6 ~* J

+ n* M: j& {. K2 h4 D' X# q( F# s

$ `( H( h" ]7 e& ]) X+ X6 E <?phpclass upload { 1 R5 J% B& j- S$ e% H6 r

7 O8 P3 Y$ g" p j, I" O8 ^

s5 E$ ^) p' t; S6 E9 m! s     function __construct($_file, $savepath, $savename = '', $fileformat = '') { # e0 J, P! S* A( d; {1 F

/ i8 x f8 P; u: k/ t

- o7 J' M% t: u I3 w* d1 b         global $DT, $_userid; ) ~" B4 T$ P* L

4 T( k! e1 B7 S" m

' o" r; ^. S" L0 |         foreach($_file as $file) {8 O8 B, `) A. k. m7 I

& E: X: g' P* y

3 C, ]1 K" ~' R             $this->file = $file['tmp_name']; ; p1 x! a1 n+ k# Y

" Z! J3 C, ~1 d, k' M6 ]

& T6 }) s. {$ e2 u+ U& T             $this->file_name = $file['name'];5 }5 Z/ ]0 M" R; P# W1 a

' {1 R9 m% r% m# Z1 ?. V

/ E6 K! Z( V7 M; q# x! L8 Z; _             $this->file_size = $file['size'];( r% p7 E( e& `# w

. m+ C& [ [9 I0 \

1 X7 v/ p7 q. R9 e             $this->file_type = $file['type']; / w# K# ] s. E6 M/ m( }( U

( c' L/ J5 w2 W3 d/ z" M

: w: E, ]/ Z; Y( I" V. ?             $this->file_error = $file['error']; ; O$ c2 I9 h4 V

5 P7 F, T( \: P0 l

5 _& w" G; O; ^9 j* S' A   5 t! V) _6 Q% C4 O* ^" L

# R% M% ?& ?4 ?5 F

! G7 d1 U$ X0 `: c. {         }7 k3 _( a8 F# t4 Z1 Z4 Y1 G

5 p1 j5 `' O" c- p0 L3 p5 h z% a

7 l! l' r6 ^& v, D5 P         $this->userid = $_userid; , k! z. F) c+ Y- g% |

# _' O' x9 f/ l8 j" |9 ~- D9 E1 l

$ q5 I6 K' O" n( O4 Z         $this->ext = file_ext($this->file_name);9 `4 H- {5 M' z- X

1 B) a g- j) c/ J1 ~

$ Q6 s( G- E' ~, w: \         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; ) N7 G7 g* v& v8 z

- L) V# `0 x. M+ O

6 U# F( C* j2 d" B3 k+ D         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;$ E" l, W' n6 a' V" g$ j- v6 x

" m( }/ _" `( o" Q# c+ Q8 r& }9 N. E

8 v- r* u% Z& C/ @1 i         $this->savepath = $savepath; 4 h) ^2 \6 U+ D" z, y) A, @5 e

4 f, o8 N- S$ Z

5 Q$ B7 f1 |+ c8 g# \         $this->savename = $savename; ) \! x, g$ h/ U1 n5 I

2 d0 p( z) k2 @1 o

9 t8 j9 w' {( D     }} , ]: [& s$ ~$ Y3 ? b

) u7 U( o, K8 S4 p

( w8 o9 s; w5 u 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 , S- K; o k9 ?5 D8 j& w

! k2 U% w% W1 R2 u; A

% K' j( p. ]" {1 A& i. h2 \& I( n( I 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 - @# `( e! Y' g5 T

2 g# G8 }& u" Y3 @

0 D; T9 P4 \# m7 Y6 m f4 [; a" _ $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" y# w( A3 J) C8 _7 W3 E! N

! m: A9 o, N9 C- E

5 o% w/ D, o8 H0 ? 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:8 l6 X/ k% z5 |7 _/ K

' l; l7 n" ~5 r6 s

6 o v0 {0 u: g; ?. R6 V  # N7 X y4 J$ C' r& K& e

' T, w. P! A8 `+ ^$ D1 U

* O3 L s8 `0 z7 ?2 R 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 8 ~; \& [7 @1 W

: u# \1 u6 m. H- u' K

& Q6 A) I( Z% W( q' A6 x <?phpclass upload { $ l; k% b: r( g& R" S" A

1 z f7 W" X6 K3 D$ Q

/ A3 A- U6 e! Z, }0 N3 H     function save() {# M' L- T( N2 o. \; m7 F

3 M2 m8 Y s8 v2 W/ m2 W) O* `

( u: i% E0 J# e: L, W8 v% P         include load('include.lang');) [+ f. u N/ ^5 d

0 H2 s+ f* { z: @* {/ }" Q

% n" ?% g+ z8 r1 r0 C         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 4 _# [' o5 ]/ _3 B4 C

7 h0 e) `1 I2 A4 k( a

& ^! t0 J: d3 _+ C/ l  / n6 h }# ?+ d1 g X

P" q; f1 C# j% M1 T

" X, s f& ^ a4 Z3 b9 p         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');. {! [" n+ C6 }6 p$ z a* N

7 V) y3 d) |& [' q' T

$ |6 _# E5 G# V  1 s, R8 Q% d! b" T& `9 H

5 x. w8 S8 `: Z

8 t/ q0 h$ N/ W/ w) d" Q         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ; U- _- z$ V/ ~- ]0 _" O) A

1 p1 P% [# @/ d5 A3 u' k

+ ~0 d& M% ~% h5 ~9 { l% j8 A  % M0 v" {1 r, T. a4 N5 y- z

7 f. M" m" }& m6 Y( c* M, f

0 ~$ D, L, D: R+ U: M         $this->set_savepath($this->savepath); / y2 b# ?8 c- `& n6 e( J# x: O

* \3 P& ^8 ]8 }3 {# N7 Y l

; t" N) h: e8 a         $this->set_savename($this->savename); - L# {! e: Q. T. g8 v* L; t; P

" ^% k8 V; T; U

* h% s, m0 L. A% ~& w  9 F* b; p, W5 c% {/ Z) M

+ @6 S# [3 X7 o

* m G9 { u* |( b; y         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" ]( p6 X5 m1 V3 w- h

* m4 Z7 \2 H4 `9 Y$ u/ C

. _# A5 K4 C: }' U' s) v         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); # E2 U% E+ U- c0 X- r7 e. l7 [

* ~9 Z2 Y" d! P( k6 ~* b

: S! r. g2 f3 c) o# t1 A         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);! n9 X& `- R3 f: B7 `

4 a v/ ?! z* |& P' i) w' R

' @! r8 D9 k) E: U7 X9 M  ( ^8 _5 k0 i! f9 i0 ?4 x- p6 W

. O K3 Z8 @+ O E- q1 r% p' V

) X6 F% Y" t. }* v7 a( f# @         $this->image = $this->is_image(); {& ~: ]: q$ k

7 p- s o4 e, m" J* @% U

7 k' S) T! i1 l/ F         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);0 _% B( _% r) w8 e5 z& {8 g

6 }% N& I) P, c

2 p. L- M8 j" f         return true;. I7 g6 t4 ^6 j; @+ l p

1 {: v2 O$ [- |# |* }% [

3 a. E$ }! e7 d     }}9 [* r; ^* d' l1 t9 o, e( o

+ l" X$ S% h! q+ X) q6 ?: c: n3 A

% J9 L* K2 Z& e5 j 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 5 V1 q# x+ _/ @7 F6 D6 P

. F3 y' Z1 ]) I, j6 m

$ s; S* o O1 K g- P1 A <?php _: M% W5 E, L

6 y$ f- I% Q: D4 M; S

* ^8 u- G, b' m' B! K1 w     function is_allow() { 3 |, `) \% @* n. a5 F1 J6 z

) Y" s* {# y$ l# H6 D

" O2 S* A: e2 [' V         if(!$this->fileformat) return false;8 Y2 {9 p% [# c) x

( |+ }% v- {2 j ^' ]' Z5 C- m' _

9 I* x. ^- Q' J0 \         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; , \; v; x8 w" p+ l; F

" Z4 V; O+ b0 i& ~

# Y/ U9 A6 P/ V3 q4 Y3 r+ g. y4 k         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;* a e$ f* C% N1 I4 D8 f

& b. P& \# O7 z6 O0 W" O, Z. O# k* A

8 w% R* G: @3 B, p7 m         return true; 5 r9 S1 j% K" B1 w1 e7 x

. U; M2 |4 g/ D

) J+ V; i7 x$ e3 L ^/ r- f" e) t     }' K+ N# q; z- D7 E: S

, X S2 E4 l/ o! C) w$ ] Z* T! \

, W: g: P9 ?6 T' ]4 V# b 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。& B( A8 S' ^! T4 U D) U: v3 E1 Q

. ^, u( f1 V0 I; f3 }: u; Q

# Z) S* p8 F% i& m2 E5 w: o- ] 接着会进行真正的保存。通过$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文件。 # Q4 d) |1 g- O1 [

) P) D& h a4 l& w6 i4 L# J

. V4 Y" r5 y) B2 ^# g 漏洞利用5 ~+ z c1 n. A8 n- Q, M5 }

! i U& Q& q8 x5 H! T3 U2 I. m

; f3 ?- W& L( m3 u2 y( }" g6 A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。# |% S9 ]* P7 N( z! h1 u

- S/ w2 _8 @6 q: } h

7 c# B1 Z4 k/ y1 C6 I ^& j   ( U q, B' w ]5 M" E% y- q

. b" o/ q6 A$ s' F- ~" ~

7 i j$ C6 P) | 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 6 i- V& p% Z% j M$ r* y

- m( k( X, _0 C1 ]$ s& q

- ^% W8 \$ C' c1 O1 e Y( k3 U 不过实际利用上会有一定的限制。 ' y2 J. I/ V- h8 W. K* W6 H

% Q5 M( H' i5 ~7 F- Y8 d

4 ]" J1 `4 W$ M- [ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 ' W! x. ?. B" B7 l, B! P

8 P" G# \. U* @6 R* @4 d9 G

6 D+ r, _4 x: m   7 y, h2 U0 X6 A1 ]: B$ t

$ S5 B, [1 a+ i2 L4 z8 |* F6 k3 C- L

2 q5 {4 V0 s7 ?" y, l$ N5 v0 M 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:" W9 o8 o- S, m6 L5 w! N

# j& C) _7 v! z

6 N$ E2 i/ [* Z( t 省略...$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]);省略... ; g/ Z6 B% Q6 s n

: A# l3 | c- L& j: A

' a) a" I. `0 I 因此要利用成功就需要条件竞争了。3 T/ w+ R0 S; s4 W- w4 o! w$ ?# g/ Y

$ K3 f6 o; U$ K" s

& `1 |' C: b1 W f( i 补丁分析. [( D) }' }2 Q, d

4 x6 u3 _5 _) D9 b2 u9 m' n6 I! X

9 q" c' N& g! Z. {9 ^2 {  : H9 O' H; Z2 c3 k3 p

8 |0 \+ D8 O4 e6 ~0 I. I: ]) a

& l" o' k9 h C8 W- I& [/ j 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 4 X7 ?- L: F- a1 P. w

9 K& d9 r6 @$ F% h: C# R

6 u9 t; g1 h; ~6 D# k: g2 ^ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}* Z: ?; @4 e1 I

- t! ^( {% `! P% F

4 U& _7 g- S$ z   p' e! x! ^% b

. H' E( I) d- W6 @. |6 I: ^+ i$ g

4 E1 ?: f& G3 H4 Y" }. r5 N5 c 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 H3 Z ]! W7 D7 x+ n

( r* W( F8 t9 l

( P1 S7 x2 U: H# c* v* \ 在is_allow()中增加对$this->savename的二次检查。 ! \. s% _1 ?0 f A1 m8 J

4 g9 a) d4 r2 N& s

+ v* i0 n% N+ R, f5 i6 \ 最后% ~ f+ }' v- g, P

+ V0 [) c8 l. ^* y

! R! X1 V- J6 ^, P 嘛,祝各位大师傅中秋快乐!- a9 \, L: S' s/ c+ C$ Z6 @4 Q8 e

( Y# Y8 D% H9 r: f

0 L- T1 V1 U9 _6 `* r   " J/ q' J# g# R

2 w& M. T+ ?% V2 q





欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) Powered by Discuz! X3.2