中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
. D, r3 ^- Y" f1 m- A

# n, {7 H2 ^4 ^$ x0 X/ X

Z) N' v1 }# `

: {( f7 z `2 x/ ?* ` @ 前言# w' Q/ O9 V4 {& W

~- r; e$ C0 E% a3 H

1 z4 X0 p) G5 Z) @6 T+ _ q# c% C 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。6 l! x7 }( U8 r0 |

& E6 K( U: C) _& A4 b

, }# ^- U6 m( J4 }5 [/ u* Z  + @: A$ @4 w7 Y/ _- }7 J

5 W! [9 f3 M8 ]0 Y ~3 m

: f, Q4 B* I5 B 漏洞分析 . `2 C: u& f/ ]7 ?* j. p

* a3 y& b) }0 `6 b. W

2 D7 N6 u* v% Z# T7 H0 a 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: / |$ X: h# Q: t# h

: d9 U6 F. m5 f6 M- x

- f$ r9 ~- U: s  3 T0 R5 r+ w0 }: W

$ ?9 c2 @/ B, K: u E" O8 E

+ v) t: ^8 P; S5 V 对应着avatar.inc.php代码如下: 2 ]/ M( l" b% [; l

) a) c# |, @ E2 K5 m

4 z* L$ _; I% {. t" B" p <?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) {. B, {+ Z$ f5 l" c7 i0 [

1 E& T' j( i4 ~( ~9 _/ j, \

6 ]4 [( ~9 K7 ?, P" W; g7 d     case 'upload': 9 a$ _3 z6 C# v. L0 D4 u7 R

1 N, `7 B! z- g% c

* I9 p0 A# T6 Y- G/ W; q         if(!$_FILES['file']['size']) {8 O ~8 @' d3 f" H

. _3 o1 i; T) C8 B

- L0 z* O# w2 c. y/ Y             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);: W2 v- S8 \. ^( t, Y4 ~- _/ m* n3 b

( q* H' h0 `, c8 P- S

$ o) u' i5 j' V2 L/ D             exit('{"error":1,"message":"Error FILE"}'); 3 @/ B" c7 D! M

/ H8 `; N3 n. E, d

5 [$ y# @' [$ [! s- e) A         } P. L' X) ^, u

$ d! `, K1 H, L- U5 p$ p

$ r) b/ c u9 D. U' Y         require DT_ROOT.'/include/upload.class.php'; - |9 o8 S+ q. c9 Y2 l. h- h

- k* H) t3 d, P' ?, `; Z+ m

9 x0 f9 \2 X' _6 ~1 R$ t% {% Z2 T  - j% T6 s3 e# G, W4 E2 F

& z8 D0 [, q# x4 j/ \) J) [1 u0 v a

* T7 ?: `5 i- c3 a+ r& v1 o         $ext = file_ext($_FILES['file']['name']);" K& j) f' U% e* P% T

+ H. f& t) x3 d8 q$ `

8 t5 v$ `& U- r0 T         $name = 'avatar'.$_userid.'.'.$ext;2 b5 P6 y1 _( P4 W+ ?7 l

+ f5 [7 ?) I$ |6 o

! n1 ~7 Z/ Q& a( }( \0 w0 F         $file = DT_ROOT.'/file/temp/'.$name; ! F- I# }+ C% S. [# b/ ]& P

! ~5 h( H' j3 [

5 Q( g. v3 j6 g) @- r' R   2 k- @) G+ J9 Q4 D- F! w2 G7 M! o

o) i1 L& @. r$ S9 l! o* z- x3 u! o, o

2 }+ | p$ [2 j* ]: p$ d         if(is_file($file)) file_del($file); % Y# U& i( e4 r

% M' j7 x$ ]0 F0 x% v

# k; l' G# I9 D7 p# T         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');2 v5 v `0 e' I3 d' ?, R9 ~* J" c

8 O6 | N& s0 s, l1 M8 X

2 Q2 D) z* N: ]$ a   ! a+ u1 U' ]( U

1 d4 R! P; L2 o* O h

* N: |4 r- V) ]1 v# J6 v         $upload->adduserid = false;: r* _3 D+ F1 O3 h# @2 Q/ H2 `

, r1 v4 S0 a5 |0 t

, e3 A, v$ l4 A, \. v   8 F& T" a3 c; x0 t( n

! Q0 O5 x) t$ Z; e2 u7 V

* { {0 }" v7 j3 V, |7 P* `8 a         if($upload->save()) { " u) |6 z2 }3 m2 I, k6 _1 v

% E, ?0 ^# g& M

