中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
5 T- `9 J7 `! ^/ |: n4 W

3 }( A- a1 V$ x G; @4 _

( J; |3 h2 g: }# O y

' j+ m( E- L0 m 前言3 E& C1 X* [: i$ [

5 H" P. E$ g5 W9 D& R/ o+ c4 Z9 \

1 _4 D+ r4 g8 C) w) s; | 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 7 s5 Y Y+ I" w! j! y+ M

; | C5 k# {2 ^8 |

# P7 n+ }+ _" w* N: @" }  " m% f8 J E# E0 N* D* D: U1 ^

3 X2 g! }2 r! M) l9 c- H

! ^ g# h4 H5 F( r- w& v 漏洞分析 , c0 |5 |7 X( q# N* U$ D o

( B2 s) _5 E; G5 }0 t5 k

" z ?& d2 E% v 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: - w( `: P" E3 p: s6 @! O

. ~" n6 C: H3 T1 |- ^' M3 R% z# |

) V' ^1 `2 ?& G. _   ' H6 q5 `/ Q0 \' f! P7 z/ M

' E0 w- ^- i: Z, q

0 I3 p. _) W2 @. o0 B 对应着avatar.inc.php代码如下: _0 {4 D1 _3 z \" b) _

9 k3 p: T( v+ {+ j' ~

. o; G ^7 s) @* n0 G5 H <?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) {$ j# t- L1 J+ X$ w( ?' m2 m8 |0 w

4 L8 i2 q+ A7 Z

, E. u% q+ U! P$ K1 v% G5 J     case 'upload': ! |* ~' l+ r$ \# H

6 x3 {, |) [3 f( }

1 J, `- u! ^7 l7 I$ @; D0 C         if(!$_FILES['file']['size']) {& V* y3 Q. [# L

1 s7 d$ r6 t! ]

- c0 z, W: V* @             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);# q( H/ i$ K% ~3 b4 u, d* t

3 _8 V; E( `0 E' t+ l: Z9 w7 P

* M* K8 f1 l3 K( c             exit('{"error":1,"message":"Error FILE"}');, z0 B7 e4 T0 n; r" y

. I& ^) F% E% r( p/ f

6 W3 k( z: ^3 g) \" |3 c         }$ e7 B% h1 U# ]6 m. J5 c

. }1 v3 d- y1 Y' N

u( P2 i. `" c, T5 W0 M# E         require DT_ROOT.'/include/upload.class.php'; 2 ]2 B# t( w2 n* ]4 d3 Y) T$ M

# F" z {6 l) x5 l$ x& R: H

/ B8 f( O. [- M  ; u- v2 d- e7 h9 [, ~

0 r9 J9 M; S x- A) b" Y

5 C8 \$ d5 Q+ d% K: l, f+ O         $ext = file_ext($_FILES['file']['name']);' [/ y a( X1 b t

& c8 s' B* {5 N% y

! Q8 D+ F4 \& C7 m4 Z- q         $name = 'avatar'.$_userid.'.'.$ext; / q# ? q9 s! n; T H, b3 y# x( }! R

' q& b# D7 ~% l: J' Y, d; N3 Z

4 z7 _7 L+ H c. ]& r. ^2 e         $file = DT_ROOT.'/file/temp/'.$name; * W: I, d* W! ^* x9 b$ e

- m( [8 _: i" p9 v! X8 t* D5 e

9 k, E: H5 n5 L! `  6 Z/ g% k- x' ]0 i4 ?; {" e

: L1 ]: a* c8 s" y/ c

7 ?: X+ d: Y0 L6 T% e' Y         if(is_file($file)) file_del($file); . G" i/ g2 Q3 W: q: e

/ K' E* ^0 R P$ O. W. X) j

/ {2 d' K0 L" J1 P7 C6 ?9 N         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');% w8 l: H# k. ?0 Z; R0 v

- {, Y- `) d& _3 b% W! G/ b. S2 k

- Y1 A9 C* r) o" {/ e- T+ K1 h  7 T. Z2 Z7 G2 }$ L

4 P5 S( r/ h7 Y9 U* Y) G

7 m0 R+ f, @% X/ g: D         $upload->adduserid = false;! z! l$ f2 K3 Q$ c$ t$ _0 W6 p

/ _, f& _ G! v

3 k' I3 [/ Q8 ]3 ?7 I5 ^) m4 b   8 o" ]8 n: m# X1 W+ Q4 A. n4 N2 R/ P

" [0 a! l! s8 z A) Q

6 }' C8 A0 d" s# d1 z7 c% X2 o         if($upload->save()) { / w8 M1 I5 s' M/ B( t; ]5 ?

' W# g0 P3 [# u" p; r4 m2 l

}6 v8 K& u# a! b4 t/ B0 n( M             ...( r5 t& Y$ J5 J. y# P9 k

