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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
- O# a& _% y7 j: }9 Q( Z: y1 e$ \3 K5 @

- c( M/ n3 E) c

6 O3 G+ q# l% L5 T# B7 Z

- c! W6 ?, b, X8 G( I! C1 V D 前言& u, i$ e+ g3 g9 x! Y+ l. ^9 G( o

- v5 z, j: p# g/ C1 |& E

+ E% u0 Z) [( l% d* V& {3 v8 d 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。6 U, i% X* u1 m- X1 }# W$ q

7 O2 N, `& I* t, ~* [

. h- G1 x/ t: J0 j& G" ?5 x  1 _) c; z* I6 W

$ N$ C, P# h- f+ ~6 D. l/ A9 m' Y4 ?

+ i" ]" Y; @0 k 漏洞分析 8 m+ _. P- a8 ? y7 m9 J* p

1 \; C' f+ r& {7 E! v- [

9 E5 H* N, E. Q2 ~ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:, Q, v4 L( I4 o- k

' C: ~9 C/ \7 z6 a: U( G

' Q9 d! P+ ~" N( g( y  - n( ^% d* ], u

3 w3 `" O) m& w9 M: l, |

" h8 W5 I( V; E2 F 对应着avatar.inc.php代码如下: # \5 Y. d% U& N* O5 f& S

/ e( ^4 {2 ^# U3 j) s9 p" F

/ c5 z+ D1 m8 k2 C <?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) {" J8 \4 G5 p) D" G; Y

2 ?' w5 L) \2 W/ t( M5 t. K9 _

: _6 H3 l. ~8 B+ S9 V     case 'upload':: u- ?; F) U! |$ _/ y

4 g g& ?9 ~. n h1 ~9 H$ a

+ D$ m: X! D5 [. e5 ], _9 h. l% _. Q         if(!$_FILES['file']['size']) { 6 y5 Q. V' A2 ~- }

! d; W4 ^+ i9 J y; f

' `9 Z" e# ~- `' j4 W. q             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); - u) q3 G6 h b1 M- ^* x9 S2 k7 K- ]* Z

2 l ?# _4 ], c

; o1 s: d! ], C3 w             exit('{"error":1,"message":"Error FILE"}'); ( c+ q% o! L! }" X# J) w

4 I: [( M; i- o% m6 ^2 d8 x

! H+ o0 y* b4 F- y- i         }6 ?. u& ^, e0 s% x2 x/ q4 R

. ?3 Y" Z% x, s2 ~. k: p/ {

! z) T E7 t: [ |2 I5 w* S% g6 y" A         require DT_ROOT.'/include/upload.class.php'; 7 x B* ?" P( D& H" m

: t% `/ l& @' X

+ d2 M) k' l% I  & ?1 t$ e1 N; s O( z

3 d* X7 U# b- e

5 T$ ^- M/ v T( A4 W# \8 }% K9 c2 R         $ext = file_ext($_FILES['file']['name']);* x, i# m1 I. I7 c9 i1 r1 e

8 K2 h6 y" \; `2 U3 n! Z# C

6 T( }+ g, E7 z8 z         $name = 'avatar'.$_userid.'.'.$ext; 1 n& `8 Z }$ P* L1 }

. u2 a% Y% @2 a$ l1 g; o% {0 J( P/ t

. {' \% y+ { p         $file = DT_ROOT.'/file/temp/'.$name;! z% X8 T9 @7 B( |% z k

: T( U% C' Y* N0 |

& P7 G2 p8 h5 z# n2 s4 i, {$ V1 r! o  - D9 Z: L( z9 ^+ ~6 K: [

2 R( O! Z0 o3 L; h

4 W( `" a8 [7 _# H         if(is_file($file)) file_del($file);! O' c1 ?! q5 v2 S" {# b

" Y, z6 |- l0 M% R4 s

5 I$ ?* P6 S2 c: w, {+ w2 s         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 5 q, ] k# _% T, {5 F/ ?

4 s7 \, w- a! j2 u7 ?0 a

; @2 d8 r2 ^- A3 P) p7 Z/ D7 N) |   3 t( _9 F2 K- d

- @& `8 Q& F9 i5 f! |0 s( Z

2 A0 g, a$ r* ]         $upload->adduserid = false; $ [* V4 |+ s S7 ~3 ]

6 T: z8 m6 M( n! I* k2 }9 i% ~

0 }0 p, G+ H2 X! O7 C   " }1 a @6 G6 x

2 T/ r* W5 |0 w1 |* V9 P+ U; }

6 r( p+ Y* ?- P" @         if($upload->save()) {3 k0 I1 Q" e( v4 p6 @ h; u, f

. _2 h6 I5 x' ~& x) `: K6 U z

# r* _, N. k, b- J! p             ... ; g+ {( s% t! @/ Q; ?+ N

. B, M- p7 [' C/ _

# X, q$ M/ h9 P2 n# d         } else { # b5 c. G( F9 J+ d+ r/ w) F

/ \# o2 E }7 y' t* b4 m

) @; h x# j4 K, q0 t8 J) W$ c# \ Q             ... : ~+ f9 X; C' ~" }' }( h% _; o8 a

( z7 N* A. d6 L0 I

4 ?7 Z/ _+ d# s* F, g2 d/ d# n         } , t& K+ q6 @7 R! y9 X" y, k5 ?' m8 e

# R) p# D) P/ U7 T. S# A$ a

c! P4 m. ]7 Z     break; 8 i* a( o8 W: E/ |, ^5 {

4 Z+ w+ Y" z$ m+ w! O! b

3 N. s \& X# L 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。+ _) y1 D. L" B2 C* a& {2 @

8 U/ \4 H8 s+ f( m5 Q

e; @: s+ a- U2 e upload对象构造函数如下,include/upload.class.php:25:6 _' ?, c8 g6 F

% R ~% u) x& m) g* W$ `" f: Z" d

0 D& i$ B2 Q0 V) i5 a5 f/ E <?phpclass upload {/ u+ b1 O+ x6 c' x8 E

" K4 O7 Z( j4 S1 E4 @/ Y! _* ~

J' _$ L7 V' y k     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ( `+ @# h0 o% s. p- d5 Q8 D

, ~& b; I7 b4 v. u$ X' c; s) o1 A

2 y% {6 \; r+ v         global $DT, $_userid; 1 p4 E J7 L0 h9 U5 j' V9 `( \' \

* V. H _% o \) v( }7 t8 V

* B1 C! N! ^: X         foreach($_file as $file) { ' X6 }3 M, {4 f5 B7 E

6 o" N& A+ H1 _6 I1 r& [- }

3 U: Q8 H6 d1 S, m) j' D; `             $this->file = $file['tmp_name'];8 Q; q R& s9 J+ X( a/ m

( K. W+ |4 v/ Y8 ~( n1 L6 K- V; C5 z% J

! K% C% ?, I2 z- B3 m1 _             $this->file_name = $file['name']; " Z1 ^: F' Z$ c, y9 J3 g$ V

! t/ Q2 Q$ R4 [7 ^4 a% H$ b0 Z

! H1 j) D# Q, r9 a- s             $this->file_size = $file['size']; + K q) y% r# j( p6 I

9 |* n; b" o6 R0 W

H6 Q0 f8 m4 L$ l             $this->file_type = $file['type'];& Q/ o4 s9 Z' z

- u) _+ I1 x8 y6 q' J1 H* _* I1 j9 F

% R- y' p, S; l2 u/ S7 U             $this->file_error = $file['error']; 8 R3 z Z- i# j" x5 s0 [8 l+ x: Q; y

2 C) Y4 j4 g& c; i

% E) \: C0 R! u: T# m" j  & |) I. H1 ^ j p

0 C( P* d2 R; ]; A5 b

; `% R$ K; E$ l+ S. I. p% q, \         } K& F* Y; g# {- }# {

: I- o& \! J: S* t- Z$ A1 J# c

" ~3 E3 z% K- Q7 ?% |         $this->userid = $_userid; ! ?9 E, U3 C% q& c! b

1 U9 ^7 ^- ~9 V# Z b. C

- i, @$ I* E) ^2 W" \% d         $this->ext = file_ext($this->file_name); \4 i" A! e! T0 f7 h( J6 ]

% h2 u8 ?2 m7 I' v

7 j6 f4 v5 E" ?$ E         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; , v6 E8 }6 E9 \

8 X0 s: _' Z; i: |( G

2 c% |% Y# c- M* y, E         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; " _6 X- ~- r! W+ m- }. f0 f- L* p

5 E" ~" q5 _$ k7 m: A; S( X

. v+ H& o1 ~4 [' o         $this->savepath = $savepath;$ B0 Y8 X8 b9 b* [

8 l2 R g4 M6 J, f: E/ _4 M

& }" `( f; \+ h0 I% x/ j         $this->savename = $savename; - A% e* Y& ] ]. X+ `

1 I2 g& Q2 `) d+ S; E) t

