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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
5 L9 S" v# ~+ E4 j8 V

% z& S( e5 Y- H/ r- Z5 i9 R4 O5 ]

) u; I- L2 j( b5 t) B9 @

( \1 V1 ^/ ^9 W. k/ S; t7 f+ _* [! k- p 前言 + j6 T% X- P/ D- J

: z2 X" l$ a ^# A9 v! g2 Z# u" T

# w2 `( s( j" W: u3 e, e0 w1 d 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。: g0 V5 U; n& O# \# k2 q p

6 t5 y% B; m! b: O( w$ T1 D# C6 K' ^

' F4 t1 U. M6 S$ S. M. D   7 c& V9 S7 C+ j1 {, S/ G9 d

6 o! R# e- D) ]1 K* ^! [

$ g/ [$ Q6 e: X 漏洞分析4 |$ f- _9 N% E

+ }# r. k1 c% k& p

& j; Z& v& T' ]$ b0 T 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:1 _* E* H; @* x# T

4 ]' \1 D! c" I- d' C8 ^

( V) ^8 ~! a; Y! w ~0 y   , T( `5 f$ A' n8 f

& t) S8 H! F. c. F- k7 [7 s' f& Z

( Y& K2 J5 z& \+ i 对应着avatar.inc.php代码如下:: z0 B& ]% @, O; o

" r# Y! o, H# z1 ^$ p

/ L5 c7 m+ d) E) ]3 c( E. g7 v <?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) { : O& M$ T# K7 [) f, s9 U: U

; f& K2 n, ?3 b

2 Z) X# ], ]) p     case 'upload': : J& Y7 J2 D5 ?7 q* p+ \( Q+ b' l

0 i9 `1 T2 n/ \8 r1 s( m

& j- Y1 _6 y/ _4 ?         if(!$_FILES['file']['size']) { / Y; L4 E0 F* Z

6 [: W5 f( t8 `. L

- L/ X4 ?# ]2 X4 y             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); $ z9 ~8 `, i' g) L

. o7 C7 J' Q& _( c" e9 B

4 k. [) t) C0 C$ e: f             exit('{"error":1,"message":"Error FILE"}');& D4 m( J; Y1 E: Z) z# x

3 j) s% B3 y& ~3 n4 M, L# @0 V# N" f

* b0 w6 Z8 T9 C9 L" w         } z0 T- ~- }, }8 Z' F V

( L0 Z& y% I& {* t0 s4 S$ z

) F6 b, n; J. d* @1 k: ^         require DT_ROOT.'/include/upload.class.php';& ~4 j; P4 h3 @ y r# }* f- R

_+ V) V# s- W# F, Q! {

, y8 t" p* q+ E+ D7 \+ {: U  , F+ y- ~3 T: S+ o/ Y

( B0 E( l; M" {' E

4 S0 \; Y* I: k         $ext = file_ext($_FILES['file']['name']);5 Q5 E0 P0 v4 g, y

$ ~- Y( b" W: a% D

$ o* b8 t- G! U         $name = 'avatar'.$_userid.'.'.$ext; , b, r# M! K4 z; E, x6 i$ y$ m# i4 q" m

: }! R) g9 n+ c F

; l3 A, |4 `' O" m+ H1 u* `         $file = DT_ROOT.'/file/temp/'.$name; - u ?9 V# d! q

- S8 O4 _" x- d7 e/ i, H; c

6 z' y2 Y4 l& M. Z  3 `9 b! ]# |& N1 {3 u

7 R$ r# Q3 n, @. u. C4 v$ Q6 p& x. l

5 P! D% P e; N+ o( N         if(is_file($file)) file_del($file); ; a( s$ q9 v& V6 j

" @5 c( _4 s# }* F$ d. i

$ d: Z# R8 x3 T w; o4 g         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 3 d8 r$ z: k, G" D, T

1 E+ ]! Z. w# O

. w+ w+ i2 J( T; z/ Y6 d1 Y' E  $ Y+ d5 R% v) \+ b; q3 A

) I% z3 J& @0 }/ M

7 \9 Y8 L) N N         $upload->adduserid = false;4 ?- x+ v& ~: N7 x) H9 l

) Y0 Y1 y+ h5 b$ m

4 C. h% s# H* o: T/ c   7 ~; O( O# R. M; s7 {1 ]8 [9 q

2 D* r/ Q1 g5 I6 ~. |7 E

9 P# m8 u" e. S, {5 y0 o         if($upload->save()) { * M! j0 U: S% o! \' b; S& m7 G

: B7 L. p+ {; b( s

z$ ~& M: ]; V5 i1 ^% J; d2 [             ... ; h2 U! C% u- G

; o# | F& n8 [* @4 [7 O/ k0 Q

$ Z3 \* I) u" p         } else { F! k; ?# T: T4 {) B: H

' z/ k0 W3 c5 P: G) t8 c1 P

/ P- Z4 |1 H2 L) P             ...7 O) A4 Q- L }5 K& X( c

- W- k- P! O7 B: i

' q& u; I6 j; y1 b% y! l$ d         } + M5 c3 K1 ~8 l; P! B" y

% M) E2 K8 ` b) H& q

, B2 b" [6 Z7 k5 U     break; $ _# u. D4 O9 a$ y

% K& ~! n$ B- O7 T$ ^

0 B4 w5 F2 m9 Q) K2 ]" s @9 g" C$ | 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ) h/ c* ?+ V. c; F$ H7 r

7 g' g. y1 T `7 r% u4 L. g

3 @0 M5 @2 I6 C ~8 e. U9 Y' m* E upload对象构造函数如下,include/upload.class.php:25:% U4 A& @2 g* [. T8 J

5 j2 Q* {" B v! \7 J

. v) E& K) s4 b <?phpclass upload {7 h% T; A5 X* ]; T' O

