找回密码
 立即注册
欢迎中测联盟老会员回家,1997年注册的域名
查看: 1218|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
9 x/ \6 D! g L Z' j( v) u

N4 c7 ^3 h$ C/ [% r

) e* k+ F1 S$ A. O

: G& a7 R3 e# I' u0 ] 前言: D4 ^: {; \" v9 l. u) D' b

* F4 B; F6 v! T0 ^) Y* b

8 R% ]- q3 Z4 V: p2 N; C- h3 H 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 # ^3 [6 j7 Q8 m" u" Y% K$ R

# L7 g! ~, K" P8 T/ o

0 k- F0 Z7 B, H2 ?8 d, _  8 X, f5 m |0 S6 O- ]8 [

: p7 {! S8 o+ W. x: t

% Q+ o; y) {+ q& N4 v# { 漏洞分析1 P3 Z K5 A& t$ ?- t) }5 i8 D

' m/ w$ R% U/ o& b, L3 u

% ]$ _) r, K/ q9 x' I* Y4 T# u 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: $ x8 o* [0 ~. | D U6 v3 m

& U" Z- D, k5 `, j1 i

! ?/ W3 ~5 N5 f/ D( F$ @* e5 t  4 l; }% r7 S! T: z3 e, {0 H/ w

7 _' d. _7 `7 G: J

/ P u% R1 q q' ~: `$ j$ u 对应着avatar.inc.php代码如下: - u* b1 ~ n" k! O7 ^* L, p7 [

! c$ b) H9 r- r; a- W

) w: E& J; N0 e <?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) {. w3 O+ e+ S' l; D8 Q$ q

$ p5 X, s! o* e* r. H

U4 D l' ?) w/ ?( w. \% p     case 'upload':* e* t2 ` @& {5 n7 u0 f$ |4 i+ b

; k& x7 E( }+ |: Z& ^8 i* C" A

* g2 x! S# O) y) a3 I0 s- D1 T9 F         if(!$_FILES['file']['size']) { ) _* w* Z! F/ P) |3 U9 r$ X

: _* }, u' @* B, O$ k2 l

2 l @$ P2 [% x! J3 Q             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); ) i; ]4 K g& c0 R, ^7 M

: o- B1 X& U' V' `- V

% K; _3 V; M+ }2 y; y" H( R$ }/ W             exit('{"error":1,"message":"Error FILE"}');7 ^8 }2 R% S7 @/ K

( |5 F, h' S- M0 M

" P: a1 E6 f- U, T0 T0 D( p4 q2 r         } 3 Z8 D5 Z; a Z& R2 p

* C' u5 l. |1 O# m3 X( o2 L1 d

! D6 Q$ Q& a2 X: F         require DT_ROOT.'/include/upload.class.php';4 `1 b- w) x6 z9 P7 y+ T3 f+ [

9 ]# U) J8 q3 `$ w7 }3 c* d

/ M- q$ u" s5 O' m8 r1 I* T* F  - p; K. g( Y( P( J% U

0 k: K% R+ m6 m$ l; b( T `( |0 R. w) l

" _& L! P& @: V         $ext = file_ext($_FILES['file']['name']); 8 V( r1 T2 H) V* P; h3 g8 h9 U2 K

8 Y; |" T5 y: H" k2 ^' @. E% X& K

5 N# B' H, g% ]8 l         $name = 'avatar'.$_userid.'.'.$ext;3 I5 a2 _( s6 T- p) b' {9 R" \

4 J1 m7 b/ T$ `) k0 L+ z

/ |8 t F" f; E2 `0 \         $file = DT_ROOT.'/file/temp/'.$name; f2 O+ d% N A. _+ [- c5 G" m

: N- O' q- ], j/ M! S- }1 E; @! H

$ x/ y: e8 r& k* J! n: B3 b   # J. O2 `, N* _4 r0 o+ Q9 ]

4 j! X3 h4 {! S' C1 `" W

+ T. ?" O" w+ X. G4 ^* N1 _         if(is_file($file)) file_del($file);4 g6 S+ p4 `1 b# A0 ^6 t. H

& X8 x# `" R4 ^2 k( X/ N3 h

8 a' O$ @$ f5 q& g         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');8 |- @8 p& z" i" ^6 g" d2 ]8 D

8 X+ w) |; X V- b

8 U4 v. q, O7 ]% G  9 F" S% d! x# q# b' J( o, K& q0 k2 K

9 B$ g8 Q' s* [* B u& E9 y

7 R9 |7 S- Y! |+ N7 i x; O8 e; W         $upload->adduserid = false; 9 n& @# d5 |$ A% o, M* c/ J- Z" K

