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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
, w+ L9 ]5 r' T! X1 G

* i4 n& Y( y2 ~- E8 g7 j& S3 O

' F2 o. a. W4 q, l) H

) l [' y, M! X: l& I S 前言1 x! U! U0 [( y( b

+ M6 x! n$ G9 I. k

" J( L5 N7 q+ a) _ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 5 j$ H8 \8 v: D4 B# e% `; V

0 d8 T9 w+ z+ S, Q

9 _7 @( S! [& O9 T; }! O. A   7 B2 s/ }& n8 a/ v) a

" @3 L% b% ?9 T8 s

7 w) y( U( d% d 漏洞分析 6 C' y7 l: ^- r6 i- g& E8 S, `

' f3 V# c/ v/ I

1 j; Q# r w* R8 q' G0 g 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:7 ?3 x+ h" u. f) b5 {0 V' p

# y4 o/ ?9 X- c6 x

) d" l+ f1 ?, d1 w3 O5 G, g3 }  - B$ X0 m' ?) o) g

. w$ h& _+ Y7 [# V7 S

0 k0 f& t! i5 k' U$ P+ M9 [# N 对应着avatar.inc.php代码如下: . _; k7 O! \( \ a/ W+ z- u

( s' |0 j; n7 o1 }7 f5 l5 e4 w

2 y, t9 ` j& i6 u; b" C& y5 q <?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) {# |$ Q7 K3 D9 s

6 _# `- C! _. C6 n8 H3 {

3 U% _3 A# i2 K+ L. C$ j9 R; z     case 'upload': / W$ o6 `; d' N( h. u

A% U! V |" {, S. f* s' T

% t/ S* U/ A* p+ ]$ w         if(!$_FILES['file']['size']) {; ]" [6 G2 r" W) X5 n6 U6 i

$ L- a \2 Z6 Z

. J- d1 G. `, k1 b/ r; |- H7 s             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); , [- w3 p4 [8 F9 L5 O

' V! H9 w: U$ \( g; j

7 [+ r1 d8 I" f+ t9 A+ p             exit('{"error":1,"message":"Error FILE"}'); ; o/ D0 L# Y4 ~0 ~5 s

- v/ q1 A+ U" |/ s

% `* H q. ?4 N% n. {         }+ x) ]1 |8 I0 u4 r! V& Q

! {; {: b4 }$ m8 \( _

" p7 e- u, J3 o' z( y Y3 M         require DT_ROOT.'/include/upload.class.php'; . J9 C+ }0 g8 j( J' b

: K# n; e& t+ ~

2 |2 ?' Q$ @+ q. r1 h; C) q: H4 l6 d  1 b) Y. ?+ P4 Y% Q' t1 `) ~

9 h1 d# g" x8 `, t* h9 R5 b1 n

1 g' L& p" E, m; Z8 R$ N         $ext = file_ext($_FILES['file']['name']); * e( m/ v, v8 \' R- b- _( q

) b9 {: s# M" ~5 F" t/ D* m

g7 S$ z! p" m& G! k/ ~! U* l3 v, Y         $name = 'avatar'.$_userid.'.'.$ext; . |0 Q2 `& a# _

1 p( B2 r0 U1 Q

; y& [5 m' w* B: k# F1 u         $file = DT_ROOT.'/file/temp/'.$name;! d& }' d& i2 J) u3 i9 X5 A

! q" [6 W, T+ }- g; E$ i% B

' S+ w5 j8 \4 Y( r& \  0 c9 o& A/ V* ?& u

1 g# n- ^) e2 P" _

5 ~) S' h# I7 g2 e& l5 j         if(is_file($file)) file_del($file);- _% g" z+ H, l% @: r6 |; K

, ]2 t- C) {; ~3 g2 z7 b0 f' z; p

- S+ M+ i# J1 h! U5 o         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 9 }/ d$ v* I/ y! Q. \

# @& j2 Y$ r Z

- i( ~) {' I" ~  ' J& F6 l1 S( W3 l' w* W- h* W% {

5 Y8 M, M2 I/ l7 w4 W

+ O# M6 z4 O* x: L& b5 e$ n         $upload->adduserid = false;; _% U' \9 t0 `9 v: N

9 B# J# i" E# h0 G" }

$ v; u5 }$ }' G6 Q& P- ]( I# G6 _7 W  ) }) l% Y9 K) J$ H& @! V

3 u( v: a2 d/ w+ a) C- ]6 l

