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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
# z5 f9 |- w" A$ z* x- g+ Q. y

& F8 u, M# X6 Q/ f6 E# o" X' ~

& g( g0 @8 G. V- n# M) C

' g) Y) E1 V3 J+ W2 ^! {% A) T8 P) ? 前言6 @& i3 x- {* a1 ^$ _

0 m% t. }$ @6 t

5 Z4 V4 Q: f( v7 [- ^4 \ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 $ O+ p: T( _+ \, \# C. ~0 T+ M. E

- T2 g/ @* ~7 V. w+ D# ]' y

: X" c3 H% y" f3 A* x   $ f8 z5 |7 h$ z; R5 ]0 ^

+ _6 i& [/ A: j$ p$ J* B

w; G3 I/ h5 I z ~, } 漏洞分析 7 k3 `! G" r: F! x) V& P! `

. g8 h) z/ [+ N6 K

' y2 ^ M* _9 y. H- {6 u 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 6 w N2 m4 H! k$ W H* m

# ~, o+ X- e' j0 X4 F6 ] I

+ v; X+ ^1 a. V   8 v- }7 x) R) z# A; K. o# R

3 R, w6 H6 w+ |2 k

' B- X: w: W, E5 x; u- I4 g: v 对应着avatar.inc.php代码如下:! ]6 {" g# ~0 ^; E6 t$ V

/ Z1 J' u$ M) C4 k3 `3 b7 }+ e- L

1 h" N( |+ s8 t* E5 A! t <?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) {" h' x; v( `$ v5 [$ }- e" f

/ y: ?; A9 w1 D$ f" y" F

g+ U y' j6 w3 J     case 'upload':5 ~* U4 a' e _4 \* j2 @

! _3 u" s; b4 Q/ i5 c5 J

* d# d A' _6 J2 C3 [* p         if(!$_FILES['file']['size']) { ( W% T v0 D* D1 s& \3 \

+ E3 v @# a* ] x+ C$ V# N' ?# M) M

! B4 j4 P4 d# v, M0 B             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 0 A+ W u* c/ G( f/ v

/ g- K0 D( y d$ c b8 b: ?8 l, v

( ^5 `& @ a/ z4 }, |* F             exit('{"error":1,"message":"Error FILE"}');0 o' t; i: f& o5 r# [+ T" n- O: {2 `

' G# g5 j+ R( o$ q# G, W

( x+ u, Y6 c3 Y& k+ g         }$ Z% P8 Q. r+ g! M4 X5 h

, U ]% k) J5 K9 B

# w$ l: u: T; P         require DT_ROOT.'/include/upload.class.php';/ Y8 n0 r1 R* v/ P C9 {% ~

1 ^) b) [1 X1 v6 ~

! D) j4 M) E7 }2 F& v* |* z  5 I' X3 a4 A5 y6 x( f, q

9 h' X: P! q# B' d$ F9 \+ y

( l6 w) \0 i; F1 k% `( D; k         $ext = file_ext($_FILES['file']['name']); " q p8 q/ @; J; d

M7 U4 g; P- l3 f V

9 b; u* _7 |& s; x5 I; Q         $name = 'avatar'.$_userid.'.'.$ext; # j* M) } V) n% N7 T; M. x9 U5 N

' x4 \9 J6 T0 u7 j

3 W4 h# n- q* g) ]2 a7 t         $file = DT_ROOT.'/file/temp/'.$name; + c/ `6 e& N; l- p' t( t' t4 U

+ [4 m# @$ o1 V% z" K( B

8 |+ T* P c& h. \/ k' d9 e2 G  ; r2 J7 y5 J$ v

; u2 C( c7 G, s' @5 |

9 ]4 D: r4 [. q         if(is_file($file)) file_del($file); ( D& M3 \- e9 L- L7 Z9 g% n3 L

' j- w7 l% Y7 \3 w

. \- T4 j5 x: `3 x: ]         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); . Z/ }) |/ K) B" g0 i

+ C( k# h9 _2 |! W) r# Z8 p& ~

6 y) Y2 M" H; e9 b, R0 B  , x" s. `' U* e# j- L l4 e" }* r

0 H9 n2 \1 V* t- Y. W

, }! G$ D0 K* b n+ |8 c         $upload->adduserid = false; " }$ g0 o5 E' ]: k0 D* \

) Z- v! t: Y) F: ?* l0 L1 a; t

" q/ c: R, I" u$ J4 @5 S   ; p" ~2 u* H$ w0 Q