( v: M7 D: F! w2 R T& A

& I6 L5 {0 N/ W) `7 G7 e  6 d9 _& K- p7 C& ~. R5 L) r7 T3 O

0 W. X. @+ Z- @" {

' _9 \6 ^' E; E! H% x) A         if($upload->save()) { 8 \/ n, ?, I( L9 G1 |' ]* T# a: Y

4 p. S0 e, Y* d3 P6 c

4 a- X7 D2 j' b# u$ g$ e             ... ' b+ `! L/ J& p- v: ?' H2 Q

5 ?, }5 q' l; T& C# u! u

C% w3 t" b' X* G4 I         } else {! `6 K1 A- J1 J' j% ?

) ]7 C3 Q* V4 A2 X

4 g3 p9 ~, z9 N, O% D             ...- I( B/ u' ~4 m* g5 R7 U

- Y# V6 H3 H) N* N0 s0 F0 l

/ ?1 Z) h+ H6 p0 o         } ; f# ^1 G/ s2 J) x9 a

' v" G# s' r/ y! p M& t

0 H4 ], @. i1 K     break;2 R V5 I0 ]& V) [3 T) s5 p

6 q' q) e( s e# F) g& ^+ X

, j+ {5 l5 s$ I 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。8 {2 c' ]$ Z# x [5 C5 C: n/ B

; F! E0 N! }! d" S

d. d8 d+ }2 m. t7 ?* O' @1 N U upload对象构造函数如下,include/upload.class.php:25:; B0 l7 Y$ d3 E D Z. n

; D6 Q! b* y5 p/ r

6 h' `4 ]. X4 m <?phpclass upload {; L) r! i0 e* ]2 M

5 U, N, s4 P% n8 ?; W F/ c3 ~