7 k% v# q3 \1 P4 ~( x     }} # s$ f- O0 ^) ~- ~3 Y Y

! d+ v* r1 y$ ?$ w1 c+ ]& y

8 u3 ?) L* ~7 f, c 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 7 H4 p& P6 z# z8 V

; L# D& ^! Z5 J

" I+ S# V4 s# v8 i3 B! d 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 , I; ]+ q; N+ b* b7 a: ]

X0 E' {0 N- F; x' D% n

u' Y+ F7 l% T. H4 e $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# w2 @* B5 l; p

! I1 z" u: b- Z8 f, }7 ^9 t: {

% ^$ O3 {7 B# k# B 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:& L. e' X: r" n1 I3 e I+ r" M

M. {9 R# K4 c+ z- j

4 Q; g- j& g" J1 T2 @. Z& Q. L7 F   ) D' ^/ v+ A9 b/ M

# I. q9 R6 f& R& q# I

9 T n$ u0 m8 J& J% [1 r, r' P: P 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 2 T3 o/ {, m: [9 D3 j4 h! Z

2 w& c* ~% Z8 M [; `- j' r

6 o% l Z. ^9 }4 w$ D" ?4 e <?phpclass upload { " f2 m; [5 t B: Q- y

& i& M( `: X5 s1 K0 m3 y. [

$ @/ d) |3 S4 t. C: W     function save() { & w" w$ L0 U/ c# p" B E6 b7 k0 A

! b5 A9 m) r- `' c

0 ~9 K6 p# Y6 [: U+ I; v         include load('include.lang');* o% ~ A4 A5 c% L& @

1 I, j$ S M0 D/ n/ ^8 ~

4 P9 R j; D5 x. ~         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 1 v' V& u& w- B4 |1 N

9 U; L4 A' i8 K

; Z1 u# e6 `4 M, W4 m   ) m; C4 r z3 Q2 _' m

) f5 ?/ H @9 r7 R% P

7 D/ o8 z" p6 L# Y         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 a6 ]' `# W; M3 r! Z0 O' @, `

. h) D3 T9 D2 F" ]. C" S0 y6 r

1 B7 j9 B: ~- P- g   5 Y2 C% U* c% u" c: U* C) C

2 z5 ?/ ?, \) t1 L/ Z

