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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
6 a/ Q2 U$ ^0 Q( q N) s

( `% j4 H% a, |! C# _% S1 `6 \3 \2 Y

$ U# l. I) g4 a3 Y% F

2 e5 Q1 u7 p7 {3 v; C0 y7 j |. X- n8 b; @ 前言) S+ b/ `* Z8 C9 I0 U7 J/ m" |7 Y

5 P% e X& B# ^$ Y

4 I M6 d/ `* o1 z. v% |, [ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 0 E9 {1 R: P$ |+ H- E8 a

; m5 }; O0 `1 m( k% K4 @* ?7 ?

) R3 X ^0 m% \6 e  4 T* q; Y% } r. i* K4 m

5 f) D* |- x( \6 x U

& V( c+ Z3 u8 f0 Y2 a9 ^ 漏洞分析9 X7 d+ `; N7 S+ d+ X0 p

# L& ^1 C+ ^% Q# H

' c) @: `0 o& h7 K7 t+ z 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:% }$ V O8 ?- v

$ j5 t* i, C- W. K' F; y9 }

% u/ F' M+ }* S3 J0 f) U, S0 `  0 N8 {. J0 o+ Q( Q8 z& z

* |: I: L3 t" f/ x' i* ]0 G

. V B5 |& n( Q$ u$ j3 E' o* } 对应着avatar.inc.php代码如下:8 D( Y5 M/ y% D

6 E5 Y a1 n6 R! S

( `" W* n* }4 I- 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) { $ s# ~" R. {0 ^2 J

# g/ x1 t: X9 y

. v3 e% [- P" I* P+ j     case 'upload': 1 o3 m4 V& i% [& t( v) Z) ^

( n! \% a: h% A+ y

9 T P, B) X+ Q) z         if(!$_FILES['file']['size']) { / v* A0 E& ^; `0 P& F

* X! O' U6 H [3 W$ l+ a* T

2 T( b8 W: R/ @7 x* t8 N; A* ^             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 8 j7 S# h! |( e; e

' D R2 h; O% |6 G6 P' M `9 r

" W/ M6 a- ~7 E1 o, O [: g& n             exit('{"error":1,"message":"Error FILE"}'); : ~) o. y2 ]+ N. m6 t

4 h: N9 J. s( Z3 v! \: v

$ E3 `1 W: U% N" ` V         }; _# d F. i$ f6 P$ _7 G4 |' J

2 K0 x# M* ?; r1 M, L$ `( c* j, y& W

2 X9 N. }6 r3 D: u" _# L/ t" `         require DT_ROOT.'/include/upload.class.php'; # D9 v, O" E$ Y. l/ S; P {6 f# M

4 W$ w9 a0 n% Y4 k& ?" i2 v

: ~- F4 W+ W6 X7 {* _* Z/ Q1 a  6 J L& t7 s3 a

0 M. l8 c, B' a2 d

- \) J/ T0 F3 v# \1 E' q         $ext = file_ext($_FILES['file']['name']); * b+ B2 j# u$ i3 U8 ^0 H

, S9 g3 g* _! F- Z4 D) B

- U* ^% ]* E: [9 L* D4 g0 M+ _         $name = 'avatar'.$_userid.'.'.$ext; - U. p; } ~1 A6 @( M" R

1 B. J$ w [5 y; J8 X2 L

% Z5 G! L- {% V) H* i' }3 {         $file = DT_ROOT.'/file/temp/'.$name;) l1 ~0 W5 [' r2 g

) u& _0 M! e2 n# J

0 v- j6 l& b6 D/ ]3 o" F7 B' x  1 y, w2 i/ L' \3 V) n, `

; V( X* s9 B! ~; j% j) O# ?

4 e( h* H/ A% }" @/ f2 C         if(is_file($file)) file_del($file);( o' V. z+ B( K+ `+ ^3 o

, @; q7 g; S9 Y }# I3 }6 s

: P% @1 u$ [. \) Z. m7 K9 z; q         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 0 o. i9 M/ a. y) c

. m# |4 W* E" a' A% Q! ~: P! K

& F* Q0 S2 b* v: {& p; E   ! t7 a+ T$ J [' N: F g

! O- T" `" q$ E. r1 B

. x) P& f U4 ^4 K4 Z0 h/ ]8 l         $upload->adduserid = false; b" |/ N; p0 w9 O) U

9 F4 m) h! K! |# l+ T5 r0 e

+ { h0 @ w) _9 B   + D) v) F8 q/ M& o7 O

0 F/ r* b9 q" P0 Q* j

1 D! _" c3 g4 i3 g& U         if($upload->save()) {. G6 Q0 H1 I H

