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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
# {4 S2 q) D6 j1 S1 E

- f. ~. q b3 c/ N: I$ I) o

5 ^2 y3 t0 v: t) O

* u- P/ \ @* W9 C6 S) \ 前言 % Z( g& ~* J& t* i3 N

* S: i9 U# R7 d3 _2 C6 j8 \2 T

! a g" p# F8 Y* g, {% B+ m, R 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。8 ?4 }6 Y2 F/ r3 @0 h& b! R8 _

' c' z( \/ l9 X( g' @0 D

9 ~5 {, m% U+ f( u9 M7 W+ q* R! {  , S7 ]& ]2 H+ Z }4 B1 m( ?

/ K" }6 ^2 F6 L# j- c1 E

* ~. a3 {) |; X- k& X8 I3 ^ 漏洞分析9 ?8 [2 O- P4 E% d

$ r- y+ V9 q/ ~/ }3 K. V

8 u% d% c3 a# @# T3 s9 ]6 A 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:1 h# ?8 [' K3 k

2 i+ a; S! }: x I9 x; o

. d( ]* f, [6 i( n g6 G2 U: ?  % Z6 V; _( A/ F1 {0 Y7 E/ D7 s

/ O0 H1 p1 m* ]% M7 F: y t

3 \ S, P* P) a0 n3 }6 f; p' [ 对应着avatar.inc.php代码如下: 7 a1 c" Y5 U6 A, Y/ h+ [( P

$ K2 @" r/ m2 r5 k

3 d/ C. K w* p( F3 i- o5 N! u <?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) { 4 I3 Y) m: l8 Q/ u

; v+ y! f9 N+ M! c+ `9 U

2 k! `: B! N1 t$ v     case 'upload': ) D3 [6 I8 ?" v8 n% N1 u

6 G3 W/ L4 u/ g5 C: R2 `

! X* J( R, b* r- z         if(!$_FILES['file']['size']) { 7 V7 e4 w0 s. e/ H

4 w, e7 i2 M: j7 ?7 Z3 f! O/ W

; Z2 _' x# A4 \4 u             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 8 c1 X3 h/ \0 a A6 w# x

8 L# `( I0 H. G8 ]. B

6 O( n* i* T& q: K2 i4 x) U3 s9 T/ W             exit('{"error":1,"message":"Error FILE"}'); ) U) c7 D+ H* a/ m1 z& z2 K, n

% n5 j z$ U' q* m# C

0 q8 G! E' L% X0 I* V# d# Q$ n, @         } 6 S. D# z& F! x; j1 K( d

0 a$ v6 E* Q6 k

" Y4 p/ [/ H5 T# A# f         require DT_ROOT.'/include/upload.class.php'; $ m0 i& k% W5 }. m

8 D1 G6 `% a5 `0 S. D

' A$ V" [5 j$ a3 h! z# u/ C   / n( C- `3 \5 m7 Z

3 T* [. z2 l+ x% l

3 k8 |3 C: x' }         $ext = file_ext($_FILES['file']['name']); 2 a% D N' X9 o2 K4 K! t3 V8 @

' `7 l* p% U! @0 K% L$ G- z

' J' L: J. [! P6 v. ?/ z$ \- h         $name = 'avatar'.$_userid.'.'.$ext; - W0 f) l% G- H: s- N) D. m

% }, g5 v. N/ O

9 `4 ~/ R& @/ e( ~* K         $file = DT_ROOT.'/file/temp/'.$name; 3 n- w: D% X$ @* `6 q @

B% `. l H8 h8 G0 ]6 u( ^

; R# i/ E a1 i; _: F4 ~2 \. u  * t( X/ I' c( x: i/ V

. v: N, v- O$ `2 K' m+ M: A4 H& |

! e9 b6 s4 v% L) ^1 T+ t2 H( g7 l/ K         if(is_file($file)) file_del($file); : q' _; D0 u4 O6 H4 J$ n: Z

! Q n2 J2 E; {+ b1 d8 C

2 K4 N( ]9 a9 R7 e         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');8 ^) B* C( N5 C5 F! R( X- v; A

& v7 F, U: G& D% ]# n8 t1 B; r

1 f( f% [- d0 {( j" C6 q  5 c! _, v: |: s- |4 I

$ a" T8 F. y/ z% @% H y

: J z& ^" f9 q3 [- ]         $upload->adduserid = false;$ Q ?8 }! @# O4 X

& l, K; H" a+ P& f# d

' C( \& l) B8 o   9 u5 ]" c' I$ P. A, l& `3 d5 o# k

# Z1 W$ j) J/ S/ r3 y7 z

: s* B- j8 m3 J0 _- F( c% d; C! |         if($upload->save()) {( n8 a- c$ }4 p* g" J6 C- D. u

' \& s( n F" K

9 ~: G$ K! a/ N2 p, u6 Z             ... % u6 h' W# }4 c! t

( O5 D( G1 Z" {+ o

$ J* q/ @; Z* z4 T! I% r. k( V         } else { . q0 N) `/ n3 s5 d/ _

# y* e4 N% J+ ^8 E+ t

?$ G# y1 y" [; e$ `0 F' A, h             ...* B2 y: z# ]0 E. |# i$ m

6 ^ V- u2 {9 `- \& [# n- G

9 o, c+ v7 g, @" ?. }8 @+ x         } { f2 z0 E) R8 F- ]+ C

2 n! Y# @, f6 X* a! s; U0 x- X: M

% S0 b- q* S; m/ L; k- z3 f     break; 4 y" d( z* t o% g" b' e3 t

" x2 \& y& T/ X+ n

/ |8 |. T" B9 _: l' H 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 6 `+ y: v# V7 p6 _

6 d2 {2 ]9 I9 j1 B. a

* H& o c( J; Y! b0 |/ S+ A8 |' ? upload对象构造函数如下,include/upload.class.php:25:7 s+ P3 I$ o5 d' ]5 C& d

/ b) }$ D; Q G4 G$ n' K8 R, O

u( O' k# r& W0 N1 d <?phpclass upload { / Z/ \ ^& F# G/ p

& f5 ?7 ^1 W9 s; Q, I' [4 \

6 g# {& S" C& a. _# q     function __construct($_file, $savepath, $savename = '', $fileformat = '') {0 C9 N6 h. {4 T

6 O1 a* {6 G' D7 z

1 e7 `) S% N9 Z: I         global $DT, $_userid; ( I3 e# J; M6 `' Q2 t9 F

2 M+ h, Y3 N& R' R7 @

, j: X( t" y7 b! L0 M. H% [         foreach($_file as $file) { ! m5 |4 s2 \- |5 g

$ ?- B# m) E. S* n; ?" o

* e ^! e+ r8 h             $this->file = $file['tmp_name']; 5 L- l0 p2 m& M1 E! E1 t+ W

& M1 Y% C. A6 i2 M, }# u

" a/ d m' i) q; _. i. \             $this->file_name = $file['name']; : D% d: r8 x+ P+ _3 F* W

6 v& T3 ?* S- u0 @, q3 g1 Z% N+ ]

8 c9 h# B6 W/ M1 t& E! L( R             $this->file_size = $file['size'];4 q3 k9 n) z v' {& k

1 `# j* k ~# F& j/ o

1 r, d7 P& m3 a1 x6 E! s             $this->file_type = $file['type']; & g& O# f; e; e

. {6 D. C+ O& @- I% B d9 \1 @

6 N# u3 F9 _. \& E             $this->file_error = $file['error'];8 s5 \2 K6 ^% K

5 g7 P, k; E2 V

$ ]& Z' E& T- j/ u0 ~1 x# h" O  # E/ a3 G# W9 _( `6 r

( j1 e: c. L- m# y$ O

$ g7 L, f' L Z' n$ f. {4 k. [         }8 M7 Y1 w7 s( U$ A7 R

' d, ], P! d4 a+ p* P

9 K$ n) z; n/ \         $this->userid = $_userid; 6 W! j! [, w5 s3 @" y

! ^% ^' w# g# |0 k; V+ B6 G2 a( n

2 a. D, y+ @7 Q         $this->ext = file_ext($this->file_name); / @, H M; u6 r5 Z, u* S

( S8 ^% K$ R/ O' I H6 ~

2 \+ R& a/ ^8 _: }/ q         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];! e6 U" e4 K* A+ L7 R

6 u: R) T* w1 _) R

# U: V, @% F7 Z' o7 u7 o( u' [7 V$ T         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;1 V8 U$ {7 K$ [

. ?- f4 p' ~2 _! l

( A0 `: N9 A6 W6 Z4 H         $this->savepath = $savepath;, \6 J: }. k6 s* D7 q, D: u

~& d# c. u& n% L

0 R' U/ v) h9 L* s. Y3 D# P         $this->savename = $savename;; H% V9 D3 R$ r4 c+ ^4 ^

7 v! F9 z$ E5 F" l

& p2 s6 r+ `: ~3 i5 ^/ y3 k     }}! F. z. n) g0 h3 A

' P- F: M" l/ S7 k7 k1 S0 Y6 Y7 z

+ e( Z- U& _& X& C: A 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。2 j, {5 X' j8 \0 [" B. }% C9 K

- Y% l6 x1 X3 s9 o! {

2 F' C+ P4 c( P3 ?4 x6 F% ? A 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 # x/ y" ?% {4 z+ U! ?5 e- N* `

+ A' z, K% O3 ~1 ~& T

8 _8 d q: U* d7 B $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 1 w0 i; ]6 i9 x- M+ w4 W K+ f

9 X* Y9 f% c4 q8 p @$ ~

4 w4 z/ w+ Y" J( ^3 K" F$ O 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: , K2 g e. A6 O. C; H. A. C

/ h+ M/ E- c) {1 F5 g4 e; Z* I. c

8 _# Q/ T5 F) n% `+ P   ' J9 H% I( z7 P& O6 D7 p2 {

0 k. e0 \( U) F8 p! ]

! ]: Y! O" w6 y 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 7 r$ p8 P" q/ m( R$ g

' K6 t" J& E6 w5 i

& q; x6 L2 G" b3 d <?phpclass upload {+ e) {1 u. z6 P

h2 X/ o: F7 m1 K9 X9 @+ q. |

& r0 l3 e, b' o: y" F9 J8 x     function save() { 9 Q Q/ l1 V( f" P( u

0 E4 l* a+ {8 X3 f. ?9 u9 \

+ t0 f, x' s: p; W3 z, p; q9 I         include load('include.lang'); 8 s. p8 U3 F4 V! ~$ K# _$ ?! N i

% S3 }/ K6 z: R

2 w/ q% C$ E0 j4 U6 `7 p         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');1 C3 R/ c) B) E4 W! g

+ k4 \+ R L3 t3 v4 i) l

$ A7 K) T% u t3 \8 Y$ x   6 L4 n$ u) n' j

, P/ }( T4 Y) @3 G, C

0 E" ` g% I. l! \2 a1 f         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 8 F# `' u! _3 v$ \4 x5 ~- Y

m/ c, c% l0 V# E, d

; s/ k# J4 c1 @: X T" l* Z   + O! n y! E( ]9 _

3 j5 A: e% D6 P8 M" {( a |2 J

q' N5 f/ {3 ?2 a3 _2 o2 `         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);: Q2 ^! m B3 v$ f9 [

$ Y' R2 J" L I" `! }4 H% W5 Q$ O* ?3 X

; y1 o% |7 |9 g4 [- X  " ~/ x! e" G, k) ~' E

8 p4 f5 _# M, Z, Y" {

8 b1 e: l. A$ }* z, ?7 }; ~         $this->set_savepath($this->savepath); 1 l9 n. d6 g, a% y7 [

9 D0 l; P, z8 ^! [3 \

/ ~3 T; m0 `9 H+ D" k7 f( v! Q         $this->set_savename($this->savename); , a4 M$ I0 u. ^7 h2 Q

. t! T- m* x0 d+ u5 s& ?1 J

F3 l! W3 Z r; t$ K   $ V/ @* l4 J5 s+ w$ G6 d

2 A: W9 O% T2 i: @- t

$ D9 T/ `6 r7 x2 o/ r         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);8 a2 u1 g- b/ o9 m) \9 l( {

$ P* u& x' Q0 g

Q3 P* X8 ?; _; W8 J         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ' l( X" H' j9 E+ y9 l! e) L: U

+ J2 U* k' A3 t) j# F; V( ]% a7 Z- X

$ U' I; k0 c9 ]2 J1 c         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 0 ?) ]% L4 d: }% ]: f- ?

; a4 l& C; E% h/ \$ B5 V* t

/ h4 I6 _; W4 @! b  ) K$ } J2 r8 z, A- T5 \& [

4 Q- \5 v2 W7 H5 C" x c

* A1 ^+ k @& f- w         $this->image = $this->is_image(); 9 R, ~) l/ {; j

& u( w& G0 {9 `, ~/ v8 X

- z X7 c. l( p4 {, W# }         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 0 V; f4 G4 M* h8 ]- Y6 F" p

& j) ?5 Q6 Y( k3 i; Q3 A1 l

* }. N* F4 Q6 F( }. \7 _, R, P         return true; 3 l3 P0 ~7 A1 j7 C

5 ~$ Z" S, }7 B' F: U

; Z( Z% N1 x1 {( k' a r0 q     }} 1 {6 r* s* e. }# R

I+ q8 a# Y7 V5 G, C5 M

u$ `$ n# a2 U 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:1 k/ L/ b& x, Z9 E

$ } \! r6 w: T9 F5 f9 K. S3 s2 L

& O7 m) c! e& w: A4 F <?php; V# i: }% M) s {6 j. }6 Y

, Z7 c! R# u6 D$ d) u; k: D

* J# W( b o# w$ E8 q4 Q     function is_allow() {7 ]* X: a$ N1 v1 B% [8 T* k' o

/ \: y1 U& M0 U1 m0 C) }