f9 q8 v- j0 ]     function __construct($_file, $savepath, $savename = '', $fileformat = '') {/ n* u9 m4 J5 @

+ Z# Y- B2 C8 M: m# Q4 t

7 s8 i& X( q& T' u% n+ l         global $DT, $_userid;- k ]! T9 D, u0 b6 ^# E, N" j

S! g* j r" a) y, u& E

3 C7 o6 _! y; q4 ]* w         foreach($_file as $file) { ' v) J1 N$ L. @9 J0 K

$ G# c' y" N# G$ U, ~0 }

5 v- c7 h( Y# p             $this->file = $file['tmp_name'];4 ^3 i- g; _, J1 n1 H. w

- y+ V0 X; x5 P. W

" C2 {, @8 S+ k             $this->file_name = $file['name']; ^" S/ F0 P' x3 D+ Q8 j* J6 Z6 f

/ r" c s. f' O- i% @

$ D9 X+ ?. z3 u             $this->file_size = $file['size'];8 a1 Y, v& j& [8 \5 d9 q, k

! f% a& \7 w- G

5 M" h+ l" N' U1 Y; \             $this->file_type = $file['type']; " |9 v* ]5 Z, `- Z; z$ o/ E$ A

2 x, G( Z; i+ v: Q2 H# u: z- H

# d) Z4 s4 C& `1 ?             $this->file_error = $file['error']; : ^. U: s* c# P5 u m" _& R

: A m/ k7 c' y; G& E

4 C# D6 p S$ [2 r; ?9 [. ]1 A  # W- I4 b4 k) [8 n) _1 _$ X4 F; X

% ]5 I8 B$ |- l

' _- u. }) l9 U' A5 L' d         } ' K, {$ Z" c" p) M0 d

7 o$ E" ^" g) a5 M3 r: o9 [, u

# d0 l2 q9 g/ ~ H         $this->userid = $_userid;) F; h! s* E. F' C

% q: l$ `! P$ a8 F6 n1 y$ Y4 R' R7 f

H( Q- y3 B' Q0 o         $this->ext = file_ext($this->file_name); 4 m# h: \4 ~# {( h3 ^

- j% {* O8 z, ~+ Y

" R) }0 }, W5 I, h t* N- U3 K6 s         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];1 o; I' g- T8 t2 y9 c: e

3 e6 ^! j2 F- P8 x

/ X: E* q. e* E4 c% y         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;5 p8 x0 N6 q+ D4 ~

* T, G4 b) @$ X% }4 L3 Z) W

3 W" u& f: V) f- x$ }. H         $this->savepath = $savepath; 3 E7 l; b$ k( N9 c

) l6 A- [* m$ r n& U3 x" |( ~! e% L

, j' l9 U5 i7 `6 d l         $this->savename = $savename; ( _& \! @8 p5 p) K; U7 i" Z

/ R& _) Y# ] W. m* h

5 M# M3 }. ]* p7 D     }} 6 O- A$ A. X" S! z C) `

8 L# ^6 z" F' A R* {* _

! v ?0 p3 ^% j. k9 X3 c4 c; ^" k$ p 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 ' W' J3 `" w8 W

# F, R( ~% I/ ?8 ^2 M5 I5 q- r

0 D% L% O: S2 X b/ y/ _& [: m 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 6 A2 E7 A7 O( h3 T4 @

# G6 d$ U- G0 S

; ~3 Q$ S9 z$ r/ X: |- z/ L8 J; h $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.php4 I& F, y8 l' r9 y- N# Q) k# S

8 Q! ~7 }: Q; Q% g$ b# \- p

$ o/ V! I! a" y. \ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:4 O: f: h) G( T: s, Y7 c, w

: K# O2 W: |! ]* c6 s8 F

* |% C! V ?: B5 B8 l  % W% T, P, G, C

Y: y. z5 o; d" |! ^5 z" n& t( X& ]

2 i! M0 M0 ^; _8 _! d 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:5 ]% Q; O1 h& w% x5 C6 f

: j% @' i4 k9 `

" l+ R& p% E4 X5 [" o! } <?phpclass upload {) n4 V7 ]! [+ H& ^* ~1 B0 J7 k7 B- s/ z

8 {+ c( Z7 }6 q$ D9 B' x

h* P; r4 h9 c! f7 P9 n     function save() { $ f" z. T3 g' L3 Z

0 x' Z$ G" w- O& y# A1 H, X. [

# }' I. Q8 I. e" f* j! b         include load('include.lang');3 t$ N( a# ]: W

# `2 A8 V2 ~( p- x

2 U- W$ y# ] H2 G/ w0 \         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' v$ E; X, ^' |9 ]- r

. E" X. @. D7 V3 |* u/ q

+ A1 g- Q" N9 m$ p- J' k+ l  3 }) ]# X T. n4 k- c

/ D, a0 q: I3 T

, r" d" Z5 ~) _6 Q1 [& D! i         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 {$ ^3 n- d! M4 O: m" l& r' ^

3 o5 P+ g/ p, Q( t* |: K/ U

, g$ j+ G. m; U  + F; f1 A& y6 a

( m; s; J3 J2 W0 ]/ E

7 T7 y, m1 f. b" v- {2 F         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ) }* H5 U) B! Z: ~ b2 D

" |% m. e7 e+ d' f7 `

* u- ~1 l7 w5 o' M   + ?# L' b; s, |) D) k

Q# G) {- e. F7 r

/ e3 v: y* Z9 b/ _* f* m         $this->set_savepath($this->savepath);% ^- h5 f& A! c& w) E8 T# d

1 L9 n; V e. ^- e) l

8 I u7 W( u; ?6 I! X% U         $this->set_savename($this->savename); 2 {$ n0 F% W9 b' i& W

- w2 X& e0 c0 U7 s) K4 \

/ ~; v5 ~/ f1 ^& y  ! S$ K& ^. `8 P B, {

7 ^5 V: e: Q2 n- k) F

# \7 M9 |2 e; Y! f9 `4 a( O# g, }* P( I         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); % q7 |& [0 [1 _( j/ {9 ~

. h- P3 e( k/ N/ R% p% @

?+ k+ [3 Z$ U' ?" U m$ ^9 _2 }$ J         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);7 U' _/ C; i. e5 [5 _6 e

* s' H' O4 g* g$ c

6 |7 z, m3 ~4 w3 U/ Z         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); , M( i8 C* X% M$ c

6 M) d \" c. t3 s6 B

) P+ V3 |, N: M! F: H5 X& c3 D   ! ^4 Q% Q- r4 g$ Q8 N9 K9 }

1 B! ~4 d0 e5 ]+ X8 ]

! b: i$ C1 |7 E( d4 L0 y+ T4 x1 k         $this->image = $this->is_image();# {# f) O( ? z; m. G. }, _: Y

8 Q& y. @0 H8 ?) O0 e5 `

. A8 }2 f& N- P         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); - [" H0 n ~; j! M+ w- m

; u( [2 J4 E5 m$ [* [3 {# s, X; z

- ^7 l% v5 j ]7 p; l0 D& k         return true;, d. h* y9 Y' P4 v

6 k* G4 q9 U- U7 s/ t

4 w/ S; m+ Q4 ]: b     }}& p1 F7 h) P2 E7 U: q

/ [# Y: A6 C3 G" [ R% u/ H2 W

$ ~4 k6 `" P, O) r0 t4 _, A5 ~) f 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: & K' w a& X, A3 C5 D

: {- u/ d, X. X. A) U+ K

5 P! n0 i% y/ [% J <?php& y- ~4 J. g) a W# { n2 Q+ T

" w$ Z: |/ M* b) W

f. I- O% D+ t0 \$ p& ?     function is_allow() { ) G- ^/ |. O$ V4 Q

2 ~7 _3 [! K7 ?& `) O9 f

1 u p- ]6 D) K/ p! ], G) D         if(!$this->fileformat) return false; M9 z& Y h& S/ c* Z: T9 q5 g, Z

0 y) T* C' \+ y: W4 U7 q0 `& R

1 _2 `* V4 j. U# f' [         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; + r$ e" v" ^3 }9 e7 x3 q

" o e( a3 K5 K% y* ^

N, u2 X6 V8 A. D U: ?4 d         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;2 ]' B: v/ a j/ @

2 h# d3 p' G8 T% m

( J% ~4 X9 [' r7 P5 I1 P L         return true; # a% V* T/ e) e

5 `( x# y4 U0 ?. E5 O9 O

0 f0 ^+ ^8 Z3 u" S     }$ p# Q) F% V i1 U+ S, I" J

4 T8 b1 F; T" |- }

8 I$ c; L, U( r# H4 s7 C 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 ; }! y5 }2 C: b/ m9 E8 H

( [. r4 _ v3 ~) V

) r9 M* a. V% d1 Y. q% {1 u: Y$ ~ 接着会进行真正的保存。通过$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文件。 : |% O1 M2 o i# h# d

' e- i+ ?# [* b$ L3 y

2 Z2 {$ A2 O% D8 T2 n. N# n 漏洞利用 ( H& |1 s& a5 v) f* c

1 Z: H; m5 m: y' x9 i3 `

) V' ~! N C$ K2 l1 i 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 8 ]; f! J M# h9 ~" ^1 Z0 ]

1 F$ L9 _: s- {& g7 w% S8 ?

: b) Q8 w& F1 @1 }0 m; F2 ^   & K6 W- q* d2 P

9 d# p& g+ F/ M- H

6 ]/ m3 v) N; w) F. O( Q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ! T$ L/ K+ ]. M5 x

* Y& }# j3 p f2 K% A% I

; d( V. p- J- d: ], b: @6 c 不过实际利用上会有一定的限制。4 t# f# a$ e' W, B9 r. J+ e

' ?8 S# l' |6 J

% Y; x0 k) z0 m" E, u" Q 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 8 a; l2 ]5 G. W' y$ A0 v+ \% p