( @! o& y. o2 \

9 u! v$ k# r# }- y7 ]8 S3 _6 m. `0 B             ... - [+ E8 \5 a, @! r

2 T" t0 M: V3 d- W& b# p

# W# d2 a8 G, D6 M" ^5 L3 x         } else { 5 W" S: X. j8 L8 p" `" n+ [; C2 V

- s" v& K( z+ g- x+ C# ?

; r( y7 Z4 j0 }4 p) V! x             ...+ j* L" a2 p3 w' C" h" C- T, Q$ R

& S$ l( M) E) O2 I) p% N! P& z$ w

6 s3 e7 ^* q" P         }0 C7 L9 Y M2 L9 H% Q6 f

5 }' }: i$ H( q5 T j3 X6 u, ?+ s

) T, H d5 ?* h* v6 z* H" }" y     break;3 e/ U" I+ Y r/ {( o5 U1 H* b

2 |' z/ G" f0 d0 A' c7 w6 B2 n

; I6 @6 e) e0 V9 B6 h; ^. B 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。9 |- v) f9 P) n8 f$ R6 z r9 o

/ s$ r) H5 d" A" t7 l& L* C

8 k7 Z% y' ?2 i% q( O$ y upload对象构造函数如下,include/upload.class.php:25:- ^9 k P; M( R

4 L/ S6 a* i# L" n. E" c6 G$ S

4 L! J) [% O; H! P6 e2 Y <?phpclass upload {2 V C6 J( o4 u: ]

# u) x: B% [. H! K) f; @4 j8 O

1 I+ _7 G- ], ]' `     function __construct($_file, $savepath, $savename = '', $fileformat = '') {2 [/ {, N/ m* ^( W; ~1 r' D1 ]8 I Y

, f& t) J8 y2 q/ Q: Q7 x

* \4 w1 n3 B8 k% Y* w         global $DT, $_userid; % H4 n0 F8 ?7 D& [

4 K/ t- P. J( `" s: e- \6 O: t

9 x7 J. M( v, W         foreach($_file as $file) { 4 b$ M* z0 u3 N0 l3 c) T6 Z L2 O# D

% ?7 \( e) V8 L. E. ]) G

9 t/ N! {2 H& N% e' Q9 f             $this->file = $file['tmp_name']; ( c- D J$ v3 {6 Y0 L

2 w' z6 h5 @! B

# T" V, g" | \' t6 u$ N8 r             $this->file_name = $file['name']; 3 z8 b& c3 k- a( K: l4 }! |

* |6 N8 }! e( O: {+ B

& C5 @2 M+ e: D. ?4 P             $this->file_size = $file['size']; 1 Y7 b; s' F g+ T7 e3 u }: t

0 }; J* a2 d! P2 e% \! q6 _" v: S

" ]* r1 [; p2 N# Y# `" `+ a$ n             $this->file_type = $file['type'];9 C( V8 [% n" [9 U) l

6 {% M) s" `0 t

$ l( p' ^0 p k& K& }. r- j             $this->file_error = $file['error']; 4 c) q6 L! d: B* ]

0 s/ S$ y& c; Q# G% z

$ e8 J! L8 `: d5 e; C   / {2 u& ^4 n6 L% f8 U! b

/ Q* f( G; |# ?/ U; F

4 h# r. A$ D6 i5 a         }2 y7 N' b/ y- o& n$ X: g

- ^$ `* ~1 H+ D/ G

$ V+ r$ ~8 w9 k; M. P9 s) m         $this->userid = $_userid;2 r6 d+ Y @' V1 u

' } c4 e' y# e

* `; r6 G. H3 R         $this->ext = file_ext($this->file_name); 9 z! T* E" ~6 j2 d, W9 C

6 V( W& v8 K1 u, p

+ r2 K k, y& N w/ j         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];6 l" K2 R3 H; ^! j1 J

! ~8 c9 z1 i: B/ b% s- w

8 a9 \! J7 U, z7 D) R# w6 ^; m         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 5 z/ W* N( a! O; q0 y8 N' {

* `# S) m8 H6 g. s( q+ P4 X7 i7 x" [

1 j& ?+ i, D/ s) ]- S1 b         $this->savepath = $savepath; 6 R. a# U2 K6 d6 u" w C& t: [

5 N N9 _* z: \, I$ Q

