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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
6 X7 o' K2 b" M

% S" ?/ X1 e3 i3 }: z2 s0 }5 ~

( l# Y2 d0 ~3 E% R! C4 n

8 z& r. }/ W4 z3 S) |! h8 } 前言 ( J# S, n- D9 O0 ~0 a& g( R9 s2 }! [

, M" l$ s2 `& O# i2 u7 V2 V$ |+ u

; T0 n* Q. u- C9 R 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 4 @+ }0 ^, g+ B: E2 E C( f$ R

+ m' X, `3 \/ L6 o" d, S

6 B r1 A( l4 w" b  2 b/ P5 l4 b9 T* T

& L, c. N9 ^. |, \

% }5 A- j$ w1 l2 O 漏洞分析! T" X7 e3 p d3 j7 |1 Q( D: o: j, K

{& x& |; P7 D3 u( V5 {5 x. o

+ f5 P. ^1 N8 n! B2 H: {+ H# \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:, g5 m& k7 Y- n6 B- L' _# q

) M0 e5 g9 k6 R

% h$ {" `) z( s. l: \+ E   $ |4 _9 K9 \8 q, B0 D( t

" g0 n7 O/ ~# \! g1 I# L9 x/ @4 J6 ]

- ^4 o* u1 V- L% E$ D; [9 G 对应着avatar.inc.php代码如下:7 T5 N5 Y3 C+ h5 e: h) R/ ]9 f3 \

% _! Y4 Y0 H1 ], P3 n8 ]3 R

7 C7 v9 q% k! ^7 q& W, 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) {% T8 l6 N! g9 v) ^

- z/ j( ^6 E0 N: X! l

; N% @: D( S$ ^1 [3 c     case 'upload':" u: V2 ^2 X. d7 a" X s

e% A3 {# U2 ~9 P! O% J0 Y

) M, L) E4 `9 r% }7 M, l         if(!$_FILES['file']['size']) {: ^. d! U& X" V+ D. y* b

5 q& j$ p; l- A* j' ?( S/ j; I' a

/ l* W( }7 o& J! K" H( X$ T             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); , }0 [7 l. m7 F4 f' d+ l- F" O

- L- J7 \- V9 |# L' r/ {8 _8 N

" N: P3 j/ b" f% B' J5 K             exit('{"error":1,"message":"Error FILE"}'); ) K8 p0 Z5 ~+ }2 Z

2 V8 M3 f7 Z, ]# u

6 n1 N( e) E5 h! j1 _         }. J: m4 ?% ~6 U6 W

9 p \5 O0 X" A% P& ~

* A. t9 R" B/ z; W         require DT_ROOT.'/include/upload.class.php';- N8 {! |* n' L j8 `1 `9 C A% W

( b7 c+ c4 k- O& ?9 s: V

- R+ R6 R! z6 O: M# K   % B& S) k' v7 V9 O2 ~

0 w( ~3 o# d' {& |8 W8 n# A" s$ c

: c" J! g @% H( N8 E0 A         $ext = file_ext($_FILES['file']['name']);( s# \+ w( n8 p! @6 u, _

5 y. f% F" h) g; o; h1 w# z

/ F! c+ _* |! l* A         $name = 'avatar'.$_userid.'.'.$ext; ! y/ i0 m; V4 S# Z

" q0 w1 b& n# f0 f7 u! m' H0 n

4 r- E/ q% j+ A8 z' {0 `         $file = DT_ROOT.'/file/temp/'.$name;4 e8 f6 a! E( [2 Y7 w! s: F* { Y! ?

2 K; E- N) E) |( B; ]' D4 V

7 i/ a+ {2 g1 c- D9 X; a  ( ^3 o- J1 q- m5 R

2 w/ R& E: {" N% Q% B& Q( U* p

% s# l' I) R; w! v7 J         if(is_file($file)) file_del($file); 3 e3 q7 y7 |. o! }

: f a% b$ c$ R Z* o. ^4 J2 e5 o" P

: O+ m+ I$ } [7 a# S- ?$ i$ W         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');/ i" F1 \ b; f6 m/ D" h- p

8 r8 S% f1 a; U1 N" ^

2 {; ^# o3 |# V+ o   * n9 N$ z. d) I* j

. ]( x& `8 R' T& j- h2 E ~" c

# b( y9 o6 v4 L; w8 p         $upload->adduserid = false;8 A8 k0 F, f. n3 O9 G

$ {1 k6 P( _% u) [) A

9 D# w/ T) v; V" N! V   5 @: \5 A0 f6 W$ f) D6 c1 X$ T

: H$ Q# K$ t& _) p) p, Y

8 N1 t& S( B6 g, t         if($upload->save()) {& H- z6 ]3 p' Q$ H

8 z+ P, a9 S* ~9 c3 P

, [( q! |" s9 |2 D             ...( E" r0 A w0 w) U! K

2 {8 X4 B: ]2 \: j* N, C! a

X$ P( H$ i0 i% m         } else { + D6 V1 j* K7 U) ^! H# d" {

1 q5 e1 s. ~* C/ u7 g7 O: _% R6 d/ i

$ g6 q' |, _( Z             ... 5 f. J* q5 `' C& T9 k4 c

" D, `/ \5 y0 h; G" D, V

1 a0 k6 G) s: N5 m7 k8 O4 H. G         } + H- b; w7 C- ]; C

* i/ a @( T+ O- ~

! E: q" g4 {" G) v' l- u6 z |     break; 0 ?) {/ k: g0 K! b9 E9 c: c

9 H1 q% L; I4 Y8 M. z4 r

7 o* x! N; \$ a% N; P7 V 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 & i$ {3 {) e9 M* u% g( b A

+ o& |. b( |; H( \# Y' n

# u' C+ b5 ]& K# ?; ]$ C, G1 u2 ] upload对象构造函数如下,include/upload.class.php:25:8 T( ~! v' ~+ R$ ^1 N3 k7 i/ n

& z B* P! M5 {0 S+ @

2 L4 u2 w! T; f/ l# y9 }# k <?phpclass upload {" ~, l6 @! b% _, I

! D& |9 p6 Q0 f) j( l$ k

N) Q0 q" y8 v, B     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 7 C+ [. Y" g: C* k/ K% _! Q

3 N+ f$ |0 f" J- i+ q( R& c

8 u9 u3 m# D) F& Z         global $DT, $_userid; 5 d w" T I9 X6 R6 O4 S

/ i( t+ \% |# f5 y. p" C

4 E' E" s. l- L% G2 a: K         foreach($_file as $file) {; l4 G3 P8 s( ?

2 U: @: p! y; z/ I; t( l/ v/ C

' u, j) p" Q: h; l             $this->file = $file['tmp_name'];6 D" w" g9 O# j- L

8 A% J' Y* f- w1 J; p& b3 b x2 L

- W8 _8 B! Y8 b% F( Q# e% o             $this->file_name = $file['name']; - m/ z# u2 s( k0 _9 S; @6 T9 [

0 r" x E9 d& a% l

* i! h, T& m) U/ Z8 j$ ^             $this->file_size = $file['size'];. B6 p3 M5 W' q3 ?0 t, W

# O0 g1 H0 i; G# N" [0 l

4 T7 v% ~) t% v7 L             $this->file_type = $file['type'];& k6 I6 |% e6 r$ J; T) C! ?' _5 ]

' g+ H$ R$ B; q, v

4 [; m+ t6 u4 h: [             $this->file_error = $file['error']; & _( b& N/ v8 a( T8 z/ B

! R9 o% B% q1 F$ W4 ~5 s8 U' g/ o

5 \6 ^: N/ w8 S7 Q  * F9 ]5 [& |: ]- [) M0 i

S1 h) P! c! I1 r% A4 o& l5 G2 `6 L7 q

* Z% T1 P( ?1 ?1 o* E         } 1 d) Y% h/ i6 v

: B1 o: T- v: C

4 s$ E' g. K% y% h' z8 D* U6 i         $this->userid = $_userid; ) A% p1 |) j8 f( i1 O

" i; Q3 s! a* C

J: [+ q6 T9 s0 R/ @         $this->ext = file_ext($this->file_name); , q; I, Y& _: Y% x- \9 V

( H' M2 v5 N: i6 G

) {: B' i9 t& `3 W6 O/ J% |         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];& n9 ^0 L0 {' _6 [ h4 G! q8 j

# L, C1 A8 S, h5 g

- _( i) p% L& Y* t# h. _. v         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; . D0 ?! |3 q3 S' V

8 Y; W1 V) D+ x$ Y5 b7 |

5 Z: o; ~( `0 U) x2 A; t' V         $this->savepath = $savepath;- [+ v. H+ W+ ]( W6 N5 L& M6 G& p

) z3 h, {% Q, l6 `) T! k0 C+ Q

7 G7 s. u* e$ H) [1 F4 U         $this->savename = $savename; $ M: q. Z- K/ P3 q7 q k4 T. g: K) q" T1 a

: d, z% H+ | D1 x8 d5 g% J' V

, \8 y6 e4 s4 l     }} - a6 S C: s8 ? O7 F$ z2 q

& K1 g1 P) ?2 V! D$ }7 _6 f3 c

3 ?# N5 Y& B" [, R2 u% F/ g- H 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。& Z+ o/ e1 M. g, d9 A

6 ~9 a, F5 {6 e0 |% r0 \

9 X- ^1 ?2 Q: e' H% P0 _8 h+ v 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 , ~' u( T# J1 H( N0 [! r. m" [

8 n. ~2 Y6 }3 _; R7 q; s$ ~ O* X

8 k: I( \& g4 m0 p' n $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; d. D" F1 `3 U* b

0 T, g$ B$ h- X( C* i& S

+ [/ n: e5 P$ E& I8 J' q9 o 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:2 e+ w* C! D. t6 j. |

1 A" c7 D" F2 Y1 n6 h6 ]) S

/ U2 a% b5 K4 Q7 L   9 ?+ C; G" |0 I+ J! t2 q

! e3 N2 {7 }/ v) Y, x- l! g

8 [& R7 S" L @! ? 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:5 I$ f6 V" p5 B

& H- v( q9 _% G1 b f J) p5 Z

+ K. p' e7 o) y' i <?phpclass upload { * w( j/ c) R) j0 i8 x4 r w5 K% l. M

* i9 D4 s7 E) k+ P

: d7 p7 z1 \1 a2 B p8 m     function save() {* m( u& O H5 |" `' E! v/ v: Z* X

7 J" x7 l9 Z) D' ?! {' t. S

- Q8 E0 k- i* Y5 F4 _+ b. U! x5 q         include load('include.lang');8 K7 r' O6 J" l4 U: V: F

' H9 R7 e$ C/ N. `8 _ N& U

- a. I# Z7 p; y0 W% n0 n         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');+ D3 ~! n; d l' Q- K5 T: V

- |" Q# u! C0 r3 t" Y" c

! k9 u! i) t, h9 y# H4 l   2 n: H h% Z4 ^ O

G; \6 F& y# c4 m3 X( D

2 D; o, z1 ]9 B' q& J         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');5 [2 b$ p9 |% Q! _2 S

9 Z6 V4 V8 Y7 a

7 K, g5 m6 Q* C/ l' \( u  ) ^. I1 D9 J4 B. h: g! M

, N% I& P' a# w( Y, B3 m. s

, w( v6 Y9 l8 T0 }2 D         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); ! b. K+ b9 z6 L- Z

5 t; U) J3 F5 e8 N1 k

# t& I; J# }/ n& j$ v   ! X" l `; h# M6 x

2 K }7 S9 R, z! G# ~7 d: a) }

0 l1 S5 p# }3 |         $this->set_savepath($this->savepath); . f- z1 O; K# S( m+ h4 V& Z

- t4 V8 S6 s! x% e: V2 Z& d! [0 e% k" K

$ I% c! W* N4 J! `! C( s/ ?1 t         $this->set_savename($this->savename);3 b& D, y P# x6 t6 j+ L3 s

) N7 W& |/ I% y4 s+ ^: R6 o0 H

# E" q! ?6 D8 Y6 @% k- H  3 j5 l, W, U9 w" d! _! t

4 B& f# I( m! i9 j/ x! U! M

4 N7 @6 _/ Y6 q% h) ^* M         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);# n9 B+ i) _/ w( G

6 b' Y2 O& g/ H8 S3 f0 Z

6 j- E) J4 L M8 N9 f# ^/ `% ?# Y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);# O" v( m. k& e5 b; T

3 [! t% C+ w5 k6 D* h1 e

4 g. d( _; F- D; T% _ ]* X. ^; z- x         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 5 }4 m" L; [, A+ [5 [2 ?

8 s- n& z0 Q& c' w5 C

) h; o( L4 y2 l Y; P$ o  " ?8 z. T" o) d+ H/ f

8 q+ G6 p2 @4 G& G3 _& Z/ c

$ F1 S0 [+ i8 p6 G) ?" z1 E         $this->image = $this->is_image();3 a+ |! O3 H7 \ ^

- o; j" j7 G. j7 a

$ M4 s; B# c# R$ s) [& Z         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 8 L, S" u& a) d: Z' _4 V" k5 |% ?* T! B

[! b0 P3 v' _' n6 V

! j$ v2 B% c" C7 i8 t         return true;8 V5 h% p. @; T* Z( ~$ j @! u

# ~; n3 `8 x. u0 C" m9 y$ t

2 Z. e- p6 D, s7 _/ z- w; F, E9 K     }} 9 b0 p1 o3 ^1 K) R6 P+ F8 B

) l; Q# {$ h' f* ?% }