8 ~* G* w; O* x2 B& q% C& L         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 8 Z- J& y+ Y; V3 e

' Z1 |( p' b; c: f0 a

$ Y! |& ?* z& r. L4 u" l  - @' B+ A- Q" f, y& q: u

2 Z( ~% x+ J8 V" `2 t

$ K3 q( l( w: z, `         $this->set_savepath($this->savepath);& v, p' M& G4 ]% V2 k6 C

. l5 v* f; n s2 i% U3 A

6 c( T7 }6 @6 s' Z         $this->set_savename($this->savename);+ g! E) _+ z( F

) Z' ?; d, ~* a& S5 b" T" S# T

6 x$ c3 d, W% F5 n5 l   + I0 j* V. t# G, j+ M2 k

. z4 _2 }& e3 S8 g( H+ Y4 b

# }& G$ A0 Q5 ~         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 2 ]) Y9 l( q7 u( E% z: `' i

" O+ R7 o( l# f

7 j; F! G9 y4 ]: z+ F         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) O' u5 n0 k* z

9 G6 `8 ]" |; f

0 z" M. _# t4 w: k         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); - t$ [% [. | C2 W3 H

/ R/ z0 |% h& i& E ]. \

! f; @: U1 q1 v1 S6 y/ [   6 i% Q* M# }+ F8 c- i

; r9 _1 b4 h' a* e& K

) X# A! e( b/ E; D! o         $this->image = $this->is_image();9 M" d$ W- L5 X# b9 C9 s

! p+ y1 T9 k M6 k

5 V2 Q- J% F) _) |         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);; Z4 y5 v" y" o7 Q$ [

q3 p9 n$ r! k0 G6 n/ O1 z

* E, h2 ]* g+ ~, [3 f4 i         return true; + J/ h* q t# i9 x1 j6 S8 v

y/ t6 t& W5 D, c% ?/ Z. J5 v

) j/ Q1 ^& g) \0 y# j     }} ( \ r- q4 N( v

, q2 [1 M4 z2 Z8 o& ~

! t0 |/ Z4 z6 ]9 x; p y0 { g3 z 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ' l* z# c$ Q% Y, z! F3 F6 D

1 ]5 Z! X. F& p" Y. L8 B

7 W# h5 e' M6 }) v <?php: U: f: o4 m; H0 Z" t* Y1 N

" x; K9 D. J0 Y% m3 W. z

& c9 z, g3 o" Q+ n) h$ H     function is_allow() { 1 |0 d! @1 [4 c/ h; k8 p

# @3 a4 Y* E- b8 T5 j

2 Q9 w, c; j7 Q8 g         if(!$this->fileformat) return false;5 U& L- N+ t9 L, z4 }5 d, {1 C* s

$ H9 A& y$ d% w: }; A I

3 e; L. r N/ I         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; + I! R, T% j4 g

, r9 Y2 C( ]) D! h

8 v g$ ?* ]4 v         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; s5 [0 y, |) E0 g+ ]! p

: {8 v* c4 k* ?/ g3 t% y# |

K- B* v2 n& O% P* f$ A         return true;/ V8 V$ z1 T5 k+ T s S

4 W0 K5 b9 }% l8 }# l& P3 A9 S

/ {$ _3 \2 }* `     }* ?; [, j" q+ d! I

% O, \ r' W0 F, \8 ^# K

% E7 Z1 \" [( p 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 9 d, D* f' W1 |, I2 F/ `

' ]% H- ~/ S# ?( m" E5 X8 x

( X! y) P9 P- M8 Q& G5 ~0 |, x0 i* O/ K. V 接着会进行真正的保存。通过$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文件。- I! K% o1 d. D, s: o& y0 Q& R

$ g, I1 C$ ~- V. a( q* P

( Q+ Y# p$ W, Q5 j; ~; I$ M& d 漏洞利用9 c/ H4 R- \* A

# v0 b4 g! I2 `9 U& {

7 q! X" V- h8 u0 a% u/ q, m) N 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。: `- ]' T& H2 U: S* I4 P; t

, u( \* b8 r9 w2 W/ z) ?

0 Z( Q( }- z4 U1 e' B0 K   8 U& S3 ]' N# ?

0 Q' u" Z1 \6 M3 }

7 X# c) U6 w9 g9 Z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ( W: N$ e; {2 k' Y& r5 q/ U4 k

7 m- I/ E6 @1 J8 d4 A

0 O& ^9 R$ g! \/ u+ R 不过实际利用上会有一定的限制。# d* @6 U7 D4 k* u* k

. g# c$ K% F9 M

* \' Z4 L' R# b" B' }3 R9 L4 u 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。1 W, H( ]* B* y

: o6 G9 Q2 Q) T) |* y+ r

2 | h- {- v: z2 m, Y# i. O   4 y6 P$ D' `: }4 V

G$ d( K9 u- i& X8 X% T

3 x( H: a# m1 X% C 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: : {. k# c: H. y' \

& S+ ~. {6 R7 E) {2 F0 O" j

4 U6 t4 | V5 ?" ?3 k* L 省略...$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]);省略...& i* H* l" q. m$ F/ Y) N4 x" N

, e7 k. W' P3 q2 R

1 C$ Y, E, p) u) r) S8 Y 因此要利用成功就需要条件竞争了。, A% w( y8 [; d

" |# `0 R0 H/ X& b6 B% p& }# j* M

4 i! e$ J# B. ?: [2 u) J 补丁分析$ a# u5 z$ N+ l$ k) S

! K& R1 a! M2 [$ C

0 q3 b; ]/ ~( P$ U   ! Q+ C( h% U" {/ p u/ a8 f6 w

4 d t6 ^$ w0 B# y5 H) }, M; u6 k

3 }2 p; {# q3 X 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: ) u" o5 T3 {7 K1 k4 l- H7 B# r- y3 {

- H8 ~6 u1 f5 m2 }1 r8 V! A

* R4 n" {0 B' h3 m) W4 o- A5 H function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}/ g; {9 N6 F s4 i

; C( W; Q& m! s+ A' \

, B+ ^, a4 F7 p7 r" V' Y   4 F* S1 Z2 X) l& k% T& D0 B$ w1 n& |! v

6 Q6 G3 F0 l: D! f! X8 g$ D- s1 I& b

' f+ ?2 {/ ]' J 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ! |7 ?3 J8 f7 y0 g/ m- [7 G+ G5 u

_. b$ k! k- ^9 y) Y6 W. @, y/ s

6 U8 Q! X' [( y4 ~ 在is_allow()中增加对$this->savename的二次检查。 % c0 j, h0 v3 g- \; k

8 A7 |7 C3 P$ \8 z) x3 a- P

B0 E/ y3 p4 w 最后 . `/ W: p, j; o1 h) m* m2 M

" `3 y: P \* L2 V& x5 J

/ R- [& H, w" g7 c* T+ G7 ~: E 嘛,祝各位大师傅中秋快乐! ; N/ I6 e- {. S8 l

% v9 E2 E# R( b" n

+ F4 R% E6 ?% H& u3 P   % @; l& j1 t0 b# i5 J

& F2 s8 x0 Z3 }) J) w
回复

使用道具 举报

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

本版积分规则

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