8 n6 z& s5 b0 F6 f7 o& A/ F/ n

k: h- U2 m( z' V     function __construct($_file, $savepath, $savename = '', $fileformat = '') { : h' e/ p. m p$ m9 z, N* Z

% x; F. _. s/ H

+ _1 K4 _7 c( I         global $DT, $_userid;( P/ x, Q6 c" z" s1 C$ o# g- z1 ]

1 O$ V; R; B% K, D2 Z a* N

* _; W1 w' s) X/ }, V' U* j+ z7 Z         foreach($_file as $file) {3 F6 D8 i. `, b

" F) `0 [* U9 v$ B' n

k c0 o7 G1 o: K$ u0 M: C             $this->file = $file['tmp_name'];( e: ~ S4 p- z2 I

) o, i( F+ L/ X; f- f

1 a |* H/ d8 d0 e N- p& E" Y9 E             $this->file_name = $file['name'];! v; K% Z& ~8 w6 |" R; p

/ u; r A6 C/ \+ ]2 `

* Q! [6 i9 H% @) M A! b& w             $this->file_size = $file['size'];7 A$ K9 K; r3 ^

- A5 y) N4 _; O/ B5 H5 d, p

+ M% y# j, O* d5 }) d             $this->file_type = $file['type']; , E& C8 y8 R; B" G5 j

% y. `6 }* v K' V, w

: ?+ K' T% O1 n             $this->file_error = $file['error'];4 n$ k3 U" E, {& T

3 D6 x; i6 P+ L

3 H+ V& n' D9 \  9 S. \% A- |/ z" ^9 k2 j

: T5 D7 A$ k( b

! `, i5 d- C: V# i* }) \         } ' K6 A3 S& N5 M7 A+ K

+ Y- W7 {: R I9 k( _3 k, G

7 k# O I' y3 n& P$ z# V, O- x         $this->userid = $_userid;4 H% Z% T+ P' u9 ?

0 }# A/ @7 c1 Z& e1 m5 }% X1 a; R+ Y

; p+ e: p) L0 W         $this->ext = file_ext($this->file_name);" K7 L2 p- W' A; o% d

# p9 d1 i2 @ e3 o2 G

8 p. r+ c. c& D; L         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 4 |6 H5 K* D- \) O0 h5 F3 F

2 f1 c2 Y: k: |( i; Y$ e* e

! `0 J1 }- c2 R, D! g1 k- k4 q" P         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;+ m. {. X. C: K( d

+ |1 Q! n8 N- v: X

2 U' m* D$ z8 H \, y         $this->savepath = $savepath;8 E" _8 a0 N) v3 \

$ b: x8 N- _0 b" n; d

. F! k# w. A( M7 `# g         $this->savename = $savename; / g/ \& u! l; l

6 {: [: H6 r& y! n0 \/ u2 p

9 ]# [5 l; Y' s7 w6 F2 S     }} 8 z, ~" a) W8 T- m

( l. T3 c% t/ K8 t4 A

/ q* e5 r$ f" W+ q# t+ ?, Q0 b! c 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。( ^2 Q* a( Q; Z$ j0 n3 S

+ i* a5 Z7 O F8 W* p, S

+ A* t4 d3 r$ G) R/ M5 l 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 4 l" B% I, A) f' C2 c2 I( Q

. f$ e4 ?$ H! q! U$ m

/ B- A8 l: x% I; G' _4 s $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% t& G. s4 b, s( @

( O2 \7 y3 y7 j4 D

2 B: j, Z2 }% H# v* C 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: % p2 i7 h* f- A9 p# Y; k1 @) s+ h

$ m0 z7 X# }+ D/ v3 [5 F4 t; R

; s/ m4 ^6 q {7 V" I, E8 t   * Z: R4 j3 [5 _: k4 Y

9 }, W6 x6 i v5 n

! @9 E, f6 j" S* B! ^9 B+ b3 V- p 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: ) U2 M( L' m, ^2 T, c0 s/ C6 t7 ^

; j6 Y$ Z7 `! u8 L" p

/ S2 w6 ^9 {3 ^0 o* Q8 @ <?phpclass upload { ( @; S% p$ J8 ^! W( \' h

; @4 n* Z; P: U2 Z) ~

" p! c8 Y" f! h, g3 q3 R     function save() { r( k. H5 M3 H# E: }: s+ s

! v0 r' o) U+ \ r

9 }) O2 O) d3 J) @         include load('include.lang'); 5 V$ w& w9 c1 n8 E: r# Q- r+ q

5 v: _/ h% }1 T9 F( ^6 h2 {+ h

& w* S7 a5 [8 t8 Q" b) {" M- n' v         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); $ J, W2 [9 j! N' h8 e2 s

. Y( N. v9 C& E3 ^

& {1 s- t+ D1 F0 n4 C  5 t( N2 x$ n& U! n

/ P, E, g$ v5 {9 w3 f; ]' [" N- }

1 F" }! @% c+ n* ?2 i3 d6 q+ p9 @9 o         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 9 Q8 Y+ @0 k7 J2 U5 W

/ }; N/ D% k* }5 J$ N$ g7 d/ U

. `1 ^1 q2 Z4 Y! p   : v) W/ } Y) J

1 k3 ^. I! x" {) G- ?( N! A

2 {, F2 y1 R9 Y2 w: X         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);2 B. S& a2 T U% q4 u( o

( r3 K8 b5 s. B/ }4 w, q

) Q7 F9 b$ x7 \& ~8 e$ S  9 Z, i% ]2 w8 |

( W: X; C4 U. @" f9 a3 M5 \

, p2 S( N/ q! @0 H         $this->set_savepath($this->savepath);, t( D1 g# [- B6 f

& f# K* ^' ]7 D' H

. V# _ T2 \, w# x8 ^9 X- I         $this->set_savename($this->savename);/ I) H2 i' U' s

8 I% G; i9 S M; l: ]( K

6 s3 J# a$ Q0 x$ P |8 p   & x) ~; Z/ ^; n6 x) P# u

7 s; x' {. f2 l

3 G- _- v* @6 u/ B- Y         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);* g8 u5 h. C" O# a

- o6 U7 Z3 E1 E0 z9 x

' T7 z- F0 A* I7 |8 p( V         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); . F1 q6 ~3 l; J/ C5 a

! B, d5 S. B* Y6 c( s% d9 S1 L- p3 i

! Z$ M1 R: U: o0 s; b9 ~/ }. z         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); * f; C5 s! j' ^- V2 |$ g, _

9 }( [" B8 n7 M: l. q2 u; h& ^

/ n4 q6 W) a) J7 O/ y   ) r1 K# |) Q/ W+ X