- \* d1 v/ C' @ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:0 D K% E. E0 B. K ^" n0 n

4 _/ `0 r5 M# @1 o" R1 [

0 a, l+ Y9 q$ s l/ r. U$ {: B <?php/ K1 b% a/ A- x1 Z R5 b3 C

K( [: h" ^+ y, l9 q* N' R

$ u% }/ |2 _) F% D& |     function is_allow() { / w$ k* _5 v! U3 G7 P8 Y @

1 s) x/ ^, N8 Z1 s* J

3 G I2 n# c* ~; C& q ?: X         if(!$this->fileformat) return false;1 }) `7 Z3 O5 X: u$ H

+ s# B5 [- \ H, s& x$ J

7 \- _0 G. I1 o, p0 m" d         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 1 A* T q6 h/ L3 i9 s/ f: @% ~

1 l% ^3 ]* n0 E4 {$ \

7 d% v8 [. `. X# A         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;9 o5 ^ c, a8 w: @8 I5 i: c

; Z! T3 z1 O2 b( u& i2 y

* _% ^ M' Z k6 U+ a         return true; k1 C2 `: V& N# s

, ?3 @) O* u( F6 M

6 O1 B# ]+ ~! @     } ( ]2 J+ N9 h, C! g% B$ X

) {. D/ n [" I

7 `/ Z& Y+ O7 U P- x% X+ A9 t" V2 V 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。% K: B. [- X% L

- g; n2 \/ k) m: |

& q1 j) G' q% r7 E6 i 接着会进行真正的保存。通过$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文件。 3 S% |% Y0 ?+ N6 ?) r& v* Y8 f

+ N6 f! C$ l N

4 r' V% \) j2 i- V9 C T6 L 漏洞利用7 U/ b% L: k2 i5 v* x* J0 q( g- Z

& U& y2 v5 u& k$ \% d) c* V9 A, G

; T P/ n4 R; f4 m( H 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。, E7 `# @3 ~( i& ^1 B

0 s6 B. W: ~8 c5 h6 c y+ X

8 s: W5 D2 Z- c) n2 e. Z1 Q3 n   E9 k8 T/ f3 O8 l; V+ f

! q2 `8 e3 z+ x! C+ p' g' ]4 q

# R3 |( Y! j+ x5 ^% z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ' M K( _/ [! ?3 T

; ~9 @3 c3 ]& c |2 p4 { X) q

" i" ]" x% W+ Y; ^6 {8 H1 g 不过实际利用上会有一定的限制。 9 n) G" s& H: _: h1 s: L& c

+ K# _* h- o+ i" K) n) b

0 i! H4 x) a+ R! z6 c 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。" P* C8 q3 k% T5 ]

7 B' M3 Z$ {5 m; R+ W! L

/ [4 o5 m/ `) G% M2 a   ( r& B4 m7 W+ V& E6 O

) B$ u1 w$ s' B4 P4 z

5 i) g6 g( r4 ~7 T. _5 F 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ) F z& x; Z7 E. S8 L