; t3 M; F, N- Q+ k         $this->savename = $savename;- o: l3 k: d! _% \( F

1 O. W5 X5 D" W

! a# H. f' q) ]3 [: y2 e% _     }} / T6 X& `' L( T9 ~, \. k6 ?

6 w" P1 H# Y4 V7 v! b7 p. |

2 n: ~- _4 j$ D1 k# q1 C; R4 | 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。+ z9 \8 |2 I5 z0 J" L

" ?4 ^1 e: m! a# H

2 @ m) D5 g' S2 a% W5 n' ~( _$ \' S 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 8 r+ K/ d& q# U7 q

! A5 y8 u$ s; V w

9 N' ]% I+ {( 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 1 f( L+ L- G, Q8 T" ]

% K# U! D5 \ y3 k) j ?* B' S0 Q

6 W; v* p8 E6 [- I2 z, K 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:! n6 G( K: W) H

5 t% l; C/ V7 k9 p: G t8 W/ a" Y) n; H; Q

7 f4 B6 z, k( ^2 X9 J* \* z0 N4 K  ( c8 A- t6 `0 I2 U* h& W

3 G& a$ z4 W2 e2 _8 w. g! A

7 o/ i& T3 R1 M 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:8 [7 c2 f! J# U% `7 [9 g5 ~, f

: ^ H/ W' o( U2 l

" {+ r" C B( a" p <?phpclass upload {4 Y- m+ }2 T1 O' z- D! U

0 v+ \8 X% Q) y

8 G) z. j7 ^- a- G     function save() {$ Y% \; b# b5 _% g3 O

: Z( m0 F+ Y% S

~3 m4 ^6 P6 M8 N* ]# V- t9 o         include load('include.lang');6 t1 ?0 |3 j. T+ {2 J: p

. U+ U& |5 [' b/ u" ?" H2 S

0 e& C, y3 E& ]3 m6 W: [         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); * b! w: i2 P. y& L

5 e1 X- @6 h1 q( }# g

5 s/ n* ]3 M5 d! \) c   [$ H2 X# S' V5 U. F) f

/ k; |5 J- x' ?$ f7 O# C& w/ r

6 G6 S6 V' @% c7 `# P, M; A$ c6 x         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');7 c: I5 T/ R' n; [ F

0 ~$ L* m% ^3 f( m" ^

0 u: H4 f( R5 `" A  7 s$ i q# _8 H9 z2 T1 v6 l

% R' Y5 R8 q& |7 k

3 `/ X, ~- V) b* h* t4 M3 U         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);+ V2 w. l- n% @ v/ S' U2 r

5 v, ^- X+ S& b6 m. r/ u6 [. {

/ u! k. m) W. F2 m* Y# s; A( w: A0 t  ( D* V6 ~% P# g) c0 D. G

; X2 x/ O' J- E, r2 J$ s$ |7 b5 _4 R

$ M) I0 ]: r- {8 D! a: K4 T" Z         $this->set_savepath($this->savepath); ]9 Y0 A. m, I) w# k/ a

6 |( e; s7 y5 P

0 u9 B8 w* z& L         $this->set_savename($this->savename);* b9 f/ ~7 N, C0 k

( y& T$ w- r9 O D G1 S5 L

1 } g# Y0 v& T4 t4 R  4 [9 h7 V$ k+ {% C4 V

) r$ w& b5 l: d9 c

% F7 q% F* C) b! `. i# Y( B0 F         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); ! Y" P) C; t6 B( w* y2 P/ a

0 g% L0 }9 g9 u8 Q+ H

3 R/ k! t) U: m         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); : w; J$ B- J0 b/ v6 H, F7 y* }

8 n7 o' m7 n {* L

8 a# E% Z# u0 [* ^" X2 H         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);; U! @, {2 U2 |) e7 L

/ H. W' w: m* x. S; {7 D

0 _1 G9 n! @' T9 N, [: L5 k  8 i9 p0 k* C, Y! \4 Q% `$ n

) a; N2 N* k5 X$ J2 y

9 @# D% j3 S9 |3 k$ n& v5 ^         $this->image = $this->is_image(); ) o" Y9 v) D0 ^) R+ u( ]

( n5 | i q' V7 W; K& v6 d

( J# s" V7 Y, x7 X/ ^2 l         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 0 ~4 H' C4 ~2 R" r2 h; u- L

2 B8 D/ j# v. ^4 H$ N6 k

9 X/ g0 s0 G4 n) {         return true; ) O% l; \- o. f

7 h, {2 ?/ h R7 J, A! I: Z1 J. j

0 a' W& d! F+ t6 B" H     }}2 U3 F. K6 `* w1 J T

1 {- E8 O; ^3 ]5 Z

/ h3 t! z8 ~/ S8 F 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: % I* [( F' S" B5 k7 g9 C

4 G7 }) L* w1 @* d+ S

: A+ ^ i* o* k/ C# H% z4 x <?php 3 w7 x ]6 g- \9 Z0 S( u# d4 t

9 Z/ _1 r4 c# k0 `

: X! b" V' {- x: s7 c3 w4 g     function is_allow() {. n' X& ` G: U1 R y

1 t' G1 a% _$ }0 I, w% g$ @

2 u" A1 k. ^, s, R, I         if(!$this->fileformat) return false;& q! h/ M1 }. X% i/ j

% P, {# x: t+ o% p% U/ g7 M

K4 u/ p. E9 u& \- r5 ]7 G0 D         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. B" S" j# G0 p& T; \9 u7 I

' e, u- h3 h; s, T o+ [. N' J

: `* g$ u/ @# e) ~& ^2 w# `1 r) L5 b         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; % u3 f A. P$ t

- h3 S, v* I& M X

' o1 @) z+ }; x         return true; 8 ^/ n1 E/ v& i, e( O

5 A. L% ^. V' M, P: p* E/ W4 b

7 W: \$ u, B$ I, c, V, ^, Q# q     } 8 S- x7 k' m$ h' j+ z% \

: E' A1 n# h$ @* Z6 D

0 b* I7 O' m" s: t" j2 l 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。; w& Y& S5 g% Y# E+ R! C

7 q* w( S. h* X/ ~0 z" i: _7 R

1 Q) c% d/ `% Z1 v9 g& P 接着会进行真正的保存。通过$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文件。5 v% g! A! e8 S* c6 S' d

; a" |& F4 `2 x% Z

1 y) e# O6 v" @3 R: d8 I 漏洞利用1 h1 |) _1 I! C$ g

- ?# \% @/ x2 Q

; s& O+ f8 l( A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 + V& j' d4 Z+ G; |

* k0 w% u; V) U Q1 n6 H/ L7 _6 V

6 C0 n! O# Q# p2 G* X   : @* y! s, h5 O

0 J/ ~1 }1 x, }$ Z8 R/ {+ F2 Y; p

2 N5 r2 @% E" C( j0 }( k) f 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid ( c9 c: E+ I) t

: E3 |% \3 G9 j3 j, p I5 Z7 x! e

1 e! X. L- C9 X ] 不过实际利用上会有一定的限制。9 c9 I. v( a8 z8 _+ j

, U0 R- F6 W0 E! L3 E

. `- }$ K5 m3 @' J. I 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 5 K: `% Y8 G& `, H- G* C

' Y! m! O$ q0 b3 q4 k8 c+ A

2 U; |. ?$ T) F1 U4 ^  6 k( ~" z: Z6 }4 W2 Q9 N- A

% p7 ?9 Y( I a x3 y; `

7 K" P7 E6 c8 F- U! A5 R 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 0 ^; D$ z( Q) I7 N' A0 j3 W/ t; M/ M0 d3 I

7 j$ B$ a7 M* ]$ D7 @. f8 T8 a