6 z* \, K& r5 I( X

8 K9 E* [% C n% \/ L         if($upload->save()) {* L, e+ ]: i: f

: P# ^) s4 U, r$ D: X: |* V

- O9 Q2 x2 K" u5 P             ... 7 ^ t7 q5 @/ K6 s" \5 N" O

- R% Q4 }9 {# K* |8 o

: G3 b& V; ~! w- x7 |         } else { - Y7 g( _% O/ B) I

* Y! L* K( q% G- h

# E. L3 z. o) g/ } N             ...- ]# |6 b2 M% s7 S

) U" p: X' U" S# F3 C5 o+ q2 H ]

! |! ^5 @5 w" y3 S2 f! }( j         } 1 l: k7 J5 {( f

# E" u. T+ t8 m0 j9 H# A+ `

9 D( M" B( f1 s, p2 o5 Q0 \/ L8 p' x     break; 3 z& {* u; p j6 k

/ I# K& L: Q1 |. P1 `

) i9 |; |# {5 B( t7 e 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。5 D( p5 ~# W3 w4 E5 ?$ ~

0 j4 u- w# H m# t% X" A

: r2 G& u m% l9 c upload对象构造函数如下,include/upload.class.php:25: 7 F* u* H0 S' L- S* P. {

; F [: v) u( Q- S% E

2 @0 X; P1 W9 x# F/ C2 Z0 Z <?phpclass upload { / I- i& ]' G, M" l

9 a0 n8 n7 I/ [

. u) v" w8 H( f7 C1 p W3 L( l     function __construct($_file, $savepath, $savename = '', $fileformat = '') { / g5 J, v( l9 l) e6 D

" G$ h5 J& N# f

9 @ t, c& U, K- w: P6 {* T         global $DT, $_userid;! O! e( `8 y6 D

# C8 [: V) v$ a5 }+ L

, R( O- z7 i$ B         foreach($_file as $file) {4 i- P" {, n* H! O1 [

- c* q0 E* p% d, a; Y3 Y

( u/ j9 m- a0 v/ \" Q! u             $this->file = $file['tmp_name'];# }( Z" F: i& W! ~. \

+ d. p) d0 K) }/ j: N; Y1 G: V

" n M4 F+ G2 y6 P3 F' L             $this->file_name = $file['name']; ) r" ~+ Y, s9 n) D' o% ^( E0 x. V

2 {& S& d5 r0 T/ J

- {0 A$ Y& b# C5 @             $this->file_size = $file['size']; 5 Q& _+ C& p8 n2 _5 k

4 X: m4 y H5 ~3 V) M% [

* i/ @. h. T2 s8 L# b- n+ x             $this->file_type = $file['type']; / L, D9 Q1 Z/ Y( F. o

3 O9 z$ X9 R3 H

0 _% D4 C* N% {3 T2 O& X             $this->file_error = $file['error'];5 l3 b" L, v) K

2 r7 w, U$ _$ G6 y: ]

2 @/ n, w' N3 I0 b% p$ @  + O9 C1 h8 V' m7 T' s9 C: `

4 ?# R+ |) A9 M

7 b. l: t% ?& ]2 }         }7 }6 i T A% X e" A

* ^$ F, T4 U7 _

. ? R" V5 { {3 X7 q         $this->userid = $_userid; ' ?/ U) \& p7 T

2 v7 G+ l, _3 P; T+ `5 h

6 l1 E: P/ M" [. s6 Q4 D# K         $this->ext = file_ext($this->file_name); 4 N0 K' w6 v8 C

' A' w$ r" w, g

4 J$ N" J4 e5 s e; s) F- G         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];& W7 a+ M v, t$ y

& _' p& r6 n6 ]

4 ], S( r+ P' j' S         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;2 c: [, u! S2 T7 m7 o( x8 G6 R

$ u/ O- Y% H( V+ D X' Y

# Z/ Z" l4 X# {: i. ?         $this->savepath = $savepath; 5 J" T0 r3 H+ D, q2 d- A) t8 d$ W

: S: } L s( ~

. ~: v6 `( }6 F         $this->savename = $savename; * e$ C/ y0 S$ `

' {" x% Z3 O1 d) b& l1 ~1 Y

8 z3 P. I' m7 q5 j, o- G     }} 8 g8 g S& i# z- T+ U$ L

5 p' i3 W8 t# c' }: ~* f) M4 C

4 {. x( h h9 Z* V8 w" | 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。1 d8 y7 X( f% j) Q* u" G

; C4 k7 R% w/ e- G) i1 @1 j

8 `8 @6 R" O) N& p1 Z, j I 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * E1 K b D- J( p! f1 L

! t0 ?' }% K# z8 y, n4 S8 w" w5 X

2 {! Z8 F9 }# {0 u $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( x' j+ T$ f; T# S% g

O' M& ?) [$ @# d" P

0 V9 w' e+ o# X# {- o7 Q5 a 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:+ e6 f' {3 _+ T% ?' E

$ w8 w& f7 U3 |$ n

: m P, C" p$ Y# Q ^6 p: `   % K# [9 _- O& D* |

* p' T8 A$ I# w, u( G

5 v% }+ e5 D/ X9 i. o; I* j 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:/ M# |: _7 R* v$ A

( f$ e( ~3 h: G; J8 l# O4 c: Z/ \, L

! f/ b! T; D* n3 U. E <?phpclass upload { , Z! b* k7 T5 ~# X/ @, Q( W, y1 m

2 ~; k: y7 G$ x9 I& R3 R) A

4 T5 y& G' U9 V, _% ~- P. L/ z     function save() { 8 j3 I2 q7 s4 U' i! r% G4 s2 n( S

; M- ^# M7 `! @! B9 G) K8 j2 p

/ u" _3 l. s# |7 W1 \         include load('include.lang');# A4 o( I+ A3 R' p

* F' Z! i7 u; h+ J* Y

- h. F, y1 P6 T5 d6 @8 w- ~         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');* C5 M( W+ A+ J7 A: }

- {& R5 M) o& m0 u) V% e

( p, d! B7 y8 L. L6 W4 V  0 Y6 Z. J; e2 u# i3 j' ]8 y9 q

4 Q* j, a) i: R- v. N) W! `

' J5 o5 s, T9 r' ^         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 6 E6 ]) n- J% n- [8 U5 U

$ P4 U& ~' R* Y4 T, h* E/ x* Y

% ^% m, T& K- U/ ~- }( ]+ s   ( w4 y" m8 |" x5 j

9 |" Z4 i" T, p p% D# A1 L

* v7 B3 b2 L& u2 y         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ r/ K0 {* Z% w" X' V

3 r i$ @% k$ g7 r: R

+ X& p; y- z7 A. t3 T& t+ S' D4 R' }8 @  ' H L* p! X0 R$ H9 a O

* Q' a4 A- }9 r8 O) ]! |; c4 X

) Y! R+ |: z" [         $this->set_savepath($this->savepath); 3 j6 {3 d9 w9 i+ t4 I

% v0 y! v2 c1 [- y' z; v: w

! s2 g4 [* k) d4 s# ^6 M) j         $this->set_savename($this->savename); Z! {9 S5 ?2 ^- I7 J

2 X$ p8 F) b, o/ t6 w9 V

3 w1 T/ D7 H( @1 D   8 v& b) f7 B5 l. r% C* O1 B

3 E2 b7 I/ U; i ]7 J1 g' Q

, N0 h/ D9 M/ k9 v' B0 O         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ x3 q& @5 L4 M( p' T+ L

! X/ V, w |* a6 u% ^

. v) f' N, ~! X# Z2 ?         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);$ E" y, e& m0 D- M# T

( R9 \$ J9 g8 E/ P! a

4 @' r% g6 y" y+ ~- f         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 9 p8 ?. R" J% W* m6 r

: ?% e. ~% |0 S ?: H9 C2 Q

8 _- e @1 i Z% x1 [3 u  : e! W3 E( C% k# J

1 a4 K: i, {6 P

+ c8 \( v( B6 J8 m         $this->image = $this->is_image(); 4 H/ P7 B/ O* V# H; o! q7 W

: N+ u% f) F$ I9 _# ]

* K2 E% u5 q1 V         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 3 s& @' d& k# }+ L

: j! {% u5 P5 T" n1 i

& k D# W9 F3 B% | w/ C a         return true; 0 E5 `" i* R2 G) s+ m0 u6 \

! p. N& m- W8 c7 I

, D) R; e# n8 z     }} 2 k: {9 [ d& j

7 K7 z. h9 D' k: L

" N, S& _; ^/ f7 H; R/ [ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 1 }/ S0 l; a: E5 {9 X" u- `& C

9 r# p. i- i1 c! G# v3 r

/ S" b5 w& [2 T/ j' h( {- ` <?php / }1 i7 h0 o' L% O$ l4 J3 X+ L, p

