中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
; o& r3 q; M, x

0 j/ e# V8 m u+ g3 L

1 ~) m# l( R# z7 x3 Q% e3 ?8 d; y

4 R* n/ w5 w2 A1 S) L! w1 t; R, \ 前言 . a- k0 |: E: T( ^2 K" a2 b

# B- T9 S5 C. Z, `. i7 D* e& F) }

: l& `( d! ?4 C P8 H& ^6 v' v 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。7 G, N- r% d8 e M7 n

( o/ h" o: X! ^

- z: z; l, x& J! i5 m   / b1 u. v: B0 {6 y4 j4 G1 q

& f$ [9 \6 E; U! K

" y3 i+ k1 Z0 ~$ Q 漏洞分析 6 g1 w9 ?9 J9 _% F# l( m7 Q( T0 M" r4 j

: A4 H/ s$ C0 \6 V' T

( b J6 J C3 Q2 a 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:5 e3 c6 K% k* G$ b* f

& {2 `+ h- g5 y. I

! q8 T8 i5 k5 x4 k. e   9 x! R1 j q" s

/ L2 C1 ?" V# S; ~ @

# ^% N# ~0 r2 n; X; B9 S 对应着avatar.inc.php代码如下:- \+ q- I5 B6 }; ^; f* D. A! g# p

% u- n$ Z" V) l- n9 B3 }( `+ l- I

2 Z0 {& F! U2 U4 M; g <?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) {# F) L" I( P N6 v

8 ^+ F4 Z. h* X6 J- E: h' ~

, q6 Q% W: U( E' z     case 'upload':1 A' u: V% ^9 e$ Q

6 u. z1 l+ B' H

6 T; l; ?" T# b         if(!$_FILES['file']['size']) {& x c6 V! U6 n! w

% W1 g- K0 o9 J/ Z0 d# V) I

& H6 v. O+ ~/ ^& \             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);$ s; ]2 E/ T7 S: N* L9 W

! e6 c) j" Y: g- K1 ?, T5 I2 v

2 j% U6 C4 o7 z' h: @+ I# r             exit('{"error":1,"message":"Error FILE"}');6 O" n: N" K" I2 v8 o# p6 k