2 W" j6 {: {5 L* @2 [

2 Q& ~- B" f1 }+ s  2 c( P. g) j" L- y; V

5 B4 z8 q) m& f/ d

0 n* H& a4 b3 |) m9 e; S" j 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:$ M+ C9 h! ]# M5 ?

) v4 N: ?7 j7 l# ~ ~

, P6 ~$ R' i( g4 F' a" L2 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]);省略...1 W+ r s* |5 H, ?5 l$ o" g

, m" g% v$ R3 ]; k5 H

g1 j2 @+ i$ Y f$ m 因此要利用成功就需要条件竞争了。, Y6 s# n) E% a" }7 k9 _

2 j, l5 e2 u1 \

! _, N1 `- r. Y7 e6 D# t# ?0 o( J) Q4 i 补丁分析" q4 E( C7 u/ B6 f# z* z6 X

5 V( f$ V6 C: S0 @% ~

( _' [1 A( L3 b   % e, \. E6 n% M5 T

! B4 j9 b& M+ c0 }9 v t# ]. Y. b7 C! X

3 n) E. ?2 k: K/ b$ s 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: - e& ^, ?1 a4 g5 H8 F$ X/ ]/ V; N* \

T4 @9 f' o+ g- C0 S# u8 [3 S

/ T% |( ^; a5 A function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}- ?; s( v C0 v; t

4 o4 h5 r* x$ g- x; O5 O: O

9 s2 U$ E5 u+ z  # p; x) o1 q& w( s" B

6 h9 N1 {" n, l7 z- M

) Y* i0 W7 l$ _! }6 t) m4 n 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。, z: [$ b8 s! b7 ]3 y0 T

3 r5 Q! o `' Y8 `2 e" j9 C8 N

" e. N( k/ G9 B) Q4 `! ` 在is_allow()中增加对$this->savename的二次检查。 5 _+ H- c) ~% H8 z1 z9 [

+ u7 ^9 u! ^5 J, J3 t, v3 o

+ R/ h6 y. ]" y) i 最后& t9 H- P ~1 j( y" X

0 x3 c! {3 Y/ v5 [7 h. c7 F

( h( l" f: a0 e% X" r- S 嘛,祝各位大师傅中秋快乐!) j e. i0 W0 n( x' t/ _4 h

: k5 }" g$ O) M; t

$ V9 S( F% ]1 w+ A y  ! r( v0 X, ?% g* |

- [( `0 V! b( g8 f7 n
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表