0 ^3 L. G. a: `9 k

1 a# c+ \2 R8 Y+ C     function is_allow() { 2 A" c5 R9 x/ M$ S* @& o4 I/ w9 Z

7 t) ^) n# f- [7 z3 b+ K5 Q

4 U0 [7 P2 V ~4 q9 M7 u         if(!$this->fileformat) return false; 4 V7 S. m5 o* J3 u4 S: @

2 Z% Q+ J' l' I, b: e

( [5 Q; B$ ^; Y' Y) _         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;' E9 T' W0 I T6 |9 h5 o; a

) E* H. {( W( B/ {2 M# m! Z$ w# I

, }& O* ^0 m+ K         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;0 ]& g: e% ^8 E% V

6 H1 D* U3 M; e! s2 }

, j2 T6 M' w, b, L         return true; , T! c3 w ?# |' `7 M/ K

( ^" x2 U* Y! g4 G0 H4 d; V+ G

6 y! s7 |. j* B4 S0 b     } 9 u5 P3 V6 `3 Q, n. ?5 L1 w- O2 H

# K6 D. U5 G" I: A" l

0 Q' {; o; g' ~3 y( U9 _. j 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。/ Q( s' Y% k( a( W5 a# @/ a2 H

A, X4 p: ^; k2 o

8 u6 d7 B2 L; _, Y. I+ y 接着会进行真正的保存。通过$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文件。" G/ L: F/ V' S. n7 t

4 H2 J1 f# ]8 ]% j& v3 Q1 g

1 o) N) `: u8 v* Z 漏洞利用2 U- m6 e) e6 Q7 k4 _