# K1 O/ `+ X- P, t$ K1 E& i* |9 S             ... 2 S8 t% ^* c0 V8 x. k: H' [* a

* x0 U! Q% M- H1 w: _

: J o0 i3 N V         } else { 4 b' R/ [/ }) Q" H

: R2 e; q+ \& f1 Y0 e- _' _$ b3 Y( |

6 V) H- I/ Y: |             ... " \( X0 O0 | Y+ I4 E0 R3 ?5 v: X3 z

2 O" f5 z6 g* p7 J

& |7 k$ M& V$ t. \         } : N/ Y0 M' N0 o. W$ ^

" q# o! S; W4 t; `, h8 F

" C) H9 e+ n/ f6 ^4 N9 x* J w' z8 L     break; , j6 g. A1 a7 P. f: V5 O

9 {- _2 J# q* P* f7 D z1 h4 [! j

; |: l3 d: D+ w: e 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 + r4 V. x! l, g/ M* ?) q. E

5 W3 J1 L/ g9 `; p4 y+ ~- x

; X% O7 d+ k/ S upload对象构造函数如下,include/upload.class.php:25: $ e; ^. ~5 O0 ?* m- g5 t* T- s

! }: v8 f' O; G0 H1 [

+ i% x8 K3 _# i3 C4 G <?phpclass upload {* v; ?2 [6 N: Y4 K- g* s

- W: ~7 J9 d2 \2 c

2 F* {( y; T+ S     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 0 [1 d* \1 m9 i9 c

4 @( [" m% T Y& U; t& P

" l3 S/ r3 m- _$ i. G! g/ O& G* L         global $DT, $_userid;3 e' x! X' o: J8 E; O- r3 p

v: ^) j5 U* d! B$ O, c* u- v- q

1 t" S6 ^: R+ k8 M9 h& U1 ^         foreach($_file as $file) { 0 J3 E1 A& b) Y2 V3 o

! @2 E4 w3 r# r) X( f) {) V

5 j: ~5 J( n: U' n             $this->file = $file['tmp_name'];2 A- m6 z# E" a" l9 b; N

1 n1 s/ ] Z. k4 y- R) j. f3 {; V

; l% n: K6 @$ S             $this->file_name = $file['name']; 9 J# m( q4 k( M# O% k6 f

8 t9 v* w. A u* a% |

" X; b/ b5 q) p0 V" m             $this->file_size = $file['size']; & l' x: |: X/ I0 y

5 l2 d8 Y5 |7 M! o+ Y; c& Q

; q/ h4 g+ ?2 y' j             $this->file_type = $file['type'];) |1 V4 j8 d+ |- q3 K

% A. h! u5 O" S8 E0 p( }

! L1 i! E9 J' T! n) c# I             $this->file_error = $file['error'];) n2 |$ o; ]" `+ w% H

" u. V3 T" O/ @, q

( D% D; i7 x8 s, P3 E% R  / v' G8 l9 M9 p

) k) @0 F, T- @+ {* w( [. y- Q

' ^2 e) M6 W" q D8 s) e; v         }, r& e6 ]. R- o9 x4 ~) e7 y

8 O8 b6 ]9 _) e( u0 M

" b" _3 L; ]; B6 ?* J. p* m+ g4 e         $this->userid = $_userid; , R6 P: e6 d+ V9 k5 T" {& |6 O

' o) V8 Y% `) ~4 `& N" K

) r8 _" z4 S* j         $this->ext = file_ext($this->file_name); ) S8 e9 x5 t2 Z I/ ?! I! ?% E

! W* D5 G8 z1 q, f r& m! E! A

0 d( o) i* c; a( j         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];, `* k: H+ W* e" O% U/ A/ R8 ^4 k* I, B

P& a. _9 h& U l1 P* _" b

8 X8 s. C% B% h( A3 d3 S         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;; a! g8 l" U& [ Z7 k

& |6 g, U( V' g- c8 D/ q }

, A. A$ Z* h; t0 U: p1 p         $this->savepath = $savepath; \1 H2 V; @% O! P

& \( `: Z9 B' A& i: W: J

" H- }* i% z4 c+ Y, t% R! y) b         $this->savename = $savename;- T4 h F0 R. |2 X: C

, k* J6 z) I/ C$ y0 L

' o V, r- E5 |6 G/ M, @     }} 1 ~) |, d5 j5 u& T. a

& L$ f5 k; N+ T. s

7 z1 }2 w$ W5 G, ~, V 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 v0 E/ g+ G; ~" s

