找回密码
 立即注册
查看: 1545|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
) t) g1 U/ C* R* O& t# z* i

5 b6 @* r9 o% ~" i3 G8 [

( P9 w+ m1 k- s) b+ Y

6 L; I4 w* P& B2 F E 前言 / g1 ]; z# S7 ?) U F7 R+ E1 V

; S1 p' [6 @1 w# Y

! H: x) I7 e6 D m 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。6 @- u& u" @& ~/ `9 V R

2 _: e: c& H% k% J) f

7 V! R# c, W! _! H  4 G- M6 E0 a9 ]# e: V$ k1 B

$ y' I) t4 ~+ K Q4 `7 V) W* T

" z \7 e# Z7 u9 A4 o7 F# y 漏洞分析 ( y5 S% K! V# K0 E ?

' [& b A6 X( \

, \4 V' J$ g% @/ g 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:3 t7 Z: b6 L- Z( T0 E

$ B! B, r; v) S% w! b+ q

/ Q1 ~4 L; z* R2 }8 p  ; W g$ i, Z- y$ G/ l

4 t* q. p( V' {! }! f- T" `

2 M# {6 I3 q* M8 Z% q 对应着avatar.inc.php代码如下: 2 B4 m/ {# t" g9 {- q* n

, H/ q) G: z- ?' @* g

# N0 ]9 |: @0 v <?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) { : Y3 F# h+ U. g2 a" @/ H

% G0 f$ N9 i$ R! J3 A

0 ^3 x# E5 X [8 K. Y     case 'upload': 7 X' g( z* O/ Z% a5 Q+ x

2 r4 v/ M- j! _7 y. e6 o9 z

% a% K: a& U: c. Z# g E! Q6 X" K1 S         if(!$_FILES['file']['size']) { k2 P% b! G! y( @, X" c; v

. n4 e r$ V8 v- e l. T6 ?8 \

2 {/ G1 A5 ^3 _" C# y; p             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); ' H/ i& k1 R: q/ Y5 }& g [( n

- [- o1 D$ f+ X' }

7 t: L, q) ~" W$ @0 e$ j, S             exit('{"error":1,"message":"Error FILE"}');- X- m: F6 `9 o

& e- U$ L: K8 h( Q* Q) w

5 R* N3 E- x* T         } ; k; M; Q1 k, R7 K! s

4 f8 p) U+ E5 \3 i

7 j3 K" n0 n" H6 p: I         require DT_ROOT.'/include/upload.class.php';% |; ?/ R, h4 @) r" H

e% B3 Q, Y8 ~

* A& P0 ^# e) J$ e   5 A: v0 u O7 [, p

9 b6 x+ C. F7 Z0 R/ p

9 m7 q$ ]7 a8 L) N$ w# ^& h$ s         $ext = file_ext($_FILES['file']['name']); ' a) {% R0 o% e o9 K

6 X3 P" Y5 y7 u" @" A% G

, i3 i n2 X8 M! M p- m         $name = 'avatar'.$_userid.'.'.$ext; 6 d0 n9 i! d4 |4 X; Q/ i3 m2 x

! X, K. l6 B3 c7 j) x( B7 k2 Q

7 N; }! W( s7 L2 Y1 @0 a* s: K         $file = DT_ROOT.'/file/temp/'.$name; " }: p; G4 v7 V

( V. y) }" u& \8 R& q4 u: e5 p

! c2 x+ o- e* s4 V   4 @$ j% V+ v/ Y1 k

* y0 `2 u2 r5 m2 @+ ]1 Z' v

8 U+ O* N9 _- X A0 b- j         if(is_file($file)) file_del($file); 1 o, O+ j8 L( S7 l

; K' ?. P' a6 ?" k- |+ E! ]

q& [. t8 A# A! |0 O# J' t0 v5 `         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');1 l7 O& L7 j/ Z* ~

" A+ ~, u' b7 e% Q" y- K

2 |5 L. J% ]+ ]9 i! B   9 G" i$ r0 L/ X5 a5 z8 c% u

; N/ X& j9 m1 {# T' K. c7 d

V' w1 Z$ e" @3 ]         $upload->adduserid = false;) b; Z8 v5 y- q: l g7 U

4 o- g- w" S0 L8 a f

1 m: T" V: L# i- Q: X  : ?" u; Y! g" e, ]# e4 w8 T

2 j- L( }3 [1 J4 v6 h& D, i

5 F# N" Y+ o6 X3 q         if($upload->save()) {) ^) j! D4 L# l" R

/ j- }" _4 j+ j1 V& ^

. C, u3 e8 f8 l* F             ...6 m) K/ d7 Y; D8 D