/ O, _- Q9 l; C5 i$ x. v

6 u0 K) E0 X6 E         $this->image = $this->is_image();" M! k5 J3 y5 S' E

% M7 z$ T9 J) T

& E4 A" U: u0 e& ^         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);, [8 J- t5 g, s, w+ \5 U

1 I# X4 g: F7 h! v

. \% R! A9 q1 f D' [" y         return true;0 V5 ?& K8 M' @* ]- R

3 `6 L0 O& w7 v5 x6 c* B& T

- ~+ E" |3 }6 |2 |/ Z/ n     }} 8 q7 V# C3 H/ E

1 m4 Z& v( y4 i2 k! Z% z7 ?

6 {: u+ ]3 U$ {5 S 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ' n* m3 ]3 p9 P* T7 f* p0 r }) o

- {8 ^! J& e. C% _

/ E! t- R R; W2 W <?php. q) d5 {9 R9 X* {

, @/ u# Y. Z0 y

" E& S2 x+ ~' H$ [7 H& p( L& h     function is_allow() {3 N1 ?; S5 u8 W: N" U

3 d ?0 U, Z6 ?4 P! Z

( e v/ |3 I8 j8 M/ J$ m+ A         if(!$this->fileformat) return false;1 ^) B* a, n! W# z5 s4 B

5 \+ T1 _* i* t

2 |# i% \. l! B5 z. q$ _. }         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 ^+ S% z* O5 e& D4 Z$ B6 g- u

( Y( b) F: l; n& H1 p3 a

9 h! p, V6 g, G% k4 E$ T. q8 u         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;4 y( V: e% x# p: N8 K& ~

" ^& n7 J8 `' ~/ L. P

