中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
+ q- m' T5 T! w( t

4 R( C6 f3 x5 K1 F, v2 e+ J1 R

9 ^! O2 b2 ], C) F5 a

R8 ^- u% |, c 前言 : r5 g3 z1 ^2 B! D6 a* P

$ @' S/ z8 ] r1 p6 B9 c

: T' Y D( }1 H( h* \5 U 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ! U$ h8 k. ^6 N; M8 j( }

' c' M2 Q- V! ^( P: k

: x, I2 `% I+ ?2 u! ~2 r9 R- @  ) p/ C8 O" I0 z* i8 H% m2 K/ [* }

; M( I9 q% I7 R; ^! q/ G8 P: ]

3 b2 l' w( D* p 漏洞分析: `: j0 q. z1 L* Q2 t

$ x% [2 M8 j0 K# X+ U

( A; T8 s- V6 ^- k& ^5 L* C 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: X" i5 f/ X/ O' a: t

7 t) f, g# O% w& M+ `4 G% Q

( i2 _5 s6 H, `( R) `   7 U$ P1 c q" s) G) g! Y0 K) t9 j

& e) h0 w- E* I) w3 b* T9 C

2 \! o4 K- m% x3 I/ f 对应着avatar.inc.php代码如下:; k# F& Y/ ?) ^4 r7 g/ j

% J; t. H4 p6 p' X; u4 |

2 ~: J9 n+ A. b1 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) {, |! u$ J' ~: ?. U$ Y! l' t

* }) y* c! Q: W