7 H: v- q( t7 n8 h) j

7 z1 n& [3 Q& v         } else { K ~, ?, t$ C& x! X$ C4 q" j7 O

: G8 Y- X# \! N) P

: g' `6 j: ?, W5 \. g4 K4 o             ... 1 e4 t& t, O% ~6 P# o

) f. t! a4 K2 I6 H

! c9 t+ e; w' q) F7 l0 A1 S. U         } % X3 E+ T% r. [9 g, |8 k# M+ } R

2 B4 \3 m# b3 }. b* W1 A0 R, L

6 L+ s" a8 f' z: ?& @     break;. f, A$ B" B0 z* F# A

" s- i% _, m6 [5 q/ h# `3 b

+ r$ K D0 Z' g" H/ V4 B* @ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。3 p9 X4 \% [* F/ C6 `5 p5 j1 t

/ V2 E0 C3 q, {& T

) w+ h, C5 d8 D$ @) ^ upload对象构造函数如下,include/upload.class.php:25: 1 T0 |0 X6 b% Q D0 z# q$ v2 `

( e1 u- a% d# [

U v Y1 L) f2 B <?phpclass upload { + J% c8 n I9 ~9 O6 H) F1 k! y

2 E$ N% Q' r* E

* S/ w4 p8 i/ o* { F0 n' F& P     function __construct($_file, $savepath, $savename = '', $fileformat = '') {2 `" {+ z9 t' p, m* b* D E

- \8 g0 d) O+ F+ G# I" [

1 x) @" _+ t7 e! t3 s         global $DT, $_userid;) N- \' ~: |. @

! b, y. _# x+ G) h4 b" { [7 B

& n9 Q; B8 l% v         foreach($_file as $file) {% a$ ^. s+ d( `# ]" C I

1 b% Y9 j4 ^: l g8 o3 a

, w0 H1 A2 x5 Y" H# y4 @             $this->file = $file['tmp_name'];" y4 i3 i9 e8 u _# C7 c

' Q7 z2 t& Q$ k6 p

9 e/ H2 F! B2 K5 U# i             $this->file_name = $file['name']; 8 l# ?3 e! p! P1 R9 P8 V$ c4 g, T

( u+ _! [1 h. `7 O# G) @

/ s4 v& S* H( V& W7 ] d. A             $this->file_size = $file['size']; , M9 u5 W z8 s% j* B

( S/ W6 o1 e# f2 U2 N

) i5 _3 I0 O) H7 ?) | M. f3 A8 L             $this->file_type = $file['type'];4 H2 |* O. T. |) m B' ~- W

