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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
4 w# i) H2 K. n+ b# V: t

6 x! h z+ H( e$ T

, Z" [; M" N6 B. N" B3 Z9 [

0 f5 m4 @% L& E0 T+ a 前言 7 d8 t6 i( M; Y! V j

6 [" H3 C; A; o9 Y1 q( a

/ F4 h6 L1 A: n' w3 T, ~; T! F 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。' S# r/ l9 V! ?; N/ J

( ]1 R4 [) i1 }* k6 g! q

6 C! ~+ w0 s, R; K' V   + |+ K/ E0 h: V7 r, V7 J: `3 v9 s

: S \ R' o4 r# q0 R+ _- |/ H

- W, w/ L' o: c% q P+ l! @: m 漏洞分析" v: F# e3 h- g! p" N. n: W# k( z

# ]) T9 J/ N0 n) \4 f

" K+ C/ ?0 N2 { 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: % q4 ?0 C6 E b8 }* l

! B; U2 J2 c8 u; z( C8 H) _. W

2 c" x4 i9 r3 r' A. D0 K3 y  ! L. t- T0 b+ F0 _$ _

$ D9 \4 P" y: h5 x" Z& X8 }/ f

3 |" U' A* |0 @3 }9 a 对应着avatar.inc.php代码如下: m, K( e+ Z: i8 O1 o A, c, G2 z

; Q+ x$ |/ n, d! q5 o; w2 O. D9 F- f

- g! M: s1 w2 k& ~ <?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) {/ ^; R& h) C0 a) o( ?6 U( Z, @

! Q* Y* @# u3 ]+ W9 v" J

/ y3 x* }* ?# B9 M- y+ b     case 'upload':% r5 o6 o$ i: q; v& A: d7 f

2 o7 Q8 E$ y! F: K

9 i/ B; J. V E# P- F Z& M         if(!$_FILES['file']['size']) {% A4 T1 y- L# z/ \' C$ K1 O* V

v9 d+ A2 g, Z7 q4 {

3 ~; x2 O8 M# @1 h7 ^* L             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);& D# \. ~# h! B: l. ]6 u2 P+ U8 N

1 H, a/ I! I% c' t

, z0 N `( F1 o- Z; X2 o1 H( C2 I             exit('{"error":1,"message":"Error FILE"}'); 2 ? D" ~# D0 G% `/ q) S

. x: A( L" D& {2 \+ J

- N: R6 U! g4 R+ E& r         } j/ G( Q1 G3 C) M/ s! H6 H

% G# V# I4 P5 [. o& @' \

8 U# O' x5 s4 e" h! F         require DT_ROOT.'/include/upload.class.php'; / v+ o7 b/ L% I0 S% p: E

/ m! w/ s+ b( ^7 j3 V

( x" m( T6 e: Q: Z5 J3 X* [  1 Y9 `. o- j8 H4 t, U' @7 G

; e- H# W% \. U/ {$ \8 O

3 J( a4 ~" y. A6 G/ W( {% D8 K         $ext = file_ext($_FILES['file']['name']); ) C2 Y# M) j3 b

; ?! c8 @' y. d4 V; n5 z

1 @, b7 p9 s4 x$ L9 `         $name = 'avatar'.$_userid.'.'.$ext;8 J) G$ L" p6 O' Y8 m( p8 q+ n

9 S9 F( q; J# ]5 q! S, U

0 R+ n: t9 o3 M) ]3 @' L! Y7 u& a         $file = DT_ROOT.'/file/temp/'.$name;2 ?3 e. E, w0 [2 c$ I- I

- j/ m2 a) s- y, G! ^) d

3 ]8 O( S: M g! [/ x, n3 e   9 o# L# v$ c& v6 H

- ~$ Y# {$ m& f- j

5 [1 i8 E9 ~4 O, C7 p& I0 a         if(is_file($file)) file_del($file);/ t5 l% A* q4 a8 o/ M }3 C5 M

" K/ G! s: Q1 \( ]* E

- W" m. v5 M" B. d; V. O         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');* e( |2 Z6 h6 S7 G' }

) L C f# O4 N

3 f1 I3 J8 f: e+ K, O6 n' G7 n  - ~' n: U0 S' V$ w

& {" m6 Q; u+ o1 K2 m; I

/ Q# q. j& ~3 }5 @9 q         $upload->adduserid = false;2 @& ?0 t' ~9 s* o: F

4 s+ ]; X# L. L( j8 F, ]6 V

g- V( n# w; A+ \' f   2 I# V2 v K% X a h' c% f

* I5 u$ I( S4 N9 u' B

7 h+ m+ ^4 l8 j8 N) C* m         if($upload->save()) { 5 F- J4 _1 [5 L- M

; {% f, B6 x1 Y) U: d* Q" N" c

% m2 I0 y9 J' D% F             ... 0 I' a+ W# O B d

' q4 k! ~( l) `2 _ S1 A

* b: |3 K: Y9 r7 Z% b         } else { & \7 E2 t# }- ?; x" q5 U. j

3 j; V) c a4 Y1 y$ r0 ]1 z

0 L9 L& ^; n$ Z, Y2 ^             ... _6 t# j) | e1 q

1 a1 N: g, S/ X5 T i; ?

' u- z/ P6 |& I( h& i: h         } . V6 {7 _( \: T5 S

2 E1 W& U0 b0 @) n; m! X9 y

% U/ ?3 c0 {2 g6 l1 u. _/ P     break; 6 w7 `. w/ I% D. [& e

r: W' z. o7 x

0 f# g. Z( v. K" f# w8 a* x 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 # b; l2 _, k) x& Z/ y2 B( U

' _/ ? L T# |, F. l

3 @% h$ D" j2 c3 B; }, f upload对象构造函数如下,include/upload.class.php:25: , z2 Z! S. }+ G! A( T( i8 Y" U# t1 y

w1 M# V/ S2 u" f

! z6 e; m2 M. L' ]( M) }2 g2 V4 k <?phpclass upload {' Z* u m' g5 h { _

9 A! l- D* e) Q8 T

7 y* j; x, i$ o& q- j8 d     function __construct($_file, $savepath, $savename = '', $fileformat = '') {+ V+ L5 z3 l/ m( P% q4 s

/ D$ Z9 M9 k5 |3 [0 [* ^ E

1 z2 I; H& B* U6 R9 R3 N         global $DT, $_userid; " K* b3 W# y2 O7 W

) g" b4 s! I' H; v% a

+ L m h8 c% m         foreach($_file as $file) { % ?& v5 ?! H+ h( l# \% e; U9 i

; U _. w8 U* S4 T2 m8 P

( E; c% d3 u- h, r/ L             $this->file = $file['tmp_name']; - s: ~( C2 _& F | `

: j% S1 c r/ B( c

. C6 C4 z4 E) ?# S+ H. @; C             $this->file_name = $file['name'];; X0 {% X5 f' F, p! ]* c

% j. R; f. C2 ]0 J9 O# n

( }: n8 @5 X' ]" n2 P8 F             $this->file_size = $file['size'];, n: c9 {$ {6 L G

# U: E2 ~ H0 ]8 ^

- Y! `- \% ]2 e/ u) x             $this->file_type = $file['type'];7 @) s6 z: Q" O" K A, `

. p# A. _& E# T9 v O

, W0 e7 M+ q9 n% L2 ~* y             $this->file_error = $file['error']; & y7 x8 R( e) M. T% {7 ~. A& W: C

0 t, |. J3 A+ i8 f: w1 v+ F, K

% x$ S: s8 w- V. P   9 I" i9 T6 N0 J3 L6 _8 O

/ M& C8 |% B4 R

6 L7 o% Q m Y, H1 a/ ^$ P% D1 f         }& L% n4 T9 F0 V; k- f* M' t0 r

' [& `/ K# J+ z. |, [

$ V: w, d, L( b( e$ b7 X# A         $this->userid = $_userid; # Y$ _; a/ g# z# U

) o8 O2 U" m2 x* M2 A+ y

& k1 P9 v' Z9 f. `" F. u5 b         $this->ext = file_ext($this->file_name); 5 m9 K& t6 H% |5 c4 F; R

6 ]' T S9 ]& x8 \; c, Q1 R! h ]

( Z5 e' m) h6 o) z         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];' t+ d1 H9 T& y

/ D7 Z/ V6 i# W. u7 f5 Q7 j2 T8 l: R2 n9 m

! K( {$ P- L3 O8 ?' L b         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; % X- K) q: N2 w- n

& i1 f, Q3 p1 h/ f( {& Z

$ D' h2 h/ h) @ R/ k c9 }, D         $this->savepath = $savepath; 3 W% R2 |& s+ v. ~- `9 V

6 F& Y$ Z! r3 z

7 N$ Q2 E' y% }$ m: e! _         $this->savename = $savename;3 I) x% ]) g+ k5 M7 B# ]& _" G; e

6 L) J( m! D: K+ E

, ^ A. Z9 x5 z     }}& N k$ l, m ]" z4 y' }! V2 E

W5 u4 t& l5 y

3 l; I7 o8 K/ [% F0 E3 S 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。. W+ l, e* P( I2 j

# `9 P1 @$ a6 P, q5 A8 K- c

t5 |/ J7 d% W 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * Q4 F/ }5 b4 G0 ~7 N1 {$ y( ^

) K& I9 Z% G" N+ z: g8 q

+ j% y+ l9 Z& `0 A6 T $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! H) V+ V& `/ ]3 _6 l' [/ {' s

6 Z. R0 H, v; n/ L9 ]/ T; ^1 b) ~

9 |4 P$ d9 G$ z 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: + V# l( W# T& B3 k

2 m6 L1 @/ u5 S7 ~* L2 [

J" L: B" l7 y8 u8 l: G% L   2 n" f1 c* E: P. S! T; j% T6 c

! N! l$ W- S! y# U n

9 W, s6 A8 D8 c; e7 l 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 5 ?1 N" E+ a4 G- i* A0 w! \

3 ^7 m; \& f- d* }4 O4 l i

& q# g, R# B. I6 ^% T <?phpclass upload { 1 A/ G: _% b+ l

, i/ |6 O! G6 q; p2 d+ h# p

& I- `# ~$ A- Q7 Z7 B     function save() { + _2 f$ s: {% G. R. f9 o% G

: |& A4 v" t5 @" R4 w) P

- W9 f* g. E1 j/ y; h2 w         include load('include.lang'); % {3 Z$ s) K6 u3 k+ U0 S* \5 f1 a

2 |& Z6 X6 c! E( `8 S" v3 u& ~

5 W& p4 Q7 a4 D6 `; X         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); - }' K+ u/ ]- D% a

/ i/ Z' Z+ T8 n4 O/ W3 f

3 I7 r6 j- c9 Q0 y   ( m- o4 U8 P. w7 o6 h) T

2 u% h+ ~* o, I0 ^* ^& e, n; l% R4 z

1 v5 Y& L. M/ ?1 ~/ |+ A         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); H, ?" q$ R9 L

, t0 C1 \0 X$ C# ?4 B

: |7 r! m6 P3 _; @3 V2 b   & B# G! ?$ q1 N) M8 L6 W

, M2 F2 _; c+ l: h

' k% s# y+ O0 K& p9 ~6 _6 w- y         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ Q* {: O! n& H* U5 F

* j1 A1 p2 U" X6 }* ^9 C9 q/ s

* p% `6 p8 f% K6 Q1 [' G+ Y   0 g# O( |. k/ ~; n8 Y& u; Z

9 N6 j/ z, z7 [5 g: Q

3 Q4 l+ j+ g$ Q" R( v         $this->set_savepath($this->savepath); ( @- \+ n/ E- F. ^# G

8 l" m: @- _# B8 h4 W( B* P1 J

. I! n! ]! ^2 N! B) j         $this->set_savename($this->savename); - m6 w, @3 e5 _$ y! B( l

7 N! J$ g4 M( u$ G4 V5 o, B

4 p6 x' p. ^5 j1 \9 M, E/ r  ! u8 E& a8 h/ a9 E" z. L! Q

) L* Y2 x1 A" s

9 F% Z$ }: M, H8 @         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" k+ o4 f7 a" E, z! i+ b

1 d8 o# o8 [; K6 _# x- c j4 |

! L* b4 ~9 T. u5 Q# @) o! b! m. @1 N         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 4 E; x( y5 v+ H# o% p

- M2 m5 i" N- M2 A

1 `* H, y X" q         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);( J* X: r3 l5 }

& [; D2 N- U# L$ E9 E) Q& L: R2 x

8 s* Q; Q/ C: i5 v2 H# s   # M: m& Y# n& p

3 f' W( G a( |6 w

4 t7 L, { I$ X) L' x y4 o+ A' M         $this->image = $this->is_image(); ( Y7 D# V' M( f; r- a/ ?# t

* m7 a2 O8 X b' P1 z% ?1 |$ ] e

$ ~/ z, ]9 b9 z* `         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);6 K7 E1 V0 b& s+ Z' e

- A* }4 j. y3 E: d! O' Q