* x( `* p! |/ L$ B) b) |

3 H& ]: n3 x5 b8 D8 T6 c; I         } 6 H I4 M) i4 H

& I7 b+ Q! H6 [3 e- b$ R8 S

3 P2 Z+ B' r: q" K8 `* p         require DT_ROOT.'/include/upload.class.php';/ R3 R6 S7 u# p) s. R

; x t! ^1 r: I9 @* {0 I+ M

4 c3 A' b$ v# N; R" u/ Q! V   `6 o6 C. Q4 p+ N$ s V4 v% x, L( S

k5 `) k3 E+ S8 z7 D

1 U4 Q$ T8 n. Q" L. p" |         $ext = file_ext($_FILES['file']['name']); ! t3 ?( v# p' b5 ]

9 c; c# V# D/ z! F0 r" b

3 @7 y/ B! G* r% F0 `. E         $name = 'avatar'.$_userid.'.'.$ext;" w$ a7 L! Y! L& b: {

5 V& C+ A: M9 X0 e0 l" [

( ~ F" {0 b3 A; p" f \& h# w         $file = DT_ROOT.'/file/temp/'.$name; + b& Y L( ~0 x/ J3 x

9 @ g. H! K' x* f: i

* s z) _2 J$ Z# R( t   & l7 \& p0 ?4 B, p( s/ N1 Z; G

7 j% x* i- B$ d

2 v5 F& U* \7 y C         if(is_file($file)) file_del($file); % O: f/ J/ \' e* w! S5 T0 [2 K

8 G2 T9 O# r& ?3 u

' s: T: G# G! L0 F$ H5 V ?         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); - A4 W: a; o9 w

1 G, d4 u, ]$ a D

7 y& z7 n" U# K) w2 V: R; `   8 L$ w) e- i6 g/ `5 W3 i4 D

+ [( k; [, Z! L% G2 [4 Y

6 O: c6 Y4 h4 H6 J         $upload->adduserid = false; 2 j" I( x% ^. D7 F v% g# c! H

1 Q" _, B% d1 d/ B) q0 T+ E

3 w# R" @' s5 S3 v   2 e6 N$ X! F+ V* U

: m, H, ]5 b9 v7 p) B

" H7 |7 [4 k: B& u# w/ @         if($upload->save()) {* f+ {3 v, {% q5 X1 C. Y

' J3 c1 k! K, q; Z

6 W5 _8 W, B3 l+ {5 {2 R2 \             ...* ?; P. x, L' ]0 n

+ A5 p' U% Y1 f* ]+ r$ `9 `: F

, f1 g1 F7 U/ [! l- @* Y         } else { , n5 ~+ _( I2 P- o( Y, W) z2 }

+ h3 H$ r: | Z6 y3 f8 o

% D) |8 ?5 x/ N9 Q             ...* K2 D) M/ x0 Q0 q0 j

9 F9 Y0 |1 v. t) {. z

& ~& \3 l% q m" w         } % Q3 A2 e3 B( C8 M5 y) D# K+ Z8 M

; j) `- ~& U0 r) u! v! i

3 \. s& l7 E* {- v" u; O1 I     break; . L& e6 Q4 R/ h) I0 g o

4 {% f; k$ E0 l

& R) J- |' }1 X6 Y: S8 y, h 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。- o8 i8 b& V' c& s0 c- I% ?/ N

; D* N! V* `2 b. m2 A6 j1 H7 o

1 b/ U0 g( ]( d+ D; w, Q upload对象构造函数如下,include/upload.class.php:25: 5 k( p+ s( Q7 w& F S V$ c( m7 }

. A! M4 P+ Q7 C* U# x

+ n) F) u4 h" E <?phpclass upload { $ m! S* n, i4 x- S- j$ H3 M

; W8 J1 L/ I5 L% G" M$ ?

: D" [8 |1 M- G8 z, z3 |     function __construct($_file, $savepath, $savename = '', $fileformat = '') { & O/ ?* O% L( Q, y9 K

9 s& W8 S) E& b$ h/ N5 o

7 f6 S, |" ]4 {         global $DT, $_userid;5 c6 k4 v/ V( `9 w, a

" Y) k" s' ]& h u

0 T! u; w" F* W: M, x! c! z         foreach($_file as $file) {4 i8 f- U! |( V; |" N

! k) A7 ^3 X1 {

" c$ [3 J/ a( M+ G- y7 r             $this->file = $file['tmp_name']; / k4 u* A5 d7 u0 |6 L' V

, M v9 _4 L4 \1 n! P3 ^& N* o! |) X

9 r' O# W: L. R( P. P7 v             $this->file_name = $file['name'];. K7 t9 `% U; d* Y# n% B

: S" [" [, M' {

' ~9 g0 b' e8 B( t- e             $this->file_size = $file['size']; # `/ D$ }8 x0 ]- O8 Q2 `3 x. G) J) h) [

, f- A( A1 N9 E! ^4 y5 u& S4 k

1 F {) {% l$ S1 M) H             $this->file_type = $file['type'];3 k: A6 z/ m' B& {' \/ A

|. N' {: G r9 c. W

6 U N4 d4 L2 M5 ^5 J5 q( o             $this->file_error = $file['error']; : [! V' V' x* k V

b: t, q; n+ I, h

, Y0 [( H/ u9 F9 x% p) R5 @5 S  ' e# m- m" \' k9 E5 i! _

4 A# y! |) e* v: Y& J E* w

! c) t, `4 ?9 x9 J z0 `         }. {! B5 E/ R1 C5 S

) u$ I, _7 g; B$ P; u9 V* z3 p8 [. a

1 y2 Z; w% a# M& i) O8 u8 U5 _         $this->userid = $_userid;2 t9 o' u* P* o' z* H( x/ V

4 y( \" r0 F' m/ _0 g8 @

1 E3 |, U5 P; r3 B# o         $this->ext = file_ext($this->file_name); # C* S( _, t2 W: x" [# d

0 \# l8 p. ?% @5 r4 b

! b, [: [' k3 o o2 T; k9 v         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];; g+ t; \# @" f% d, Q6 g

9 q F0 E$ H* Z3 a4 F1 K+ G

5 x- R" d- s9 r& U# y- y' B- }         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; d1 l& u) x& u% h! O2 `

, ?/ p$ Z+ A6 ]6 n( N! F/ X B

1 `4 l& w; ?9 N& E         $this->savepath = $savepath; 4 u# _, ]5 x: j# u0 L- X

5 c6 n- z5 j! P2 Q( Y

* b1 Y9 R: ^! O6 ~! {5 J3 U         $this->savename = $savename; : a$ _0 u$ K, f/ g& o' l/ ~6 K

# M. j) o N% M( E( L

& E# z: i ~/ }9 Z     }}0 k3 q- B' V6 q1 f) t

: B+ w% p$ u9 Q) d4 \, F7 i

: [7 `/ R6 q8 x" d+ J3 [! o, C/ Z 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 8 w+ P, r3 D4 J4 o2 H: k6 Y# J* l

8 L; S7 S0 T9 y, E9 t" |5 G, w1 [

& z5 _1 T! W( `) m 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ' |+ R/ {! H& |: F/ V

, X* t% a/ U9 M

7 d, V) ~9 S2 E7 h5 Z+ q* F: b $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.php3 X7 ^2 _8 [) H: K

: l) L. g4 P$ m" J& z8 h) f

- b! A" U3 B( L/ ^; j, v9 l 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: ) R# _* l$ q( L& W6 v. Y o7 c

+ I( h; K# O t

% [/ d' z6 m- U   " Q, j ?/ M# ]7 z

" \: Q, V* S2 r z: P

; \. B3 b0 B3 B7 T7 ] 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:/ e6 I, C) E; I Z" {& W

! s' k, s1 z4 {9 |6 w

' k/ _0 {0 y: g5 l! X3 |" J <?phpclass upload { 4 [: G. k+ ]5 z- M& E

' n9 c' q5 R4 p

! E8 z( A' ^! W+ f% {3 y! a     function save() {& I( x) H( e7 |3 b) R0 c7 A9 p

2 h M2 _/ G* j: p- S1 u( A

5 b7 R+ P3 R4 |$ M( ?         include load('include.lang'); . E6 G. G+ v! @0 c' u

- U. I. }6 b8 | N& A* F# ^

3 v, I5 Q; B# G* M: ?7 h) k         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');. }4 W# I$ U- ~) G; I: Y1 V9 w

) l4 Q: K; Z3 J% i# C) \

, J9 \9 w6 c; H8 x+ c3 E/ D$ f   $ J/ ?$ b# G% K( b; C; m* Y

/ U4 i/ z6 Z7 U1 s5 `0 z: G

- h: w8 P+ o* B+ ^2 @         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); ) \ y* b& A z+ Z/ N

3 e, ]3 P! G9 w9 l

8 @6 B3 S1 n$ l9 f2 |: p   4 R' Z$ |5 E; |5 S$ \ m, B

5 g5 f& i! f" t* U3 w

! r: K O: u+ j5 \( G3 @         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 0 _& g, ?- E- j* u! p/ I) d% v

* l* x A6 ]7 V& I! `) B; e

8 r7 e7 T& W2 {! j' d  9 o" L8 N: U4 \6 M; }/ i, H: m' S4 {

W) b# ]: j) R/ C6 G. L# L

6 ]& j0 R/ b8 e! H         $this->set_savepath($this->savepath); w& v3 t5 B1 ^

w5 Y4 O4 L! S% c

7 u4 q) Z- R0 A/ v) F( Y1 ~0 {3 ^! }         $this->set_savename($this->savename); 8 @$ E; @0 @" ^: C% x% z' i

, r3 r4 {+ m4 @- g

% f- f/ o* g1 ~) F* |- t# @7 J l  $ p! A6 D" Z0 m6 l

3 h | S9 y/ G3 P( I* Y

. e% I. G8 @- M         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); - s4 B! y- u$ |3 ?0 }4 _% f

6 Y A* }0 P6 G7 J4 A S

, ~/ \& c+ u: n( b2 ~6 m         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 8 U6 a2 L. w* A

l4 a0 M7 ~4 \# t

# I8 |# v& |- u2 \3 Y         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); - g' k s5 K: X7 A6 d/ W- u

/ v4 |5 l) \- f8 L5 y2 c: e

Y1 _4 k0 s8 f1 L; q# n. }  $ t5 @3 T; q. g' F: m* x. l

( n$ y2 h: X( u# }& J7 _$ ?

" D( j) k5 v2 o# D% P$ @; ~# ?         $this->image = $this->is_image();1 c# @, u9 S i/ c: a

% U* o! z8 }! E1 `! P: @" H

7 b8 w+ i- a/ w$ Y; Q8 N) }         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);; }9 } c; ?, ^' e

% i/ d7 B e* q4 Z. w0 ~

: e- t8 W }8 x: b9 B: E         return true;0 h9 O" z( h$ V6 `" Z+ B- H

+ \% S3 c( h! N, N, u6 I/ H4 w' g

, a/ [9 f# m" r7 p     }} 8 E0 ^5 @5 [0 n: \& Q9 X+ O5 K

4 b# j, N9 w- I1 z

; a d, P0 t8 w) \. s 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:5 s v e! A, O* o; S- e! i! H

% o p% D" a+ i

. Q C7 ?+ M" i <?php ; J8 v4 Y! C- N3 t! t2 b

2 n% q2 M8 i: E9 i/ i

5 Q- q) {) w4 B2 P4 J4 g     function is_allow() { ; w6 @! h2 r% `$ A; L4 t- H

5 E4 N: u7 h5 Q5 `# T# J

{% Q7 n3 R& b! r _0 j9 Q; H, w6 o         if(!$this->fileformat) return false;: Z0 S$ O, m( H B* }$ V

" o( `( ~6 D, [0 G/ H [

1 y2 q: V, [* S         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;7 `3 ^$ u9 m' ]( R! c/ U4 \ k, k

: ^5 U& E4 r. G

- E5 x9 y2 w1 \4 X7 A# \9 s' 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;$ e! P# U; ^4 I2 ]( Z$ H" b y7 G

! L& I/ W) U+ T. H4 H. ^( C

; a, a* M& `5 p" M i N         return true;% _: ]2 p- i+ w! M2 H: C8 n

/ c8 q1 o- O7 M0 a+ @# |1 J; _

. [, z8 s# I9 v# x, c     }8 u$ Y- `$ t9 _: G9 ^8 M. E

5 `9 o2 A9 S) A: U* ~* Z$ z

3 g, }9 s: ~0 X" S( G* ~' q 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。4 O5 s4 F y/ I4 B# d* k+ v$ s

5 y! K8 R/ P3 a9 J/ k+ ]

8 B" b3 l6 K/ c, e; g9 P% { 接着会进行真正的保存。通过$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文件。 5 l# f' E. I3 s$ L" E# U5 E

; g" I4 {& [/ Y3 i) R

# E7 D8 t7 |. l6 \% }, P z 漏洞利用 ; J) m: a5 ~# _& t4 v

2 X5 e5 w/ o! E; h, F

, w2 L! E& _+ Q) r 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。% x( S% q* i5 p% V

% Z) e( e9 B: H: y k1 \

+ _( W4 q& R- R5 i2 W! V   ( {9 K$ u- k [) s

; ?7 V& F7 X: T1 M

{( ?0 C0 O* U+ e 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid " S. s6 ~0 n5 |6 v9 n) Z

1 l! V* A% g7 e) \* S' F3 ^

& b, D( P) p/ F/ m$ B+ M8 H 不过实际利用上会有一定的限制。4 h) D; s) l) ?0 u O l

7 t$ ^6 Q5 y% F: t% n

6 {% k7 z; S: z; Z 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 I' ^- U3 j2 P9 \& M" M1 G3 l

) K$ ]6 o' k; q$ \' W+ j

- K. x# g# Q, L- v2 J2 U* ~' V$ P  2 U# y& G/ S& K) E; K+ b

# q; _, ~1 D- ]0 \# V

* }8 U: T+ B0 I- Z6 q, C 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:/ F& H9 }1 N: M9 O+ l$ \# e4 z

9 \6 ~' w& S' s, u. [) K

2 m1 W. P/ U, `+ t$ a) ^& F 省略...$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]);省略... 2 D- P# M; I# G

; g8 ~7 k9 E& c5 U, ^# p

" J% }$ z, r( i. c 因此要利用成功就需要条件竞争了。 1 J9 L/ j' T( f0 F# ?8 M" O

, T- z: J, j2 f

+ T0 x: [# h" {% h# e4 P! d 补丁分析9 ^! c: s" S- Q- n/ G- n. @

& Y3 J# |/ Z U& n) p9 a7 z) I

5 S. Q( W" |- ^# u" o* S  ) X; `4 G) @: A

1 F$ L1 O# j9 H9 W- B/ h

/ Q( {# V+ V' J# R3 w; W 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 8 E) ?' ~+ o9 d! O) Z6 Q" f

- y6 d# P4 J8 @* k' l( c2 C

' n5 Y' C! O: F* i5 o$ L0 d function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}( _" V+ q) }, S0 B( b o; o

6 B8 ?) ^, d" D8 X

. {8 G8 j( h" [   : i2 H1 n$ c9 K- b9 t/ q* f

. c( j8 r& ]1 V1 {0 r$ q! p' l v

& w' B( H! m. p2 e. q7 g 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。4 h% \* d- E7 L

% \ j; b. v A* ^% O- ?, f$ G

1 p0 c. q/ d# I* D+ I8 ]! B 在is_allow()中增加对$this->savename的二次检查。 ; q& x2 i6 r8 P7 \

; m: L0 {+ b( w" z( V

$ g4 N1 b1 h7 d. a" n( ~ 最后 8 D" d' S! v2 M* X6 H: A

' K' J1 O a7 j/ \5 R

8 b6 I4 t: L6 w; w7 K0 h V1 w7 p6 E 嘛,祝各位大师傅中秋快乐! " g c# U% E& \: g4 {! b% i& \+ q9 n. G

' C p0 o0 E& Y( W5 r

2 \7 j' J; M" y; u- B7 L6 |   W2 z1 r% H+ a7 ?0 y r

# W( ^) u9 e1 D' F3 T% h





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