1 A* E3 m+ G v4 Q& h+ ^         if($upload->save()) {$ c8 T3 a. R5 h: O+ X+ J/ x

- J; ~2 k5 M# i4 j2 [

. `8 P' C( Z9 V& j             ...) _0 t8 ^' w; a% p7 Z! g& Z5 P

+ p% W6 j& R4 R* p) n

+ }: f, K1 k: j9 Q! Y         } else { ( q; ~# a! x# c0 S

+ X# p/ _% W+ _) ]

7 H$ x$ O! e" V! w* A# |- u             ... P, d, z: n |% v6 g" H0 k8 S

0 B9 m! q4 K& p7 H# a' t6 R

7 t2 N8 G. D+ ?" v. K! }         } , {; e2 Y, g( s0 F' Y9 o% |

' X8 s# V3 f8 ~, J! m% |4 q

- W: I3 l& x6 Y; i& E7 U: T$ @! I; x     break; 6 T! b7 V9 V3 b- H

2 z, a+ i7 A4 ^" x# M1 d h

8 F8 v7 a- x8 V 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。* M: F0 X4 l4 p8 R# y+ V

) |) [( o I% @2 y% @

& W* ^# C3 Q1 N1 \& j upload对象构造函数如下,include/upload.class.php:25:3 t3 t3 [; `( Y7 g$ N# z

! f- M, E/ _. d* N% y

, M9 D: J5 m. D+ G' k" X2 U <?phpclass upload {( m. W8 h/ I+ H- x: w

! t& D& O& h$ E$ v* D

" V4 v" W8 X" O     function __construct($_file, $savepath, $savename = '', $fileformat = '') { * A, }2 k- a- ?1 ~, C; c

0 u/ z( \/ O! c" n; Y: t

9 v1 Q3 g! A: ]6 n o/ ~+ P7 F; T         global $DT, $_userid;4 ^( I4 {. N0 s$ L( w

8 j# o, f* P# ^4 h( f+ l# N* G8 M v

5 W2 X* v, _8 F( b         foreach($_file as $file) { , e% f! O3 \: y2 m* J

0 \1 a0 l* @) |; [ a, b! _/ k) i

0 m3 \2 x; ^. c' G' d             $this->file = $file['tmp_name'];! z+ Q" I% V0 Y" g$ `

: ^) K9 x0 z- g

, x$ F% K5 ]' j! B             $this->file_name = $file['name']; 9 @; H w1 T) |* a; H

$ a* X# \% J4 I& ^4 O) Z a2 b& N/ ]& K

- P7 R1 {) Q" e. {) V1 J             $this->file_size = $file['size'];0 N! ~" C0 Q* A4 ]5 L# \, j% ~+ {

9 A3 ?7 O) G9 G$ w1 w# f( l

% Q( M6 @2 O- B+ r' K             $this->file_type = $file['type'];8 u" A) _" G6 i8 o" p

0 d# v' k. _ Z3 }$ H

. U8 n, S Z2 ?; z9 Q5 I             $this->file_error = $file['error'];4 k. @% {9 S* G1 r# Q J

4 r7 z5 X6 d, i. o9 z5 c

0 M) x: x, v5 Q2 K( `% |  3 i R& b9 U# [! o9 k+ |; Q

# w! b& d. t% R) G* Y

7 Z1 z1 N; X* M+ _         } & r5 W( G( s' H. G3 n

" f1 p7 w8 b& s

2 ]$ \5 T& o) d1 g         $this->userid = $_userid;+ v2 O" j+ d: p9 `5 ?' w

) Y! {0 _1 g3 Y. R- p! R

) M e$ J9 i8 c) P6 U         $this->ext = file_ext($this->file_name);. H+ d" Y6 E* W3 C/ U

8 _" @& R0 S6 u- u6 u

s* _! w, O* t, o$ ]# i         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 8 J! b4 z" b' ?6 p# Y- b/ a* W

% t9 V* p$ m9 ?

0 T: H0 d3 A4 |- ?+ r         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; K! p: ^9 |" Q$ I+ d+ W: }$ N& S

" m9 |' t5 f: I3 z

/ l3 E' ^# c7 E         $this->savepath = $savepath; 2 T3 l4 |4 y& X! G, b @/ D' t

* F x5 f3 a6 C5 ~

7 M# `& Q* A; U4 l6 |" n# w7 n# m         $this->savename = $savename;4 V6 {3 J" s8 }$ I, }

# {5 t" ]5 `( n( ]0 y% Z! J

# h: c$ C4 K7 e" R     }} 3 Q# B2 B; L7 g- r' r: n

# M# Z% P* o! G! ^6 S

7 K. d: y* C: F 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 $ b; S: q6 ~# v

+ K' }: n; P% g4 U1 K* O2 v

# v. d/ Q4 W) e* I& `" U) i8 P 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * X* X% ^ q0 [' m( {

/ t- ~" i! s% N+ r+ W2 e

7 t8 R' f! }0 R R $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# o4 @3 I8 j. U' K3 x- e: z

- a; w0 \* r/ J

+ j" [ s& M8 e" B- `# B& { G! B 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:! k: e; P3 ~. c" n1 X1 b

1 |$ a7 p; J' P4 C

5 S0 ^/ P! D) D, n: O   ' C% G- R0 q7 _, ^9 b

$ ?" }# [) }- [; {9 g3 H3 b$ g

( g, P- O! ~% w' U2 k 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 5 V# b0 L; R5 i; W/ y* d

) V0 c! B+ v* L, t3 m( F

1 f$ \! C7 {/ J. U <?phpclass upload {2 Q9 A; @3 \, p' L" b/ b$ ?

% l" N$ j7 Z% t+ e

8 B( ^$ A8 Z0 t0 F; `& j' D; X9 [. m     function save() {( C) B3 L- `( u% f; b

) A! s& g7 i. d o' }- f$ i

; y: U7 o% ^" k! a1 D, [% s# f         include load('include.lang'); j4 F- b- f, {

; W# j1 M+ F% |8 x+ D6 G7 f

( \: m1 j( l' { _         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 0 `+ |0 a* P- d8 O* j Y

5 q" q' p- D; J

+ i0 ~) H$ p( l: j7 E8 g7 _   ( w& H: s @& I

?$ M# H* C+ E: j

9 F/ P" A/ P. m         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 1 L& E; G% j" y2 b

- B" X: o4 G7 z% |( @/ m4 J

4 j0 k. G. y$ e, x9 {   0 H* v, m; v) Q8 b

1 [" F9 c' M' p' z' z

" c7 Z$ d* ]- }: K; G5 x         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); + O p- t* W0 |& M4 K5 Q- {- j: G

. r1 y+ B& ~; v; f3 D

" Z, v7 m- e( E& t  % D; j. F" d* D7 T* u% W0 C8 a

8 P5 m8 s4 q) b' Z; z9 t

, J( x6 T9 Q) ]; W" {3 _         $this->set_savepath($this->savepath); " u, F6 r: y* g7 T, {

& H) w. ]! ^% }- Z

0 c1 j1 R! i% C' f- l         $this->set_savename($this->savename); m7 w# a( d2 y2 }, ]) x* M

: Z' d& b, X2 V. K$ o

2 j' D* q3 @- q  4 I: A' D2 F% u Q+ x* J& X; N9 y

$ ~; x5 e1 Y2 g) h/ L, X8 C$ {

% c9 A6 y- W7 S$ [: F         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);4 K4 ]& T$ L+ I% i" A