{. D: S. j9 S% `. e5 j7 i     case 'upload':: N0 k7 Z0 p: I# y! Z0 J

( ?9 ?* Y! o& E) o* S# Q

1 ?! R" O& x, y i4 z" O# _         if(!$_FILES['file']['size']) {; X+ W0 X# k7 g& Q8 u/ ~

. Q. t8 E. F+ Y0 O- ~1 I

0 y+ E6 V! w/ u0 K2 k+ X% w             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);' k% E/ M& I+ G3 S) U

" B, Z$ N1 H8 } h) p2 d

+ M X* g4 k4 h1 I3 H0 c) v: f             exit('{"error":1,"message":"Error FILE"}');5 S" C* E* u2 g& g# z- a! a

8 O7 m8 U0 Y$ p

`" E* U1 g1 ?; k9 `, A& ]0 v1 h         }$ B6 M5 t( ^+ C! q- i% \% i( T( Z

8 C- L- f4 `, T# c

- y7 k% f4 [) h6 ?: @9 E* p5 g         require DT_ROOT.'/include/upload.class.php'; ; P6 [# N/ X9 @

5 f, l4 u4 m1 U8 x3 b9 L

2 h5 M4 q! ~3 b4 o$ d  ! ~! V6 W, F l9 q, E$ S5 c

1 a5 s5 @& t( l0 q! e6 N1 p' F

) b' i: z' m* {8 f$ ?8 k) f1 |/ C         $ext = file_ext($_FILES['file']['name']);; V' _8 P+ c0 ?! r

0 H! Y% O( S- `$ O' n' @" |

( w- |- F2 C: R/ h9 Z         $name = 'avatar'.$_userid.'.'.$ext;9 t8 r# T/ x5 Q7 P$ `5 x

2 p* n8 j2 L R& F! l

( S! Z: J! K5 x8 {* F! d$ }# K         $file = DT_ROOT.'/file/temp/'.$name;8 t" M1 B+ R; w$ b1 F1 R" d

9 N* a+ ?6 a* d% n' o% U) S6 j+ M

7 u0 o* g2 v) n+ D& Q   0 I2 Z7 { U& ^: I/ F8 A: [

: X% l$ Q) W% K

7 {& {- l' Y6 `# s1 K# }/ x6 R* v         if(is_file($file)) file_del($file); c+ L! K" t* X& m" ]& r0 X, w2 n

/ k8 } H- }9 n( S

0 z5 r5 j) |& ?' f# n         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');2 @ V& |2 @1 T; z

% Y( W( W7 p2 f, J$ T

x$ S7 g6 J! d9 q, z* w, ?4 ~   / ]) t3 _& ~& {. } f

! M2 e/ @( H) q" O0 R, ]; I |! c9 p

: r6 Y, ~/ U$ [& I/ P9 L5 M! y- q. V$ M         $upload->adduserid = false;8 F: R. J# T7 J& u3 `/ R6 ^

; O$ J7 W( N/ t1 v5 p* V

9 ?; N% ^ s. T7 C: X   6 v+ |6 u. I8 B/ \9 d

' c0 @0 e7 [. S

3 P2 d: N6 j! o3 X7 |1 Q         if($upload->save()) {( Y7 Q9 T" y: Q( \

/ L1 k% R1 m9 @5 w- z( z) ^4 _* d

2 y* I! W" ^+ u+ v4 e; a- R" Q* a             ..., |0 i5 ~2 |. H: x

( Q, ]6 w" k) s

: ?4 l' [/ N1 d* d0 f" S/ {         } else { {% l) b2 A [1 l

, U3 Y+ T7 P4 i. `2 Y: d

- G# c$ H: T. ^             ...5 f) J: i# Q, T/ v2 J9 V2 i

: Y) S% g, s: S* |+ {- d

1 S# b- w) f/ z V         } 0 d/ H! K6 L8 h: w4 W y

: f+ |* d; Z6 Q1 o8 X! d4 E

: h% [: }0 i* F0 s     break; ; o7 t' b6 B5 f; Z. d/ C

0 O G9 j" p' I

! c. N% r# `+ K& {# h0 P# X0 s# q 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。( @) \+ n! ?$ B# J: c: f* ~

8 c6 ^ p0 U. F0 f

" q6 t# k- R" [; v' c" _ upload对象构造函数如下,include/upload.class.php:25: % L( c! f& C2 ]% H) N' f

, j% I% Y: J" X

$ x0 V4 J2 B H- Q5 t5 C <?phpclass upload { % M$ h% h" f7 |7 x

?, A, l. ?& X; o

" ` y* Z, v# @, h3 _/ j. r     function __construct($_file, $savepath, $savename = '', $fileformat = '') {) h4 N% [3 u( h: L4 D9 ]. w

1 V* U7 C; w% P

. }7 f5 J; v) t         global $DT, $_userid; 6 I3 t, r a2 ^& L! B' Q

9 f: R) R, f6 S+ K4 ~" Z5 j

& F5 o9 s$ ?' @" E6 o         foreach($_file as $file) {6 `/ j+ P8 N8 L: C4 `! `1 I$ ?* i

, o) e) s l$ R7 Q6 G+ C/ A

% a$ Z Q- A+ C: ]             $this->file = $file['tmp_name']; # ?& Q5 k& L, F+ M* r w$ x9 i

% e! ?$ H! }) X9 M1 |. B, R U; _4 q

/ Z2 \( l5 n" [2 |& m4 b             $this->file_name = $file['name']; 2 {& b1 L- q- ], I$ G7 x# M' ~ R1 v' a

' m! D' }! L& @9 e

) _9 q; ?. K, p$ E8 }1 J: {             $this->file_size = $file['size']; " P+ N8 _0 A; a) f5 f/ q$ c5 w

% m) `4 [' W8 ]6 F" h9 G1 X

, o& |9 r1 {' e+ D( Q             $this->file_type = $file['type'];0 Y( r: H, o5 h; T

' H3 S, Z6 N) \- Q

$ g5 S! F5 d2 o; }6 Q             $this->file_error = $file['error'];2 Y& ^% x4 }: S: P+ f1 ~

7 j1 y' z! w& B. x

0 o0 W3 Y- Q: `; ?) ^( Q/ R   ) @0 Z8 \7 g* {' Y9 O# r

% y: o3 w1 D2 R( t

; m" Y7 _) Y* ]* O         } ' F5 e) V1 L0 t6 a4 H7 ~5 Y

' N: W3 E0 r3 y

9 {" c t7 Q2 I2 I& c3 M         $this->userid = $_userid;" _6 a2 C0 v6 W1 N. _. B

7 D9 T9 S7 p& }. O1 B0 D1 l, W

3 T$ e% V1 D2 z5 \+ J$ m         $this->ext = file_ext($this->file_name); + Y! C& T6 u: N

& D/ v) ?& t9 z/ Z" e

}" d( R$ U7 z" |6 a" [+ t         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 9 } r2 W P" J- N1 t

/ }( G# \3 T) d* i! z$ J( f

: Q( _6 [) }8 n/ i/ r6 Z2 S         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; + o) J- c# b- d0 f9 Y% i

- c0 H$ U }8 k( [

1 j* g- R" l/ r         $this->savepath = $savepath; 2 w z9 R+ e! E+ }: ^$ d

7 R3 i: U/ I+ F B

# r& r- @1 f6 }. G( _& _         $this->savename = $savename; 5 R+ P$ a: g8 d; K: [

$ U# ]" u: ~; O" K( t' h! c

/ l% I% J6 j! N0 l% I6 X7 B     }} ' v- v" O/ o% F5 P1 H/ G

9 T, P8 H! a) b' u9 K2 G! \# w- p