* a- \: H! u% b9 c         return true;8 x* W) ?' `& y2 q( V! [

" v: |1 { x R9 j% M

) {7 t: H. o* H @     }, t2 i1 B5 l- n$ a8 \7 n" `) x! ?

3 U7 V, y* M0 _& D

% K0 A+ V5 N, \; x" \6 |6 O 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。$ ^8 z# I0 ?8 ]& U

4 \3 v3 g1 z7 t% r

0 s7 t4 q7 D4 d) c& x( _4 S* l( g 接着会进行真正的保存。通过$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文件。2 n7 l4 V+ |! o1 K0 ~- j6 |3 H1 f

7 k- R: @# P1 m( M. l3 G$ `9 K5 I

2 |4 o$ `! B1 M3 P: F8 K 漏洞利用; D, ]( P0 n: A; l/ v! V

$ k3 F: D( V! {/ P, F

5 D8 a; O7 C ? 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 1 a7 J) _! }. f# t- O. g

_, R' s9 u* X7 ?, C3 [0 I

( i6 C/ V. [7 {# e: Z   " b. B8 \" Q7 w9 s# P

6 P0 S# A6 y* D9 U. o$ e

: e: @: g9 G$ O8 U; C 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid / K; p; P, f( a& O8 ?" p

1 W& g. O+ h1 A/ { ^7 z% v: T

8 z1 G5 _* b" P( | 不过实际利用上会有一定的限制。8 d% P& R3 b S3 K

3 L. g9 K) K3 b: i

" Q+ N# c' ` m- D2 Z# ^6 g 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 + x0 r4 v v7 _* u. O

& C6 ?- X" g% m' _

- I1 \, f# a' i' z7 U   - ?# f1 w" v! |* b. @8 @# Q5 Y

$ @0 C/ @: j* |

) g9 x2 N* C8 D% G 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:4 \6 O2 ]$ E+ S; U* g* d

/ R% ]# n2 ^' H

! h H8 u7 g2 p/ B& a 省略...$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]);省略...0 }) V" M) @2 G' Y( a

M) v$ ]: P8 \ V( e, [

* q2 O) d2 {$ y 因此要利用成功就需要条件竞争了。 9 L0 N3 w+ ]' r/ g G

, N7 l+ X) L% x) F e$ B

' e, P* R7 A6 t2 W6 Y 补丁分析 " X0 H& b4 _! y

' C$ l# g4 C0 D7 W

8 ?/ x- T3 |7 D% I7 O6 D  & w8 e0 J( b( }. Q% C ?: i$ u, ~

1 ~: ^% \" O) ~* W. P

) @+ A- b: u0 Q/ T% H 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: G4 A8 Y; O0 }# B

$ d% d! K2 e" V5 ?" B

1 }- e; Z9 x, t4 [5 Z8 i function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}( d, y6 I" `) B2 d" L

+ _" j" C9 t+ N7 Z& \7 |8 j6 b

' ^% y, M) v) [4 i, u  * v4 L8 X. }+ P# x" r

/ r7 p/ q+ V) ]6 J% H

" |: {/ h" Q/ u. u5 z1 s6 t 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 + j- ~7 w6 l6 z& |; c

0 q/ a5 s ]: \7 i

9 r9 ~7 M" c# ^! n( ?3 N; z 在is_allow()中增加对$this->savename的二次检查。 - j5 Q& \6 f0 ^8 y7 c: {! n

# S0 n C0 D/ s* D7 |5 H

* D. ^# y. b3 a% ?* Z) v* D- _. E% \ 最后: N6 @" v/ Z4 R' p* @+ r$ T) ^

7 T" j" x& p/ q) o* M

) U7 ]' l, ?, D. a/ R8 ~ T5 v: w6 ` 嘛,祝各位大师傅中秋快乐!4 T8 D# X% K* G4 l2 Y# C. |: O3 J+ S

6 K5 w6 j+ d) [3 d( |# f) {/ j

* x' Z! c* w$ g6 a% L  1 ~( f0 i- P1 k$ K+ Y7 i7 j, S

; b$ x9 _. J# K" h$ h8 b' y0 `
回复

使用道具 举报

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

本版积分规则

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