. N2 [5 e) o! F/ C

: s- H, f! y0 l5 J: K; Q" {         } else {4 X, g8 C' r5 h

9 B. w# H7 t% m# V" i

& V9 d/ o; `' f; j5 L0 K             ... 4 L+ t# S, f# k$ G- w5 ~

3 }0 a2 _- o9 e" W5 p7 q! T% ^5 h2 K

8 j# d$ j, a3 M+ N( x% c         }8 h- P2 ]2 C- b9 h; d& m, N

; e& \2 u0 _1 P+ c/ p

1 |: l7 W8 z+ p7 P6 l! _     break;; X0 B7 w9 K# j2 }9 }6 R* N% j

0 o( C3 O3 [, z) x0 S* Q" ^$ T

! _0 l9 G3 T S1 M9 k0 n 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。8 B( C# O' Y+ u" D2 n! Q/ y3 O

" C7 J8 w/ g+ C4 F- w% W

+ {7 X9 Y2 B- i upload对象构造函数如下,include/upload.class.php:25:3 s- Y. i, j- B W& N

$ `" W* Z6 S! n4 T, @. U

4 s) c$ R5 _ Z <?phpclass upload {$ q W$ K% {5 h( W

# Y! p# c; O# b) ?" d$ I. [4 {

5 a$ z# w) m0 B) r% b: L% r8 E, G' H- Y" M     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 8 z7 t# _% _2 I- G; V* |8 o

4 ~4 z' j+ [: x* k

+ t! k2 J0 ]1 s; q4 P         global $DT, $_userid; & E4 w$ G6 ^+ Y2 a% S

, ~" y) z+ R+ F- S8 t2 O

: D( [7 M: f# m1 o5 ]8 s         foreach($_file as $file) {7 H0 T* r, e& }" j0 n8 q5 N

7 Y) P- K, @2 x: C" B% B4 y

8 E! }5 L; r' d/ J; w" c7 E             $this->file = $file['tmp_name'];+ M- J) Z" C1 W, [

5 N2 ?9 T# Q% n* Y8 {7 C4 }( |( ]

9 l! v% p- k1 F* C; i9 D9 z             $this->file_name = $file['name']; 3 V5 I- O5 _3 u! G% p: q

/ C1 q/ [1 b" W; u6 Y" A8 E& T( ?; V

: f. l F% k) l6 ?             $this->file_size = $file['size']; & E$ H4 [/ E; {% u5 `

. W6 x/ |5 A; J9 Z

5 V' p0 U R) s. \6 a             $this->file_type = $file['type'];. K2 w' b7 {7 K0 H

2 L0 P6 [) f/ F3 |8 a, s. E

+ @9 L1 W( ]# ~7 P             $this->file_error = $file['error']; ^7 ]& J' {( S3 p$ h

J0 D/ H* q! a& x) C

, j6 Q% R( Y; Z* v) \  + i' r: m: Y3 P0 N( P

' F5 T7 a0 B+ I; K. u8 Q* g/ o. X

1 }0 E) C4 s4 M* p h         } 5 i% x' ~" m# [/ a( U# k% H+ `/ z

6 o: H& E/ C7 _, W) B

7 l: q4 g2 L$ G( t; { U, ^         $this->userid = $_userid; " c+ P- F8 e7 Q" x2 O

1 G6 {0 h% p8 \* l+ v/ o: e

- A* L5 ^ @. o# |9 D         $this->ext = file_ext($this->file_name); / C+ r1 b0 A3 N& k1 r+ t) F

5 ~( N0 H1 m& \ G

. o1 ]4 {7 Y, s% N0 Z         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; ! W3 P2 C* X" \1 Q; b

' J" x) k( h# l/ J8 \9 K+ W

0 a7 r# H: [6 e* a1 R5 {& |4 b& c6 J         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;; |. `: y# E) [+ r3 K# W

3 G1 u* P& o% _8 W2 s M

3 D$ {( h% L1 G         $this->savepath = $savepath;2 o7 k; J0 }5 ~/ a: W! z

8 p: X2 k/ _) [" L

/ D/ K/ q) U& R# u* H& x         $this->savename = $savename; z0 T# u3 z8 h2 p+ l/ Q

* s- T# Y3 |' W \/ G: Q: j

( b- Z* z( X+ b% H5 k1 L     }}* L) I% l9 X. a6 o ]) u1 }

' Q+ r; A. G7 O6 N5 w/ X

4 F O& N, y1 }* p 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。2 n5 _( J. s$ s$ n9 k7 Y; h

! \4 J& n! X, p1 n) `

- @+ T2 @2 @1 y 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 6 T' U" M j3 G5 i+ c" Z

& Z6 R" |2 T6 [: _- B

0 N! x+ O* |+ ?0 M' J, i) P $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 : Y6 |4 |# Y1 w+ J

k; ^1 N* H: R! Y7 O% x

1 l8 u+ r9 I! x! C 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:8 L" B: s: V8 O) Q

" c( }1 y! s! y# Z3 J

/ a, s. ]2 y; f% m  # t' d# D8 K) M2 A0 d

# [" _ @" f0 ^/ Z4 F- K/ G( v, j2 `

" Y. A1 V3 ~" q) ]) `! J- b 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:2 d. j' n5 ?! }& ^

5 y" q% p/ ~, U# N7 Z6 x4 `