$ d, ~4 \1 n5 [+ v4 m; |6 D! t

1 I8 o$ U0 O9 \8 _+ @- A! h- g: c         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 2 T- L$ Y& `( H! x2 e6 U2 L ]

+ a2 O0 D z! [

G7 m. L4 M3 y! t- U% d. d         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); . k" _) F# x6 H

0 f# s/ z5 i5 J+ A

3 `; n4 j6 l9 R8 x' I( h   / m+ C0 d% F( |$ }7 G# u; r( V

$ I6 f8 |% l) {. `* n( f8 V2 ^ X

& }, {( s7 _9 |, l" B         $this->image = $this->is_image();# _9 s! `; t8 E

: E& A6 z8 c2 d9 @; O Y

' C" z0 b$ n* B: S         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);( c. o e! F; }& i

" _, I9 I5 ]# @

) J% c; Z% \8 E" J' [; A- ]% B0 t         return true; ; U6 {! V4 A* `+ m: d# k

! q9 {: T5 o: B# h

6 Q! A6 @1 e1 `* J, c W" y5 P     }} ; m' I! ]8 Q! C4 E

- X" H; S7 I+ d c

' d- `+ A, } |! q- v7 `7 {/ w7 ? 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: p1 ^3 q1 Y! R3 m* R: t1 y$ t

4 K# J8 _0 l# D

5 ?2 N( \' q. u6 V% b5 ?4 _ <?php* ~( q4 \* L; d3 l

+ M8 A5 c' M, D

, v$ l5 M+ H: K8 o4 F& c" Y- B     function is_allow() {) J) I* \" w+ w% {

9 H5 f$ G) b8 @5 G: p4 S

7 J8 ]+ {- Z5 ]& T         if(!$this->fileformat) return false;9 c2 X. [( R* g/ T

" f/ Y9 t9 U8 s( n( r3 O

8 m( S B) e3 L         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. {. X) \& {6 a B

' {/ l3 z, l4 ?+ I

, {: y5 [" D% F. n% `$ P/ x         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; $ q' n: `; _2 G% a

