找回密码
 立即注册
欢迎中测联盟老会员回家,1997年注册的域名
查看: 836|回复: 0

Destoon cms前台getwebshell

[复制链接]
发表于 2018-10-20 20:13:12 | 显示全部楼层 |阅读模式
/ V& Q* d0 g" s: d

5 W4 _6 D- C: z; x. V1 i& [/ |

( u6 P6 |8 ?2 O7 S0 d& k2 Y

( c0 \5 P* h5 Y5 ? 前言 9 @5 Z" L& o0 Z9 M) w

: ?$ K9 S+ F( h; Z( C8 X

9 r) O3 [! u6 P1 g! M1 l$ h 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。" ~) S' n& g U/ ^# v/ d5 f

# c& i; t+ G4 B7 U I

# C/ ]" M5 B2 J" G  # A9 c! o% ]2 \. L+ V

9 j. @; l, P0 b( J

0 `$ s. b ~3 q4 q& `- m2 |- l 漏洞分析 ) V& y5 M: x. ~% {; H

1 F6 e' @1 l0 r) X& `7 G$ J

: H; k* t) l! S3 k: W 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:7 D5 Y0 f/ N. I6 t) Y

' w& {' C# n# i' u

' x8 |& K$ x H; z- `2 {   s0 Z/ `+ q6 ~/ {: H$ U z0 Q- m* I

) q& [4 |: w& B

# v9 z2 n! l! J/ _4 [# L( @7 ~ 对应着avatar.inc.php代码如下: 2 G! y6 o9 [! v8 d& ~. t3 i

9 o0 v7 A# c8 I9 `, d

, C1 g- I) g) b <?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) {& x" r9 ` _1 K% j/ P$ C; f, ^

+ `2 D; t8 z( ~# t0 @. G

e: U+ p6 B, R1 v+ d0 D+ H     case 'upload': $ _: Z6 U: R! e8 V2 w& V2 d

$ c8 K5 P x7 R) {

4 x# Y1 V1 g; k3 I m' o- U7 m         if(!$_FILES['file']['size']) { % c2 K/ P, j0 |% w

, Q$ M n) w$ G+ x |7 }

: O$ p/ |+ K: m- R$ E* [! i+ ~. J             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); $ O) u+ c. `, q) Z0 e

2 g- ?9 u( l- Q1 m

5 _3 {& O; r7 c' _- _% Y2 O             exit('{"error":1,"message":"Error FILE"}'); * U* H* e8 k0 `# [- R

# \! p+ W# ~0 Q n

! [5 z* V+ C8 N3 h! r5 k8 q+ Y         }& j( Q e; q2 p+ p, q& q) v

; [( ~" g, Z8 @- g0 |2 Y% H6 P

" ?, ~! z. y3 I1 q         require DT_ROOT.'/include/upload.class.php';8 S0 F' T8 f. l {

4 m- ], T/ c, _ Y. r! j) G: x

/ d1 x t% _3 F Q+ l+ j   I6 a; }7 B- q7 h$ p

& p- n8 G0 [8 M, q& E3 E) \

6 m" {, `1 E7 E         $ext = file_ext($_FILES['file']['name']);; R8 b+ a6 A! w9 U8 A1 c L6 U

: I; l h H8 e# F: O! d$ E

1 [5 s9 C7 Y6 V0 X2 O& Q         $name = 'avatar'.$_userid.'.'.$ext; ! b3 c- M4 D: H

1 n4 N T+ l$ b, w- k

! K0 U$ F v0 r8 c M/ [' l         $file = DT_ROOT.'/file/temp/'.$name; & ~# v( |6 U. |: ^: m

) _. Y3 O& V" L8 \! y

% D$ [6 u. y. o) Y4 P0 z   ) o" ^; `+ a: n9 a3 E

4 K s1 K' e3 h5 F3 N* t x

1 F F: j+ {$ m, y% d6 |         if(is_file($file)) file_del($file); ' v8 h1 ?. H" Y8 k) R# Y

& S9 x x* [; r1 E

& B+ W* D0 t$ K( g$ z         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); # g1 o6 H: {7 C" Y2 U% z8 Q/ @3 N

" s. T( J/ {+ e! v$ F$ N

% X1 w1 p, h K3 H( f2 `   : {2 C" s; P q E7 }

3 N& G2 ]( j; |9 Y0 v2 Z( s

; W4 S4 r) F/ J# J         $upload->adduserid = false;# J+ |$ e6 Z; j+ k) n5 [8 d# N

" L: G9 a0 l6 W" }9 y7 M

* b' T0 h4 R9 M% p; q* j  9 Y' N I! \8 D

0 h/ P" e: \ k$ P

7 F) F0 B0 P9 S" c         if($upload->save()) {4 q- {/ G+ m- j- ?

. m( y# r# i& [6 y

) Z+ I, a; O9 P( O# N+ Q9 G             ... 6 }- N% {* A; G# t3 U c2 q

# I( h {3 \% z6 } C& o

! m; [7 a4 _7 }, P# n* `% |         } else { ' R! ^$ T( w4 m% v4 h

% `' W9 g2 Y5 f7 b5 f/ |7 O; t% e

; e6 Q* |" }6 m+ B1 e             ... 6 S7 I* H5 {/ |: e6 g& @

0 n! F. t2 c" k# e9 H: i/ X: o

. S% V. `5 V$ a1 L         }! b4 P' L I! i/ c* a

3 V; N$ i6 J( p" D

: T( f5 e& U2 u     break; B0 c i# B/ j! S, p e2 k+ h

% m8 Y' k/ r; h( g

5 ^# D/ W" K2 I! G- m8 y 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ( p, Y& c! d n

: e' c. `" m4 g$ W

+ i! K" R2 ~6 [* K upload对象构造函数如下,include/upload.class.php:25:3 E* \4 ~0 G% v7 M0 u F

; ^7 y% S3 Z+ F) f- c' ^

% k8 N+ ?7 A6 p <?phpclass upload {% d' _! f' I( G' k; ^- ^$ @" w

9 I$ a" T: q% H# ?+ X

) }( Y! m5 P- C4 m     function __construct($_file, $savepath, $savename = '', $fileformat = '') { ' t. ?; S& ?& C1 @7 Y6 C

! A, I' b9 \. P1 A

! `0 W. z( M. ]% }4 O5 s- m1 U) h         global $DT, $_userid; % T2 o6 B: f; s3 m n0 Z

, R1 `1 b E4 K! I6 w

( H: J2 @; C0 _" V/ E0 v* F         foreach($_file as $file) {& I) M% X+ j2 h0 z0 @( i; S8 S

& Z) ~3 T9 Y* v6 ~

% |$ r: D1 o7 H. J+ F0 C             $this->file = $file['tmp_name']; 8 c2 q; Y0 b( g5 E0 l

* C7 e8 Q+ Z- W( g6 M: b- f. b

9 m- G7 W! B& F% |             $this->file_name = $file['name']; ) I$ i% z6 t8 O! Z) Z X

4 {& U0 F# m" M' E$ x7 ~! a2 y% M

* D, [. w* ], v8 [! a) W             $this->file_size = $file['size'];! A Z3 S& O: Q/ |- o

$ U8 Y( y3 h2 \

2 m6 `. W5 k0 ?; ]             $this->file_type = $file['type']; ! O; F; W2 I7 d

7 G2 b0 O% B4 d1 \0 {. O- ~9 f

$ T+ `; G3 G5 W% C3 I             $this->file_error = $file['error']; " V( I4 W8 _/ T7 a# R& P X

; V/ g: d) Y+ H7 i0 U

' \6 [" P! o" f3 W& H  0 V) [1 a* V5 L/ A% z8 u

9 |! |( ~) }9 r! }2 \& e, C- ?$ N

/ S+ n, P& z* R d$ G         }0 j7 ` T* e' m5 j

* R8 F4 n1 X- C' u7 B- p

2 R+ l, \ s) K4 C5 b         $this->userid = $_userid; * Y) m5 M7 H( R1 T) W

* x" t+ C `7 S

; t" { y# b6 g         $this->ext = file_ext($this->file_name); . `, C$ o: I! b$ M

- h/ D. o1 X& q- f5 l7 d

& S! S5 m4 m* ~! x5 |- F         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ {6 b7 U! h" z4 ~ S. U

& }* R5 J( f; g8 p9 H% P

" c! x8 Z4 T. P @         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 6 O9 q- X( I `0 K6 F

& R) d$ y0 @7 g6 ^! k

' Z! R. |+ ^( j" R5 I         $this->savepath = $savepath;( D# K4 ]/ ]- r4 O- Z \

) d$ G5 s; U; B7 F

0 B- L! H+ D! C N+ A1 k! T" }/ `" X         $this->savename = $savename;" |: C. b& w' N

1 l* P1 S# m$ a, {4 N ]8 O

- V: Z+ e2 \" G6 D( @. {3 ]     }}1 d) t* w+ J# d+ y4 V1 s

* K, R L+ e. @7 D5 p

, J5 q% c. y0 L" e$ p/ E 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。% {7 ?# x: U( [3 O2 K) H1 v( _

" J) b' g' ]. O

) m; {: [$ S. N5 a 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ! b/ O- g4 b1 _& g9 |

+ U N0 O' ~# I) N# W+ Z

1 ~/ E1 n; C) g0 Z9 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+ C3 m( u% ^- |

2 D% y" h3 z- t6 {- W1 A

* o, k: [. _9 e0 h' W, B+ P, l 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:; W/ }$ i) z" j6 D: d% a

$ h0 B. s6 I+ V5 b& ]

0 W2 ]1 ?( ] m7 E: R   0 j# `- X2 P" I0 ~5 J: c, U" i

( K8 n4 w3 o/ q1 t! m

* e3 G/ g" s! F# f3 B' k 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:1 J" ?" ]( U; C

; D0 \+ c4 v* P, W* `

. j/ \1 z3 a e K( e <?phpclass upload { w( q1 q' C0 j0 E

3 L5 o, k8 X0 o6 e* \

/ I/ `4 _2 ], X. O8 X# t9 A1 d T( h     function save() { # V5 h5 A: P; `2 P. w2 T

: z% v: |9 i; F1 { p

9 j1 F* I' s( P& f         include load('include.lang'); - I* m! s4 M0 C' q H3 v1 Y* ?

; J1 R3 ^2 r: W" d8 D, L3 j

. [# y+ k5 \2 V7 y i% J& @% g         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');! F% |8 c! J8 K

8 N; F0 l' |4 k, X( i! ?

+ \' S, w& W _$ P, \/ E  6 ?2 a8 ^) Q( e! \9 T' ~

) M$ q9 `0 G" _

+ X* C% d& W% o+ d0 v) s         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 4 u8 ~4 Y% P. r( l. d

1 e: T) n% Y8 m6 D8 h! r2 h

* V# G% h9 ]$ W; E% ]6 P6 J4 v  + g) j1 R4 q( V: P) E) D1 u' a1 U0 C

" }) D( m! ]* |$ W* v3 e- c

) E/ ^/ B: c# t- G* P, |, M         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 4 {. a2 q3 }: L: {+ I: L4 v

/ @6 m, h1 m v/ O

0 p* S8 v+ I# i$ B2 }, f  0 q$ F) p8 ]5 B

" n1 A( a* ?$ W( J2 Z# }

. b7 t) k6 `' t' y0 u         $this->set_savepath($this->savepath); * d) w# W' k" g; J$ o5 @9 I

& S% m( A* L \

8 _' U% j9 u( _         $this->set_savename($this->savename);$ I9 h: x/ T6 K# E

) i M8 j' u1 ~0 V. F2 ?3 Q

, V$ j8 J( S9 {# E/ k. \  + w5 Q5 }( P4 w5 V2 y

4 U% H- }+ ?; Y

, T# C8 B5 e0 P8 |' \) s         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); , q$ |. w9 v! O, i7 J4 p

" n/ F, I' o8 v5 B1 t$ {3 w' c/ ^

4 c, U# A8 H7 R1 G ~         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); ) Y3 m& ~7 B) N1 ~

& A% k" ]3 U; g. o

( i1 x1 ^7 o( w' a8 L: a' y3 a1 Z         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);7 v6 T/ D9 q8 ?' E# ~% ]

0 d! [) O) `) p% a% D# w4 X( M

. O: K T% R! g; s% h4 U2 Z; O \   ' s$ y" q: e w

2 K3 ]) v( X' ]; p9 M3 S6 o

% e$ m$ W$ t. a$ @! a+ X+ k* _         $this->image = $this->is_image();8 L% u+ T5 e0 Y0 U* n2 f6 @, t

, k9 m1 e4 F3 [) H

+ g: S! T: z9 c! U         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);& N( ~& N2 I" v

0 y, m% @7 y( u2 z4 M2 `% C) \

9 [; p: N. Z' L         return true; % }9 k" W- a& i9 z& b% }0 y

. T+ v. R0 G# _) K

3 `% n& u2 C; D     }} " L1 R! e0 I8 V5 g4 L3 `

( h% k) M8 I2 O% L5 `# ]* R+ T

2 N" J8 I8 D3 Q. R, K, b 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: " G$ E8 m* U1 z7 v a; Y; o( t

6 `' C/ P5 g5 V0 F

. i( l" [( }4 Q4 {; |# B5 V8 V <?php" P& D0 K$ ?* B/ H0 I4 r

* ?+ L4 T- a. w8 I

' T$ d! S9 r1 h5 p     function is_allow() {, X; K/ V. Y# g. Z( r# P

' q$ _' U. W& @( Q

% @9 x* g K% z U e8 Y         if(!$this->fileformat) return false; . q3 o2 b9 b# x% l; J5 n

# t% E6 U7 c% w9 y( P

6 R& K$ }2 ]0 [1 @         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 9 w: }3 s. R$ z

* k4 ~1 f9 W: J6 }

/ L! V" M, a C6 }1 I         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; 3 H6 Z5 j, ?$ U: `

. _+ D+ R* n6 _# u3 u; P9 f! J- }

7 u1 _$ M0 n1 G/ Q5 V         return true; ; ^ {8 d1 K7 C6 a

" x0 s7 x7 S- P

8 Y. g7 U" }! z7 E     }( X5 r" G: ^1 N6 ^

' X1 w. D1 t" M5 T. l9 p

5 m$ E2 _! A7 r9 B 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 ' M; a4 K- \" `! q* o @ u. _4 f* h

' A4 n. m0 K) q: e l

& y: c1 L+ W+ w1 K, V) J# O1 t 接着会进行真正的保存。通过$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文件。 % |- _" |9 G! Y2 g( {

) o0 `0 p& J0 r" e& [0 [% }2 m9 |% @

* k' x1 [5 x+ E4 H3 }! A* f7 f3 r% Y 漏洞利用 4 o, y6 u/ M! A9 Y# d

) @9 D; p+ p2 i: `) i# a# @

* u! Z, }$ S/ p6 e. p0 L 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 * \" t3 v- F- j% A

" U7 g8 W, M4 x/ f2 m/ S4 n

( M5 |1 k C* W, R: M6 G$ M   ' W: Y* {& p) N, N( ~

5 X; U, U2 `+ }, O8 d

. N. }5 X" B8 U, ^$ J 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid" f: z4 E, M7 [: ]5 D) [4 ~

5 C, B z: }& u, ]! v

( {0 o' Y9 n5 g" d4 Q 不过实际利用上会有一定的限制。/ l9 J# [) t) {; H; |; \

0 `. d8 @+ H! P' @1 l6 r( q

, e$ \; O! m1 [ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。) A' y! T1 H0 | C4 a2 R0 x9 T% z; `

! S; Z9 `; R3 H4 T: m

5 z i+ X$ L9 @( @  / H5 t: w! E1 |5 I: ~

( T2 z: \$ `% N- ?* y1 i2 K

. I Z# h- e7 q: _" a 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: : ~! h% s: H1 p7 Z2 ]- a/ a# j1 r

9 r* m* {, ]: r f' V ~8 h

$ Z5 b! i+ h5 \2 H; n1 u& d 省略...$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]);省略...) G. P, E! u+ e/ E- p