" h; z! R/ d& l7 l3 j' E) C         if(!$this->fileformat) return false;7 `5 \/ [3 A' [2 l% m: a" ^

2 H+ {% W, ?7 V

T! A% ^6 b2 I3 W. M5 g* b         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; ) g7 z9 u+ m8 J- L2 o% n

4 I! ?( A2 `+ o5 |8 n' }- x+ X: ~

4 ~3 r) w8 @5 J! u$ 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;8 B- g$ \+ t% i, B8 c

1 U# Q5 s- T: Y* Q7 x+ d* P4 M

3 o0 D/ y2 A: c8 f. n8 d. T# a         return true; % Q; s) K4 f6 t) ?; C2 s% C

% @4 m4 A4 t! r$ a0 S

# U3 j" f* K, Y" X% [& C     } 0 ]( n5 h0 Q$ Y. M

4 N- p: b+ i: [

* ~0 X: z, n7 a0 B 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。1 u) X4 `" D: i! E" W

) L! E* o) k* l

4 I% r1 `+ T% Z 接着会进行真正的保存。通过$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文件。 ! c/ h( I4 U; ?5 s; ], h# f+ w4 ]" r

9 d' N6 }* n: \% T8 c- N* y

8 |* ^8 j* q! ~) f( S1 @2 g2 b; j 漏洞利用: K. P8 ?) I3 }; h, |

% X& Q( Q u3 Z9 e1 C; M5 ~8 w

: Z( q9 `$ p1 ^& l* s1 u 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。# M3 Z& k4 Z5 ]/ f! `# `8 q% z