1 w& r1 s- l0 g- p, w: M- S- t         return true; " _( U" f! N- D* E( b

" `, ~3 p1 D0 L. K! x0 Z

1 v5 w! W+ r8 {" D: F" O0 {7 g& u     }}: V w# A) s, J# b# w3 D: `+ h

- {" L; {' z2 o# \' A0 Z9 a. q

( F3 V' c0 ^; i3 @; z 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 8 H. |. `( `! ~3 v/ `

! z4 a& |$ @6 [- ?* H9 d Y

3 |) W9 a* `, `0 p3 B <?php9 G# x. {1 k9 i, c9 @

; d Y& F" Y: T( Y" X' J, E( I

$ ?6 f- g D& P8 r2 W, c2 v     function is_allow() {5 E& v8 x- |8 ]

' g! C4 c& i3 I3 `

0 }1 P0 c6 _* y- R$ y         if(!$this->fileformat) return false;, M6 |5 X( r2 V' b, J' x/ v4 n

8 S& ~ U! M0 P f5 f: d2 [1 ]7 r

* Q% l. D7 R2 Z: {, ?& s. G         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;7 @3 j) T' S4 t

4 ^7 p( F- ]- D) B! D5 X

- P+ d0 H3 C0 W$ r; p3 @         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;2 M6 I+ I; k3 |4 t* N* b

' R( u/ d1 H" R6 o! _1 `- ^2 ~

- [* N. e$ Z1 r* R! Z a. }         return true; G1 N6 D0 c* i* r4 V

9 Z$ X s% n5 S, b3 R

/ Q* i1 H9 R5 C3 p2 @0 n     }$ N# r% H0 _+ Z, J; x8 l& h" s

0 s3 Q% r, U9 i. _* V/ c" f/ y

" C X6 a3 _$ A+ x2 P' n 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 , U& d& i* u& U

/ n( ? T8 N+ X+ t& `& C

6 a$ }: s* Z$ e$ g0 q) E1 | 接着会进行真正的保存。通过$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 n+ K9 j% f5 H4 p1 R6 k3 h6 i

0 S/ Z4 X( l4 B/ V4 e" P+ _

# r+ e% s0 ~- G' i5 j% `" F 漏洞利用 9 z O0 \5 P4 l# s/ h, V0 M2 ^

: C$ g5 Q/ h# Y2 E; `

, J1 m2 C0 Z+ E9 |& c4 ^+ W: `4 ` 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 ( i& ^# y* M. j* O+ c6 t0 j

; F, }( [' s4 u! P2 k5 V1 S

: N2 f M) T, \* f   - E6 G, n+ x1 F1 i% E0 i

6 U1 D9 r7 w/ p- }2 X* G+ W3 N# V+ U' I4 T

( G8 O# w6 {4 y 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ( c1 \( \; ? h. {: U

2 g) u/ N6 d- ?* m$ [

2 [+ S5 w' ^/ D 不过实际利用上会有一定的限制。 9 b7 c4 l5 E4 o- z& z/ A6 u, A1 t

4 `' ]6 T# [. Q. r( b5 l0 ~ ^

2 W9 f; U9 z" w. \ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。& C3 x2 s/ Y4 C! Y& r6 S- P2 b& V$ I

, W% e9 z ~5 J; D b

7 m9 P9 |* q8 C   : a0 q+ T( C3 f: s

' l4 b H6 y$ A% C' O

0 o4 I. a( q v1 t N% y 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! s T. S+ I9 D# ]4 F

# J3 z. z9 C& G: h

$ `; {" F9 W, D; n, R 省略...$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]);省略... ! C( @$ R* R& y, p% K. U

0 y7 z* V+ k+ N, [. G+ H$ V: ?

, T/ x6 u, l' `" K0 u6 Y 因此要利用成功就需要条件竞争了。 5 S& O; h/ ^ @' [* M8 l3 [ F

% m0 k6 x6 w; P/ H, [) [

6 P2 a8 M* N) ~1 t* ?% J6 N 补丁分析 * B4 a! l( Q/ K( ]8 V! T# ?

}# G5 X" r" J& `

- f; L; I7 d& X% U% |& V: A+ U  " W3 m4 g0 J8 }

1 r: k9 @3 ], I7 I

: @5 N) ^ w8 ` _, P) T3 F 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:! x# C: C" t+ _. B

. h7 q( g8 ~: O" K, Q' y/ {3 M

. b/ m% k) j) @0 S% Z+ Z0 E- D$ G* { function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}" G( H, L. `& m

3 n4 ]1 j! T; a$ r& G

8 f! O, G2 C- A6 X  ) a$ R' a( d/ G5 g" T& J* k

0 b. _( `. c2 o$ M' r( t0 q

- y- O2 L7 E+ k) {2 o# P W$ [ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 8 b9 k* e. r# a+ ^4 _( F

) \( ^, B: B# A; Z4 Q- S. g: N, R

$ M; {+ W b2 {, @, f 在is_allow()中增加对$this->savename的二次检查。 4 t7 u1 L' c6 |1 d! V6 f/ ]. l

" w q4 z7 _& |6 Q4 G9 _% `

# y9 O' X0 |5 ~& k0 f 最后 + K7 H( F- J5 ^5 u$ [

9 o+ B2 q: q) ^

! p! ?3 |3 C( K. E, s 嘛,祝各位大师傅中秋快乐!8 v+ n+ x, X3 {: W: t$ L" }& |+ x

6 q5 w9 _3 F% ~+ o4 Y* \

, _4 y0 S, h+ J( v" S" _   ; z0 h8 ^3 x: v8 D* M9 o* G

( Z4 C* X; Q( [( v
回复

使用道具 举报

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

本版积分规则

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