& z) @3 Z+ U6 U) J <?phpclass upload {- N; A' C2 K, }' S0 g# U

* ^4 C2 n/ B: r1 g* Y

. J8 g, m7 P M5 ]6 e     function save() { ( g0 e u4 E8 B b9 Z _$ @5 F

4 h5 U, Q6 {/ }' \* E

( F _3 {4 j9 p) o$ U- X: Q         include load('include.lang'); $ Y) \0 A2 ~5 i6 x. j; h* B

9 N8 |# m: Y- u) J. |# [% X6 [

, b3 l. [3 O5 ~' X( o2 {         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ! ~& z: d2 R7 N' H4 j0 G* f

1 j- }9 a' I2 Q4 s

& ]; [4 N0 U4 I' i! Y1 _  ) I0 ?4 k3 q6 e' q' s* b+ n

6 P5 w& |+ n* J5 D7 v. @4 j' T4 i

% _- M8 _2 j- L1 h$ N! H. L         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); * x) [8 `4 O. [0 \/ B# x

" U: M6 z4 d% h7 S

& }" t! P+ `; y. z0 t. Q  0 Z1 _2 K) N! ]$ u0 z1 `

2 [* }+ |, [% e' g

+ n6 ]* j! S6 W( }         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);- [! R4 l4 Z+ |! V

& S/ I) f7 v9 M, M

. E% y8 @" P. m9 j5 ^) [) b6 H# B( J   * L t' ^: k1 {8 J4 N

, H+ {7 A) y: r( a9 ]

. g1 N" l1 e; y* ?, J) q6 E         $this->set_savepath($this->savepath);7 G/ y8 ~9 R# q& Q5 q

/ l% V# E; y3 Q: A% g1 F

% `9 t7 ? o: ?$ v         $this->set_savename($this->savename); " p( f' z7 C# M5 |! i/ @$ a, W

5 x5 t u/ U/ y( `8 W

$ Y( W: H6 a7 q3 O, V/ {' D   |/ n P6 L! {! d" v1 B" V: Y

5 f& ]' [& F* E' U5 C E6 Q

; v4 R. Q( Z& x+ F5 E, H         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);( ?# h5 C8 G, M* \9 r) R

3 z6 V0 s& u8 r p1 u* x

% I" M; a+ v; c, @- X" f# I         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); , \: j" p. m" _& G+ t; C

4 y# E* Y; b% l- y# ^. I

" j: o& O$ ]' \         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); & S5 j) s' J5 o* z: ]$ v" {: Q

2 P5 P8 C9 V; @( e

- [0 p4 w, m: S: [6 n  & W* j; c! j- w/ j7 s

3 q9 ^; R0 o% h3 N# D/ V

. d9 M6 @( X1 [6 Y- P6 ^' A: X( C         $this->image = $this->is_image();2 X% \. o; v1 k* R A( C

) A, T* ~! m1 h+ @

" ]( ^- S+ c, r3 b         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); $ v% q5 R* D& G+ X- F b

8 F' X. h& q( E

) u# `: P% m' @- z$ X         return true;+ q, F0 I( ?/ W1 ?7 m" C7 u q

5 p3 F1 f* K r. y4 I

$ ^, p# ], W P- e# d f     }}& U+ ?" U: A! m3 X* L4 T

- J; \$ X1 O$ }

, _, L: W- g' x 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 7 K0 n8 }- Z# w" q( c: f

; Z9 D7 x4 u+ l! ~8 y, G

- N4 n$ j' D% Y2 X5 e$ D0 { <?php% f+ d6 d% e( M1 M' y- j9 r