( i/ [. o7 N; A3 k; U0 q

. o5 s- d8 ?& V8 F/ Y) J 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 3 V8 R; l% x: s' n! }

% _) Y, e7 X( x( M2 X

6 Z4 E' ?! P7 s/ z- ]6 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/ U9 `& W# m; M' }) C3 `

) e5 I. n+ H4 w1 N9 ^7 \

3 r% J, o( t8 I0 K 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:* V9 Q! s8 d7 J4 B# s8 c

) D! n2 k7 c1 i% y" X

1 `1 T. |7 ?1 B, U; i, k* A6 N  1 V- x& ?( q/ d! u

8 ~) l9 n' [' V7 E

4 K3 R0 s* |. Z2 l 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: ! p$ @- v" L( O6 u

, y& }# A$ q) e+ ^, ]! z

$ T) }4 a* e- X0 w8 x <?phpclass upload {8 j" U8 C5 `1 O! ?4 Q% U

6 [3 c4 K( H# D4 P

' s# _, J0 L8 _0 I% x     function save() {1 i" o' w6 }- b3 t8 U' {! {

0 {/ E3 j9 R V' a. [( J

, W6 M0 A& ~0 g. B         include load('include.lang'); # B, j* S o, q2 r8 ]# f: b! ?

3 _# k2 ]$ f0 t/ ^3 C, C* ~! w- F

2 p8 k) |) U( K3 N         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 9 V, Z9 m& `4 Y

; i' t0 L* H! ~5 ?

& w* u1 c5 S. Z# s" a   0 P& V6 }+ M- F1 a( k" M( X0 H

, v$ Q) k& c* q Y

2 D5 N, i: v7 y8 C( A         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');7 _9 h5 {! {5 T% n

i; n, K$ p6 s3 A: F1 p8 M( D8 Q# W

. R" w1 M6 E5 `2 W: [: L   ; C& W* p# {1 D7 g) b9 o/ v

* l3 R2 y% z3 B+ I; j- d- L

2 e7 o& e( d, {% G         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);( b, A4 j6 N! A* l( P; W

7 x8 q% n& D$ ?

4 ~' T) M* k$ l% N f   & N4 q- O5 o* I& L: y

+ _ L* u9 }5 A" w+ B9 p

' }8 b9 e* O1 k* }         $this->set_savepath($this->savepath); # Z! }: U( O6 U- w% F

5 R7 g! l0 C |0 ~1 x

( L# g, D/ h- P" d* D         $this->set_savename($this->savename); + k d3 y8 B' |8 W

4 F! K* W7 E8 [ q/ ^7 R

3 p4 ~- d) n: ?9 X& e6 {8 }6 `  1 ^6 U; O' ~: h* ?

7 v+ j3 J2 Y2 a

8 x' K) { n& I8 ^         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);' p) n& g0 o% F! ^, B! p% y

' e0 O3 R* G) Y& z4 l* U

" H8 ?' \, C) O3 h         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);- j( I- i* ~4 H. c" x

! t7 Q6 q( x7 n# }4 e+ ]

/ n. X- n$ H! \; e- U3 J4 ^( ~/ }         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 6 I& y+ r8 O& _3 W

& h) O" K5 w% q, S* K

* x& r) B6 J. q3 |: e9 |, p  1 j# j. G" {6 P5 t# \2 V

/ G$ X; Q' H1 u7 Q2 E5 v5 q; i5 X. X) |

3 m- Z/ c: ?. D         $this->image = $this->is_image();1 r1 h: F: r0 ~3 c

) C: {3 k1 K* G3 a4 F) K% C. A

- y& c8 \: `! _ n6 F4 S         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);3 |5 ~/ J3 G! ^) i# R

* k O% e; n1 v$ F

) h" W- o+ j3 b( }         return true; 1 S1 b6 a3 m1 G! l

* L4 o3 L1 E1 T6 d# v

! X9 f& @& d; L% g, h     }}+ f; \4 |: { U3 `

' n1 x, y. o1 Y7 E9 [- w/ ~1 [, ]" G

2 B0 U' B( y# N" T% y# B 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:% U; j `) p* V% ]

3 _4 X( M) _ }( ]5 P+ G; R1 r9 X0 a) d

* ]; i, ~- G$ ?, i; r( D4 M <?php 3 b, T6 \! [6 T k

: p+ j2 J5 D: p* c, |

) o3 F* x9 k6 ] S     function is_allow() { 9 d0 q( J9 N; g$ s _& S

1 R2 T# @1 [/ U5 k/ g

3 k0 h0 b+ h+ e9 ^1 n5 x) ]- `* T         if(!$this->fileformat) return false; ) I2 q* [: p% \; V+ _$ l

3 X# T; ^6 K( x6 E0 A( C2 a

( I$ z$ t6 Z1 C6 I' R; Q: K         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; , B/ Y0 X& B1 q) v1 `