2 a! ^$ b0 |. I- f% Z: Q 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 * `, a/ ^% v. W: ~+ f+ F% d

6 W% D6 k% d4 A3 N

" }. C: z5 B- a7 o6 \% K, B g, v 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 % L# P7 n5 ]8 {5 t* s0 R# ~

1 z3 ~' c/ d' u1 [

: y+ l: u6 R/ c/ {* m' ?; e $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 / ]5 n t7 B' s0 @

6 S* V9 |5 ]3 k5 V

" M8 Q, M- V8 N: Q9 \ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:, A- W+ O e: z/ K$ ~3 t: @" x

8 @; p9 }) q: M$ j& u( I5 z5 p

8 P5 g$ y% E7 b   " r# Q. j5 [, a

; \. P& `2 a8 V2 r( F* ~: @

3 X2 f4 U. M6 X0 C 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: + w# O+ k: _, B2 v5 M; |

+ M; I/ \, Q* a/ O9 k' |

2 t9 M. t6 k( g T0 {. t <?phpclass upload {" V/ J/ F P6 X. z% m

+ [7 h _" r% o1 D3 s

- Q% W' u+ V7 g# Z7 X     function save() {3 o) P/ W: [0 E; G2 v1 s/ r

. Q. H f7 B: n: Q. }1 X1 w

( M% S1 X' V5 r$ k V         include load('include.lang'); " k: n% r3 V* q8 K5 W3 w/ c U

$ K. t. S3 |/ l% g5 n1 h* r

7 H& \* g7 @) _* [# i         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');+ d. n7 w; A7 W3 a3 d. M6 e( i( R$ z$ y* J

: ^+ e% {: n% r* G1 ~, \4 Z3 M

9 {2 u b. e, z7 B# T3 ~   ! V* P8 d9 C4 ]. b3 w7 b- { h/ }) X

0 @! k4 b. i: n( ?' d

+ y* w# `, i( [+ j5 _# N         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');2 n+ |: c3 m, ]* [

% ~) Z8 n: E, r

* T9 Q1 Y. X5 u8 v   + F4 e0 p1 o; o; j- v

8 A9 b+ l! l0 h# f

8 `8 T$ G4 G! d# z         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);2 F7 g3 e; b$ h5 T3 S0 r3 v

9 r w8 D" I X

1 A6 j( Q6 L% }/ r* ]# }8 t/ \   1 y- M2 ?$ B1 P+ Z$ O% S2 r2 p

9 K5 A! a: M0 R" J

! }3 o" e6 I5 n. ^         $this->set_savepath($this->savepath);3 a; J; L9 G# L( q" x# W

6 ?; ^& B: s3 X4 H# A9 [" u

* O8 @9 K6 w& E7 r# Y8 ]& h         $this->set_savename($this->savename);; ` b+ A$ V( H, j5 o9 V

! a1 m7 L8 F' i w6 m6 _* l

8 h! Q/ t4 i7 X, Y# x   " L, y( ?1 ]% ^

* Z- B4 W4 g% R& Q" j; g9 o/ D

! H% y) Q& E* s( T g( e         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" M2 u |9 Z! d2 ]5 k9 X; M. y' ~

. Z1 T; V2 ?0 w3 {6 S1 z0 D- h; v

! l- z e% n, p* y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ' @( H/ M& \5 C, z

2 [# p& [+ r+ ]9 V, G

; Q2 @2 g3 v! K. t* `& F         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); * ?4 T* V7 ^! }) x3 M* Y, F" k0 T

) m5 L4 F) c3 r9 c h2 X

/ _7 W% h0 W5 r2 E2 H0 Y   3 X( X$ R8 g0 [2 w$ D' q

8 `/ Z B0 f: s T5 t

' @9 T) f8 P. B# n2 k4 m; G# K         $this->image = $this->is_image(); 6 L2 [ Y9 J) L/ H, {, W2 W

. }9 W# s" C8 E8 V& q% n

; k' k/ G! c3 D5 @, B% ?; G& Y4 v& \         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);. ~7 [8 U; E9 h3 }* x% ~4 Q0 V

7 T0 k a" B) p* B* S) D

8 H0 H. `0 c: S R- c; d         return true; ( {$ p% t6 D& W6 S$ D+ s0 a$ \

/ [1 l0 M' h6 ?9 ^+ u' Z# Z

" m% G, s4 B% D; K1 r! d     }}/ D( a# ^; Q# O. o8 m

/ n6 }6 o7 q5 I. E1 L9 [

) k. ]8 X4 V" o0 d3 E: t2 }% Y! E 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ! y2 b& a% o2 V% |) o& a# e

h+ W+ s F2 o/ ~8 z

2 ^! |9 R, c: Y <?php" @' E0 {( ?0 s

& t. s: |0 D f! j2 d! e$ h/ Y) E

* U! Q- v6 b+ ?: O" Q# N. P     function is_allow() { : w+ t, f1 h: F

) J8 R( j. P, \

1 F9 a. P- j/ O: [ V- B         if(!$this->fileformat) return false; 6 [2 S& X5 o: t6 H% j4 W9 M# l

9 W% {* ?7 H M- n3 K

" g" l& ?8 a/ G         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; ; G/ A/ V/ k o6 {/ \; }# j

6 Y) R& y1 O0 _* s

$ ]" f/ c# o; \0 C8 B8 _+ V         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; ( r) ^' i# U5 v6 {6 r

y1 T, s6 L( N2 A. H8 \: f. i6 N( L

, d s3 ~6 J& {5 I         return true;) X1 N$ a( H. U6 ?2 z, b, e

( b5 ?) l, k9 k& I ^

8 [$ ]# j* K6 m1 }" ~7 G     }9 ]; d1 W$ ~. Q6 m: m. r5 e$ y p

( r& N4 v- k M: r! \% O* t

/ ^: y9 S9 b5 j( N% l, _3 ] 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 , T" c& [+ _: T3 h& o& z

1 y: w9 ^" ?, Y8 i* y8 Y/ T

/ |1 M' \& r1 J4 @" H1 J( o: m 接着会进行真正的保存。通过$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文件。 - x8 I: ~0 @8 P. r. f7 c1 M) t

q) E ]9 X' U3 r; s

- D# s# F) e# L 漏洞利用 7 G6 E. S0 c; R& y' O' \

& A8 q/ n5 U2 c2 Z' `. @& J

6 Z- j% T8 m4 i" ]- } 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。5 `& I! K: m, M, W' X0 R' T

3 _: U9 k/ W6 `9 o# B+ }- j( m( u

# `2 H. c) q( i/ H7 a: z! s6 J   8 U. ^6 c+ R( w3 r1 v+ y; h

+ \3 _" g2 y2 i

- g7 E V2 c2 ?0 A# Z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 5 I0 A- @1 ~+ q% Q, v

% d" a$ Y( B- @6 T2 v

9 _* G, A3 ]6 W: x) W" L" f 不过实际利用上会有一定的限制。 8 Q" M: A( D# k- @" D

2 F, E7 |2 q/ D- w3 _

. j' P# \% n) K) J f [/ A/ e+ O 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 * q, c9 B* s+ h# @0 \) V: Q

6 r- p3 C4 p/ t+ E0 j! H$ S( l6 N- o

; l" g( ^1 r2 v$ P2 ?( c   8 N; P/ i( V2 G0 M4 Y5 g* k

( u% p# M2 u, Z0 S

8 z' N$ l- H3 [4 U0 n4 r1 |2 g2 s 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:5 `3 R6 z, {' k5 O, K

% V3 |9 p; ^2 ]! K" a: @) Q

- U/ o+ Y8 [; O2 p7 e( N, v5 \ 省略...$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]);省略... 6 ]+ B; u" K" q8 |9 x

+ _6 c- @* W& K7 K

) @" i/ L) l7 s; {+ q 因此要利用成功就需要条件竞争了。% v3 O$ {+ t# }( M6 ]8 r

" B* S* @3 Y' v7 j0 @2 @

7 c. ~3 { y# W3 U( N4 ]$ I 补丁分析 6 j& `% f( Z1 p0 r9 ]

$ N( C! i+ S* P3 B8 ?. T

q* M; ]% S; h+ w9 H$ Z   & j U8 R4 U [5 P3 K

; s$ t) V" o# k8 A! D/ y

9 D$ ~, x( B5 ] 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: * K4 r- b9 g: w% }& `! f

. \) X! w' U0 Q# [/ l$ d& G* ^

/ r: P$ X' B* U5 _8 P6 @# L function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}. e0 N; o& R5 L

9 N% z4 y l- `

! i8 l9 h- k" k7 r3 p( M  9 W+ V, @2 }4 i% G- ~

7 [* U, \' r1 l5 s

$ F; \# K8 e( d% K7 T- s/ @ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。8 s5 c0 ^) Z/ X) {' g' c: K5 Q

" T6 @& L4 S+ n; w0 M

% ]! z9 v% v ?' ^ x, N3 @ 在is_allow()中增加对$this->savename的二次检查。 ! n* U. F0 o: x q2 J9 P% a

4 b; s) W# k. \' G4 |

" a0 F7 x) r' w# T- {; g 最后4 r+ k. V: n) @+ `2 l# K& O

) Z, f4 V3 X# p" i6 h

# ^4 a- M$ x u) t' d; \ 嘛,祝各位大师傅中秋快乐!4 y [# ~' ~, n! R0 e

& u% W" Q7 ?* t% l' p

" K D9 B3 l6 b4 L5 l- Y- ~   4 e; x# l, N" B1 c8 s

: I' t. _& t: n; a9 z, ^1 t: t# Q





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