7 y5 s( z% x1 I0 Z. A* c

1 e( z0 m V$ W! ^! F; v$ F 省略...$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]);省略... 5 g2 J' g/ P3 n& [9 d# p: [

0 k* @, h: }4 z& r% n9 I

/ [/ q5 Y2 |% `9 U5 H 因此要利用成功就需要条件竞争了。: @) E) B$ m3 f; V% ~

y6 X3 e2 \! V4 }% b9 e' F$ B7 E

9 Y- s+ S- E: I" w' W 补丁分析 o3 ?2 i. z5 v/ h6 }

! h. R, C& f! I; Y7 c

5 [$ i0 L0 A- d3 H   / n7 L6 w- T' B9 H0 w: h5 e

0 S* x1 r$ ~- b7 u' |7 V% z9 T( z

& j7 X; I2 }1 L ]6 F1 d& s 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: & I6 Z, x7 `& }2 U

" v$ N2 V9 Q6 K9 I% G

3 W9 K6 q$ V( W. o, `5 e function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} " x. ^5 a* n# q# Y# V

9 w3 q. U* M1 \! W; J

: A5 G3 |5 z. T9 s9 g2 D* K  : F( U9 S1 y' m4 _! [1 ?9 u, Q

/ a# w9 c2 K* \; x; h4 E; D

R0 ?) r' _8 u/ a4 @. |' y 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。# i" I$ V& M6 R+ g& ^

6 J j' @% d& |/ G6 X3 c. I. H% x

9 v8 t- d7 ]9 N5 O3 U4 l% X 在is_allow()中增加对$this->savename的二次检查。 - M2 F. h5 T, S, F, _. m. w) l

, @/ p& ~ F, H$ v# F5 a, X

# `$ d9 M8 N# s. ?# c 最后+ L# U2 K5 b2 @0 |

4 b. r A( l: b- [) w

8 _9 V: X p- u0 S 嘛,祝各位大师傅中秋快乐!5 o# \( F) K5 r N0 S% X4 L. o

2 @4 U" }+ t9 s* i8 f3 z

9 I# O0 Q% a5 D- B8 u   - z9 x( j1 A, o* n' l

! r9 o) Z2 x9 O! o
回复

使用道具 举报

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

本版积分规则

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