+ q+ O& U) {3 y8 {# y

9 w; f6 q9 D( O Z7 l* i         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;% ~) x' k, S4 V: Z1 A( L

( ?, y- a! V1 |( H |2 A, `

3 w% j; z2 E! n9 r- n/ A! N         return true;: u, e7 r% f" I, y

" R3 W9 ` o# ` g( e7 z6 ]# z. z: r

* Y2 g# }: y+ t$ j$ J     } 5 b1 J8 z+ L, T7 E" C

8 B0 c$ T9 `& l

$ k3 d5 Z: }6 y' _" d 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 ( O7 X+ L; X G/ f0 t

3 ?7 ^; E/ _7 T

' k! ]+ H+ G, u8 d1 F 接着会进行真正的保存。通过$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文件。 + n1 w: m; g( F' r! Z3 X1 n

2 S! j% T; q+ @! T* i5 z

u# A; r( _# W$ o$ g 漏洞利用. Y1 Q9 |7 U8 j s- x: O! S/ E

$ {% v/ Y. i0 }8 s; d( ?: u9 G

! T/ Q3 T4 j1 V' E+ R2 Z; k 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。3 R# O6 n* B5 M# n# c9 }( E3 i

3 }# f1 {7 k) p% Y" P( W- |* X

9 ?( W7 h' M( F w" p! c   $ u. F6 ^9 p, v) i/ _% |6 O7 r2 R

% j2 @/ g2 q! M( \+ ^) \/ i& n% W& @

/ a. ?7 m' g* ]# G7 ]4 p4 [6 V: c& z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 7 k4 _/ m8 d7 A( V0 P8 q

9 o4 G7 V5 J- F0 ~+ N; p6 S% _3 V; P- j

% N; V! }- d% B* p 不过实际利用上会有一定的限制。 n! M9 ~7 y' Y' Q" K2 L8 q

1 }/ ?* Y5 S" {7 [

+ o: J, p' p- X8 g& W$ y 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。! N" F. U l( \" L

: j- [$ E, q, y

+ B! w4 I1 v; h2 a m0 ^2 F  ( p% q) a! v0 G' r( }1 Q

" d* {8 ~$ H. o1 c

) w. P7 c7 N7 i/ w- W# r0 g( A* F5 ` 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:7 {& j7 m& V7 g) N

$ e8 Q) D4 J! g8 T* Q0 |9 @

S' y4 ~, E7 D( R* `, X) Q: O- w' | 省略...$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 I7 p" r& I9 J& e

1 H$ Q. ?% o3 t Z8 K

' G' G. {9 ~& `- b 因此要利用成功就需要条件竞争了。 ; X) I+ h* [( m

1 I0 q( n1 w- }- d h# f* `

/ F/ ~+ x; v' t" e* h9 V: L 补丁分析 : {+ g T5 g$ b% {4 f

! l: x7 Z5 r9 M/ R5 u' j' b

! H5 }9 p; T7 a# R4 @1 A) {  . n9 Q9 B7 {$ U- u' n: Z) r

% t( @2 R5 A6 l) G* j5 _

" I4 o3 P \" y 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:4 V/ X4 a# G3 h/ z

& o. [5 G4 X% O Z& n

6 z/ E+ C/ I+ y! J2 x0 v. d3 Q. I function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}# l8 n) |6 G. N/ |

, T4 e* c9 _! X

& y: { _5 _/ A4 @# r+ H   . T0 n, Q/ ^) a( p

0 j" W" x' U1 B; K9 p. Y& E

8 @% J+ e, S- r' n, p, i; q5 i5 w 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 * V% n/ Q. h" W

9 j0 C0 z% R& d6 Y8 g

6 I4 x" Y( L9 p* A1 y3 c 在is_allow()中增加对$this->savename的二次检查。 $ [( O( j5 D4 x/ V8 y1 @) r. a9 c

+ H9 s. d/ A% Z1 G

4 K9 V+ V* ^7 G4 O! _, C9 b* v' e 最后1 L- d! N ]- F# I

/ b6 \. A) C5 i% k

: ^" L L7 P1 D9 }' j( `$ W& H 嘛,祝各位大师傅中秋快乐! ( b. ^# s/ y+ M: j) y: R9 L6 l

@- m! T' u9 L& m6 ~8 _1 h

) e u8 h$ y; F0 A' s   ! E1 |. z+ S1 N( ^

- l: J" o3 c" T4 K7 N% z2 o1 A





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