- B6 M+ i3 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]);省略...) ~# K0 L0 }$ j+ u' Q

: {) W2 R. C' {. p5 U

5 e* q2 O/ o' v4 {; X6 D 因此要利用成功就需要条件竞争了。9 C/ v, c' D7 R( C: u# k" ]

- h& {3 s4 T7 t3 }5 U* v1 H% T

0 z5 g8 @* C% @2 {2 G 补丁分析2 J5 e5 `% J) R3 ?; j

$ f: b$ m/ l1 |9 |

% N- e r5 b4 J% b( s% F  : p7 T1 o0 k- t! v6 a

$ u: w8 U- Q, x

( y! g+ z- q( n3 p. |( [1 } 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:# x/ U2 f: ]) ^

" z! x( \$ e5 G" o8 ?& N1 L

& N) w+ S X7 |/ s' \& V c function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} % O; g+ Q2 C& b2 }

$ E& E+ x( [6 a! g1 b

* I2 Q* R. J! B2 h5 T4 g  : `; \, H2 @2 t9 C R; B2 _

, \7 M' n9 [& U* e- Q3 }+ C

4 w8 n2 {3 }* G0 { 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 + x' v/ f' r7 b1 H9 ?. D- l5 z

+ n/ p9 |3 g( r4 K+ y# r: v9 i

( @, h3 p4 n$ i! F* A# C 在is_allow()中增加对$this->savename的二次检查。" b! j# u7 A4 ^5 G6 a1 q, {+ L

/ Q/ Y& U& E) J7 m4 b. z

l; X, f" O7 T5 h) s: B' }9 Z$ e 最后# w$ c( f3 W. R3 E

. ]+ D# I2 {8 V) ]& g8 Q

3 \( l c2 m' P. T8 U( U 嘛,祝各位大师傅中秋快乐! f* [( {3 o8 k$ ^% E* a" j/ T) f

- G, |' j% Y( h# W

: q' V& e) f) H8 C& t! h   " A1 S3 _ B( w) I0 k

. P' h2 v9 X* |6 i0 a$ g6 K
回复

使用道具 举报

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

本版积分规则

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