: p! K X* @' X4 n# V

: V$ v. e' y$ [" @ 因此要利用成功就需要条件竞争了。 ) [1 x6 O c6 r& p( p# O. t$ J

; g0 J* ^& |8 ?' z [

& E `0 ? J2 Q/ B' q) J% b 补丁分析0 e$ H/ m3 V2 O: T" L- c/ s

; S* ?; K( g7 Z1 k1 y- s# `0 {+ A$ U' L# W

7 ?2 n# F; \$ \( h  3 n5 L" O( o( C- z6 f* b

" A' X$ d; Q& \% }

% Q( V: Y; p5 ]6 p# x 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:. e" A1 \, g7 \( R7 Q

8 C5 p; v+ a3 D7 E/ ]

' Q& ?( e4 X0 L$ u7 D( | function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} - w. W: [1 S1 p5 E, D: |: p

( K% C, i) p( k

6 x- [. O1 n- m3 S   9 @* w: W; q: E& z9 V) F: d9 g

; b% {1 {! p/ T9 h2 w: u

7 Y8 U }# k! F2 C 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 % o4 n* {: O; a- g/ Q- n# p

# c+ i- I- c5 s

2 M! \& M( o1 y; `+ d2 a+ ~ 在is_allow()中增加对$this->savename的二次检查。7 [; ]. T3 `" D

2 x3 ?" D3 K. V% W

1 O4 p' h5 {2 B/ D4 m) r3 w( ?+ ? 最后 ! W x! k4 e. h3 D5 ?$ g& u

" c" J8 P; k8 P: D% {/ G

& Z; h- B& f6 m$ k 嘛,祝各位大师傅中秋快乐! " I; N# T2 B4 V2 o

6 e/ z4 _% d5 e# p, }: ~" ^- Y

+ `& R& S0 c M. H/ k& g. E+ c  , c6 \* l) o# C$ ?( Q- M9 O& h# D

0 @# { J9 L/ C- t
回复

使用道具 举报

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

本版积分规则

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