2 L4 x6 d* X0 N" ~4 d% ~# F0 c# f

8 `% J: g" a4 n6 z7 p     function is_allow() {4 t+ k0 f) X$ j9 g3 X: C

5 t- J% Q- A/ Q3 K( A" H- _

+ y, p. a1 E$ I/ I; G& X7 t         if(!$this->fileformat) return false; % Y9 c# o7 P7 r1 |2 x! z

/ C7 d- z5 y) s1 D! b, ~# x

5 |! x& Z+ L, E2 o! S+ f- d         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; + z# R% C( u* N' ]

6 I- G# ?: g( A2 z9 R" b) J

1 x0 c3 f/ k# C# {6 E         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; . h( ^ d# N# z& s. g$ [

- w3 P+ @3 `9 c8 j

/ E! z% D7 Z+ w         return true;9 h; i3 U$ z1 x5 J

. h8 Z9 q3 c1 K- G

6 @% \- g/ {" G8 V$ `     }5 `7 o6 `2 d3 M

' R; J# O( I6 k* }" a% m

" G) B% \4 V3 z+ w 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。* _3 A4 X* E% X. Y1 E+ {

& s, ]) a3 l0 A+ P) u

* S, k8 A. h: G" h5 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文件。. M+ W+ A0 s# l

2 Z: W5 H1 B5 z

' ^$ b: d: v- ?: b+ F* O2 b$ I 漏洞利用- ^& [3 ]6 J4 {: i- \6 e1 f

# D7 A4 y: z }- |* B1 J; R

7 }! N. _& f, |/ {! k) d 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。) B \, H- z) \, C

' m5 ?" ^9 j/ g

8 s6 \+ Y- e2 h) n   Z% H2 {7 ] x2 t

$ {' s+ B, e" |( H

* _5 s# T# I P j9 ^ 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid % B% t0 q. s$ U5 T

. n" H+ ]* F. g0 a V# |

* B5 a9 S. G/ [! B0 s 不过实际利用上会有一定的限制。 . ]% X8 B7 Z% w& j

- `5 N- X& t) n) r% Z

5 z( H5 |& |# D g 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 0 x) ?/ r; P- I

/ V: z) t4 b' X0 z' I B) [

" W! A. k6 R) L5 c2 u, t# H U  8 p& J3 y9 a1 F0 s2 u2 ^

5 ~5 \* r3 L4 i `3 H: ?

/ J G; d4 P3 I7 m: e. P- m0 H 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 7 j( H' o% X1 l3 [, X

$ g5 ^/ U/ g, n6 D

0 t+ ^2 A1 ]" J8 w7 K 省略...$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]);省略... / m$ H2 P% g: p* v% e! |# O

% X& a( U8 j' T

5 T4 j+ S" ~" p 因此要利用成功就需要条件竞争了。 ; Y0 ~( u; h/ ?' p) V

; x2 g) x; U! z9 n

7 I p9 |7 x9 A! [2 S4 R 补丁分析4 R$ B- S V* z; O5 X' Y! _* D1 y

, {. ]! i5 D x& ^* V1 Z

: _! C% E$ \' L+ B" J( y! ?! m  ; h) w/ _1 k, j9 P

" ` u8 f& I( w9 d1 `2 p

/ C" Z0 H; h9 s5 n% H0 [0 Q% e 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: # q' ?$ K3 M! K5 ?

! R+ a Q9 ] ^. r3 A' }4 p, l

3 c k4 B* X% H% o$ u1 h8 v function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}( q: X6 o2 O: y5 L, H

% H0 ?* W. z" {+ q

% T* E/ ^) A7 W+ H4 Z2 J" q9 z   8 Z1 V# y" t3 M& V, n

1 u) s# `$ R/ M/ w

0 f4 g+ a$ u8 r. [' c* R( l 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 & Z+ U9 N3 `$ W2 g! z% T; R

, G/ M6 O" z0 e

8 j, ~5 H* Y7 Y. w0 ? 在is_allow()中增加对$this->savename的二次检查。 # Z0 \) {# ]; K

' l; F/ z! p; Z. A

! T" c- ?/ F5 S& F# m3 D( d' P n 最后 " b; ?+ Q$ }* c4 S) x9 d5 Z

5 Y6 m6 N: _# F' e: j

5 g9 O. [- b- E* I7 s* u 嘛,祝各位大师傅中秋快乐!0 h7 \! W' f3 O6 n

; y1 Q0 b% t" B

+ Q1 J, w2 I( v; z$ N/ ?' U7 n  3 q* y5 Q4 W( Y4 o. z

8 s6 a" ]) e) o% I





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