$ V* @ v8 ^9 f: [

6 j. V( U' g9 _5 W( w, Q4 l: t/ c             $this->file_error = $file['error']; ) _3 E1 S# `4 A. l

$ L% Y; F1 W7 u( p$ {

: _6 g# v4 `. }9 p# X7 z: q   + R: ]- S' ]* Y3 e& r2 z

- M; E2 K7 p, @7 [' S

5 Q5 Z! a' J( m- ^+ ?         } $ }9 r9 X! |' B# e9 b$ R

( ~* s: H1 C c5 ?! B

& `$ w8 X" [4 ]5 T+ b3 s         $this->userid = $_userid; : T& \+ Y7 u* v" M9 w1 `2 t

) {% ?" y* B t- X) g0 z2 t4 h

& M2 v( a7 u( ]" Y7 U" x         $this->ext = file_ext($this->file_name);3 Y2 L1 n ~$ L2 H. t7 l: ]" b

# E! z' K/ l4 v; r' q; {5 T& s$ W

/ M! K# V: B0 e         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];' O* a' Q" Z3 @! t

* [: s! t9 R, ~: E+ [8 J% b7 S

. A6 D+ w* D1 Y         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; V2 ~6 p d6 k4 g2 \1 K% D

3 \9 B4 x4 w; v$ K8 E5 H

1 M& w+ t0 P/ P$ i, n+ y7 X         $this->savepath = $savepath; 3 J8 I; L2 U9 l/ r' x& O

. t4 b6 X5 L% t. h3 o% N2 m8 {

6 l! N1 V" D5 v' l0 z         $this->savename = $savename;8 I9 h0 W+ u/ u! m( D/ I) C! q/ E

7 P, W8 Y2 U& G% o' o9 G. p6 X

9 K5 K- ], z3 ]! z" o     }} % y% \ a/ n4 b7 U% G9 A9 x

1 S9 i: S$ ?/ u: r+ D

7 ]$ \$ A, H) Q& V* i 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。3 k& ? b$ o2 H# s

. t; Y/ g1 d O( B

$ X) ?2 Z, L" |: V6 T% N0 L 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 & ~* k& Q5 F; S# G; F3 a

* ?4 c( D7 X; g; k

- }" g- h2 U F $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 c5 D+ L' |& o; Z9 [

! v) x# H- M1 A! K

: m! k( H, ^# r1 r: y2 n& a 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:7 ]( v$ s( G9 h, U# l/ ], x9 n

9 q' {+ y- s1 L* t: n( R

+ w4 {) D8 k' J& G% E& K  0 q0 D% @9 c3 Y6 e7 ^

1 z6 b, ?6 ]& a1 ~

6 E. @% {$ k' Y& s6 d7 v9 K$ g$ f 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:- L2 \' [7 Z" V7 _/ |% j( m

( ^4 E2 J! k t1 X

1 q, ]& P. @7 m- g v: |0 E <?phpclass upload {; X, Q( P B. W/ e

. r5 H3 I5 T. K; P

- M8 ]6 J( k/ z1 j- g# N+ s     function save() {& i i0 a7 U* t0 o) c

& d5 s* G0 i1 |4 `' @

9 r) E; }; g1 B8 j1 v4 O4 O         include load('include.lang');- ^8 l0 `6 f0 G$ ^8 W

0 f0 y7 q2 O4 v! b) h) s

' r" x7 K# @6 B' }1 G0 J; ~" t         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 1 u) K& A! \* M& j( }

( h e/ x9 o8 g5 N

$ v. V6 O! t9 N' u) j: T7 ?   - D$ b" n$ h9 f4 p5 i; _% j- |

4 A1 e/ C a* V) u" W/ @4 L

! a+ G# c9 R& g8 I4 ~. C, u3 a         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 7 k b n0 p* W4 P) v

) t, C' b" B) g& b2 T6 G

( v5 S4 a5 ~+ q5 N9 ], P1 A) K   . i( r9 p4 g( O* `( F

# X2 b5 @+ [" M0 G" V5 \

, B( E( K1 y$ F* u5 u         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);1 J9 I8 f$ {& A

. B9 b4 }3 j; P

: ]& J' G( |; U2 n1 f   " E: `; j0 C0 i* o

7 @3 m/ }9 ?2 G+ k5 ^

# @1 W/ |2 {, c! N. _8 i `9 s         $this->set_savepath($this->savepath); 0 X( a+ p+ ^( I) a( T# W% z- u

+ n- O) U; n* N4 v7 u

: e: s( P3 M- ~         $this->set_savename($this->savename); Y/ @9 O8 `$ z+ r

. t0 t: r% A8 y

, p6 n" `' ^. j+ A! B* ~ Y  + U. V1 X a# `

$ X) J5 I% d( x' l6 t- M# ?3 }

8 R6 l- @ i3 H5 b         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);; R0 c4 B2 y- }/ w8 g0 N

6 _2 I1 J- P6 W9 N7 ?

% B9 ?+ r$ X) d, Z4 p4 I' u6 Y: |# j         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);- Q) }6 V8 y$ g3 N' B

) d) r2 z8 A) u9 L" p

N8 n" U: e) C         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);0 J* t3 N' @' e5 F* p- V

. O9 w+ L+ D& s' r5 T4 Y6 N

' Y7 w! w4 a8 @7 K   & c Q6 t( X, V" j( t- J. w! ]

( E% C9 M/ _. D2 N/ H% w

8 h. m1 _$ n, p$ C! G         $this->image = $this->is_image(); ) S3 h! e# Y8 |! |, i2 A

5 n- T. K! w3 ~2 u

1 K/ |; K; ] S+ L& R# ?9 t         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); ) a% }. J6 ?6 X! f! Q/ b! N

* ]% I1 o% l( \4 p. V

4 n$ _3 ?! a$ y3 \+ J         return true; ( d$ D6 t3 i) G' b0 w5 B) Q( b( z

3 Y2 E# r0 G8 _- c( m/ Y" P, o

g: _- }" H; |! {$ L& K     }}' t' z' X+ I) L. l( _( I" f6 T, M

7 H S. o$ e# F w4 R