; [& R. P1 J$ D6 U) p2 t' P

! _) n% }+ Y- d1 {0 L9 [! i 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。$ r; G: g0 R8 d _8 U

6 i( {9 w3 x! `% w, `. r

# W! \" X5 S0 f4 [6 @/ l$ T   ' ]2 L+ q& |; E' R. H

5 s8 t: z( ~/ Z6 B

" p& [. d2 j& C1 @6 q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 3 z" |, |0 M) I% R' b" b

$ _/ V: V+ ]5 T- T6 ~ e4 r/ }

; @& T# N3 C. E' z- x1 e/ g0 ` 不过实际利用上会有一定的限制。 ) g( M6 X! I6 j7 k0 u

" d: `7 S, D6 n" U% |6 {1 @5 a

+ W+ K+ m+ E& [. G/ u d7 f7 ^* {' O% x 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 F4 s9 j/ z. N) r- B

2 G+ H& f& u' d

. f V1 B; D% R9 s: ^% e( j( i- A   - w9 i9 Q" b' B+ T Q8 o

( y6 P, J+ h9 Y4 g) R% z$ w! T3 R

- [( \' H* z5 D' h2 k 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:6 x) f0 E5 i( F! l( {' l

1 z2 G6 W1 j: x6 {3 Y8 U

' N6 P! H: m+ ?" {8 e9 t$ K# h: X 省略...$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]);省略...3 K" z3 g& A% g

1 ]8 q6 S/ W% k! X" @1 A7 {/ o

! C3 v) x! Y5 @6 O3 o' H0 ^# t, D, @ 因此要利用成功就需要条件竞争了。 ) A5 a: ~5 C2 k$ {* s& \

' {1 L! ~3 H$ R3 P

( e! _3 u, h" ]+ f( g: i 补丁分析' ^% B! O+ E$ s! z6 E" V0 h6 W5 ^

5 h! d4 u; i7 S3 K2 h8 ?

# I" q' W/ g: K: W- T0 F   # R% ?5 d( G% H% l

; W1 O( U I: ~& R

9 j4 N3 s' p! f 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:& O8 W1 d, i) `; D+ m1 p' {

9 p6 M6 S( P' k3 W7 s" @" H

( @4 x# h5 \- {( w function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} $ o; Z% l; {. W7 O3 [5 I) k, Z7 G

; {7 k! H4 X7 B% B1 ^& G! V

* J5 r5 M# l2 k  : J! S3 _" e) P. P

! k7 U* q0 Y4 k2 ~! Q1 e

/ g) J! z: U( O2 W* \/ e! L4 m 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 2 h+ Y0 O2 U: y8 \

1 x. n8 K5 Z2 ^/ s$ n( I$ Y5 n

% `7 d8 L7 _4 N% t7 t 在is_allow()中增加对$this->savename的二次检查。 9 F' J- D/ k% |: `: `) u

F$ H' \% o7 I3 o, x5 Z

6 U9 F9 |/ B( F3 H 最后, x6 T* O g/ V# F% R

* {: W* ~' D$ H5 ]* x

1 g; _( t( ]5 k& h, A7 S+ d 嘛,祝各位大师傅中秋快乐! . | C2 L4 `/ ?, |: O0 v/ S1 O

: A o4 o. @: g$ z

9 N Z1 a% V5 \9 V& J2 D+ b: B  1 _% i8 J' J! m6 F, K

3 ], L6 C; ?1 ]/ ^) O; J' C: D2 p
回复

使用道具 举报

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

本版积分规则

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