' N3 M6 }/ Z; W" A( f) S3 A

- _5 w. @5 }6 {# R+ c         return true;+ ~/ {8 @9 W, r. t/ H- Y

5 s7 s$ w- ?7 j9 }" M3 t* s

% Y* Y4 f' Q1 m, _/ J% M5 u# V3 j     } * |* I2 m. |. x$ r+ ^' r- J

8 m. \! ?3 p1 s( w5 E4 ?. t9 i

! |7 W0 z+ K$ c1 I; C 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。7 g! ]7 j6 X9 {/ {; R( D$ f

% p* `3 G- h5 O- k9 S

6 \' z+ G" o/ x 接着会进行真正的保存。通过$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文件。 ! l' ^8 j* A" Y0 Z8 m

6 B, e; b. j/ B0 D5 O+ {

1 h/ u2 L* u3 k: K# Z9 S 漏洞利用 8 M( S4 e2 [3 [2 ]) x

U: X- ^7 Q+ I2 i, v

! X2 X F* M6 M0 ^ q- i. a 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。1 v, [# U1 f3 [5 c/ k; X! A" b

' Y0 v; X# \5 c J( D& H2 m

6 i. h- h% Q7 Z; F   9 n3 f( x6 X _% U

4 S# S* F! M$ U7 @3 N

" I$ g) Z: p% q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ' N. A; p* `1 G& ^, G7 }5 T

& Y% Y- R+ V$ U& l

) O( [5 c, J1 B, o$ S 不过实际利用上会有一定的限制。 2 U* h8 H& P7 a* c1 Z# _) B2 r

* F1 {: x' ]1 y7 \

$ V, z! |' ?0 g. t2 V+ B8 R. g/ Q 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 9 k% y$ ~0 g8 c

+ Q- U) H; ~8 S( a& c6 z

- b: U k( E& n) C5 N  " c; h% G+ g( e' J

. u1 K4 J% s- p3 Y5 Q

0 b9 F1 I: f8 ` 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: - i) f, S" E9 _' b

! e9 \+ l& G) k, G5 b% d

: O2 m5 W% e, G% F) L/ { t 省略...$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 H, j. q8 M3 a8 s. n% d

7 ~- s+ c, h1 ~- [/ `% p* Q9 L

! o% k D8 P* t 因此要利用成功就需要条件竞争了。$ } y# G( l% k* q6 x( q

, |2 K# L ^) S" k/ D# f

7 b$ Z9 F$ B1 W5 X1 B: a7 Q2 w 补丁分析" L% d7 b+ m- v

' v0 T$ M6 P' _

6 P6 d& j2 c2 Z3 v' a* v. D1 K   0 c* _/ l; d; j

- _8 |! l* ~- Q( [* y, {" P

. t' l) z, k6 W# ` k 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: $ a' B5 q. Z. h# |& ~

$ W4 S7 ~ A9 a8 w- n( ~* A+ c' {1 b

: X, _8 K, }: A function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} 1 D. j. A# Z3 ^% t8 C: T

4 S( A7 `- C0 [5 n3 ]

6 {) S/ S8 v0 Y+ @& S  0 |! F& z# n. Q( [

# u9 x4 n. M& }, R4 `

2 @6 {; w- }$ E9 l$ K% V 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。. D. S# t% o5 C! i. }

) \/ Q0 m2 V. B3 V* [* R. i

1 @& e0 h) K4 q a0 j 在is_allow()中增加对$this->savename的二次检查。 0 h9 G e7 z: q9 M1 P! j

4 Q5 A' @) t: e) I+ ]9 Z9 m# A

1 a! w" p7 L0 x6 B2 v 最后 & E+ o9 f; v3 `7 |5 }

6 H' K/ ? L3 h; T) v5 i' d) B5 n

& m% z. O7 N5 f( h% d+ p3 z& M 嘛,祝各位大师傅中秋快乐!' a/ y7 v2 q, N2 D

; A" f9 Y: C8 p& _

& H" `2 f1 @7 P4 b3 E   1 {: v/ |( o# E

5 K7 n# Q% G( h: C
回复

使用道具 举报

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

本版积分规则

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