( c& y5 r1 b: P# M6 X 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: # | x* r6 a+ a3 k

" O3 a" M; _& P& [5 U N

, s2 O% ^9 O0 ]% Z" ] <?php 3 [: i% f2 q6 P- P; l. ]! D

. e5 q- u9 V5 @3 W

3 V, M/ l: e# i0 V3 ?" U     function is_allow() {5 D0 m3 ]/ K( ^

& f0 j8 t7 {" D! E# O7 w( Y0 S. _

. O/ L8 _0 ^0 {/ h n* S# ~4 c" S         if(!$this->fileformat) return false; ' \) j- Q+ r5 o7 V- _

$ n. o9 Y8 o0 z; a- y

/ Z" u4 T8 t* ]# b$ `         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; . h7 x8 i' i/ N8 e$ x

9 d D" I" G( n6 T" x# V

W( I* g0 T7 R- y, d9 a' @         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; 8 z, R( q- [2 S

7 O% G! y8 ^: n% I

% p9 l* \- k1 c" P& F* ?         return true;* s7 R# P n6 p% M4 m) G/ q

i# S: b, g6 R I7 P

( ~, e( O+ B: j% P- ?, d% N     }8 n. j2 z/ o9 t1 c+ U

* Z( q' |- a7 r5 d4 Y0 t( j/ {

" g( _8 T( X" Z5 b0 i8 s- u5 X, E% ^ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。. ^1 N8 i# |$ ?

/ A( v" }: m: n2 e/ V7 t: _+ k. {

0 J5 ]4 C3 g6 j 接着会进行真正的保存。通过$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文件。0 ^! f% X7 j# a s1 h3 K9 R

$ `! L s2 u, [5 Z9 b0 z' N

7 |( o, \; }* n* N 漏洞利用 ! \( i9 X o: Y& |0 L& ^

6 ]1 f- o; ?9 {4 \9 G8 B

( F: |( \! `9 f# h) m 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。% s# f0 u( K( e. T5 b! Q

+ A( s1 r8 L2 N

~6 i! R) m$ ]9 v$ @   ) L" Z' {. I, w+ t: ?6 r

* o9 Q" R' Z: |8 W% i# j& R

: j. g, N9 [* [8 U 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid. h7 Y2 e( `6 X8 b8 b

0 M$ h& K" v0 y! K

9 b8 O& `; R' p 不过实际利用上会有一定的限制。 , ^2 X1 n' S& r# O

! S8 v; F# m9 `

7 C2 K8 L& f" B& u: A: x: ~/ J 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。. M* |4 r7 ~$ @9 f" I) Q

+ x5 W4 v3 t9 n* R ?/ K

* E2 y: t; _' W# c  0 f5 j7 o% Y2 z8 z$ B9 g0 i; i

- x% B# W/ ?9 a! f9 |

; k& b! a8 {9 U+ ], [) p 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:3 g/ J# y: ?% q* \$ u% {2 i

+ z) h. @9 u7 A5 M

3 l$ F# B) p: A: @( D2 X6 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]);省略... + ~/ [5 k7 c6 I" @

1 R" m/ n$ n7 c# l: M3 B6 D

% G7 @' b4 v8 m9 J, e' ~ 因此要利用成功就需要条件竞争了。* U! |$ F* V; v/ [* a5 ^

# z H) q; y( h: K7 b

: Q: k1 J2 [% r: U0 T* `1 A' v4 k 补丁分析+ t2 z: R4 d9 l2 z; U; K6 k% T

$ i$ D9 d" ~6 @1 P( t

5 k2 ^: }2 H6 ]; ^. Y  ' j5 u* \' n; H0 f1 ^

H& ~0 z1 D! ]( u: q

8 O- u4 `) |5 l/ X( _" i 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:4 f2 n4 K% | v2 j

! p6 i( k9 h' f5 W. p' l

- L V7 s3 O- P function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}5 L: h# v. U. i8 M* q9 u/ k$ k

3 D$ S" F8 T4 R' o" V4 N

+ L$ q; [7 t1 _ b   " R1 H9 N# ` F# O! b2 u, J

8 ?& o2 s$ t4 G

' x; M' V& ?* F; [: P. D 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ) u6 s% Z! X: t) c% ?

. x9 i$ u, X5 h; D$ x, N

3 `+ o1 d* R# q 在is_allow()中增加对$this->savename的二次检查。 & [* G& R; y% E: d0 o

$ S! C |# B. m4 L' n

( h& f% j% Q8 c1 l 最后; S6 {- |4 f. N. L) C

/ }2 v3 L/ @$ s* ?: B

2 \0 J: }& B( n, q3 [. ] n2 {' T: T 嘛,祝各位大师傅中秋快乐!# }/ j7 Y/ |# p

n M3 j2 q+ f3 e/ j$ y, R4 Q" E

8 ~, }- K* E( i  : t/ S# H+ G b

4 H- E e8 U5 K6 u" F. N
回复

使用道具 举报

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

本版积分规则

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