1 F H; D& t7 M3 Y/ {5 `4 j

0 d, l' _$ l" V& d. H   ; G, h8 c% j4 u* H m6 C

3 F, i; e# B' B/ `. M8 x2 B* d2 s

. d( o( F4 B6 W: }" {/ O# i- T 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid y0 a0 g" Y8 N) j2 j

( G6 ?" w( ^# t( a; v. n4 X; c

4 A: P. F5 u) P9 \% ?' M% |" Y 不过实际利用上会有一定的限制。 0 o, S) J/ G( U; _/ z1 B* r

6 k! e. U v* b

; }+ T& Z4 R, U' d# K, M- t, X 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。! q, |. m' `. \0 l5 L

: G; ]( T: B& e4 c6 h

/ g7 [. `3 ^% P- l3 H  1 K L' v6 p- ~6 u* p- Q1 d! i

4 x& M; j% H1 c. y6 z8 q6 D. `

9 Q- e9 y- s! m) m2 M2 s 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: : M6 e- r, c4 |! _& s1 R

) L1 {7 U' ~, e ]

+ u6 U7 W2 G7 Y6 V4 B 省略...$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]);省略...( p' b* ~$ }0 `. V$ H

& g' m* H) @1 d* S1 v+ X5 {

% w- j% H" X" \9 G( w4 [ 因此要利用成功就需要条件竞争了。 : D" Z8 u- B0 U% z! q

* Y3 W2 Q4 X. ?8 L% ^( n$ g$ u/ u3 |

& V3 }" y) T" O5 T8 q2 K' C/ Z' ] 补丁分析0 O9 [% {- I; c" i8 y0 j' ~

1 I' ?1 a# i L5 ~! }0 E5 P2 q% _

; i) i2 q2 m! O' I9 [; O  2 A1 X6 N# Y* f

0 b; D& \+ E/ ?7 ~4 e. [ s; e9 b- _) M

% s# I! V$ j" R4 V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:) x9 B2 |4 G. t @. f; V Y

0 G. t. l. f- A5 k: @2 {% X: V

4 y) C3 |* R/ p8 u+ c$ c7 \( j function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} M) G; T$ q9 b- T4 F" t

$ y# p3 q0 ^1 i+ x

& u1 l4 t: _8 H) @  * Y+ I( _4 M: R& _; ^

, K; b# u& @" n4 T( A7 D

, z0 b& K- ?. H* d$ J! _& W( m8 I 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 . O ]6 V' |' h2 c- f1 B3 _3 U

3 e$ M V+ y; o% x+ K3 d- ^2 H

: ^: j$ o# V# ^! y2 V 在is_allow()中增加对$this->savename的二次检查。 & v7 j9 r- H( i9 R

5 q# v' o0 Q d% O, b

) z4 m& j$ ]7 G/ A7 q 最后+ Z+ j: G5 S3 t3 D

# i, }1 g3 V \1 h

/ i2 I } p! n$ d6 Z6 ^; d 嘛,祝各位大师傅中秋快乐! ( H) s i' w! q2 S

& D/ O# B; H. c' N, w

, @3 \: s' A( e* E  5 n4 V4 T, h4 c. E2 h6 n+ F% u' H

! v) _. _# z+ K" o$ O, I
回复

使用道具 举报

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

本版积分规则

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