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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
: J+ h9 ^# }3 `' [

3 `7 ~& D. S) @1 t1 |9 @6 {; W

M) |$ |& m* ?

# q8 f3 k4 R# P/ A$ \7 H+ w* l 前言 N& A" }5 z) a6 ?

0 O. s f+ K9 K! S# d

9 z8 G8 D4 c7 C" Y2 H 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。# L% h% l9 i& }7 B6 Y- {- a2 ?

) a9 ^! |. X1 n

! V& p) y. o) z0 Y3 F- n9 N5 J  ' X6 [" `: G( T' M/ V( U# d

& X5 ?+ H5 p# _4 T

- h8 T l7 Y5 o$ } 漏洞分析* G2 S$ [( [3 A2 v- v3 j. r- F

# ]2 ?; w# F! E. i: H. W

& X( }0 [6 `4 A) y) i6 \0 Y) h 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 2 C8 x* y6 u. j7 n- g, O2 `+ t

$ [) d3 V4 _* }7 Z

! w+ S* w/ c+ c0 A4 W  2 f! k* u q' \6 L4 @4 a4 p8 L

6 K# `0 t7 n3 ^! W% i3 D

% s* M) h. W; s, ~ 对应着avatar.inc.php代码如下:% {" V6 n2 b* K* K6 {$ b- g! }

2 L: M6 |7 R( ]. j- S& b

. j: y& K, [/ L! M4 b6 B" D9 S <?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) { ' |( Y3 u |: q2 y

, k& N6 F: E, Z/ B2 P

2 f# n9 x" y; d( L; t+ ^7 b. e     case 'upload': " d% O$ W( U# o

% ~" h0 Y/ W: f5 n K/ n6 P8 M

& ?, A, |# |( |0 ?         if(!$_FILES['file']['size']) { 7 x( S' P) b8 {( P H- V t

, S5 S6 ?) a% t7 e9 k& K# s& g

6 c+ l! A- V: C             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 8 X3 Z& k+ z7 u+ ?5 X+ F" k* I

' a2 n- k( |$ a5 C/ a

+ C0 Y4 a" ]2 `1 N4 o5 p             exit('{"error":1,"message":"Error FILE"}'); . \$ w1 M' @0 m1 p. a) f

, a: {- O( Q6 K1 k; D

+ K1 @: o# y0 P         }$ F6 g$ W( ]9 ?% W! M) G9 ~; ^

8 j' N6 @' I/ ^" y

2 ]. n1 y& r1 z6 H         require DT_ROOT.'/include/upload.class.php';* N2 N+ Z0 {/ w+ R; K) ^* e8 G

! x1 v4 \" K, k6 c& c; @

) I* Y- P) N G, t( t; s! \  4 ^% e* g" U; z

+ Q: i9 w2 M/ z( `; J

3 v9 O% H) r$ {3 ?4 w         $ext = file_ext($_FILES['file']['name']); ; B+ U+ Y8 F4 r. n

8 t" q& Q, m7 F& C& L1 I( v9 `, n p

! A! E' p3 k8 e0 m) d1 D$ ~, C+ T         $name = 'avatar'.$_userid.'.'.$ext; / w) {& X$ f0 l1 e- N5 q

, @; D7 ?/ v$ Y. g8 i* G2 a

- q0 i$ g9 K: R5 u/ T8 J         $file = DT_ROOT.'/file/temp/'.$name; , l& S2 d5 K$ o# s: G0 {9 Z

4 `& \) m v# H3 `# `' @/ O7 ~

; g. R1 a+ ]. w9 F) f   8 d/ r3 [% r2 j# N8 v' X

9 C% m3 B9 }/ |( n4 M

0 Y2 `& t$ {7 [' s0 c         if(is_file($file)) file_del($file); ( `& g( J1 ~+ C' [+ j

$ c/ }( U" h8 P7 |, Z) e: z4 |

+ b/ S5 ? Y4 h         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');( E* O5 C7 i' o$ X

/ h3 {# |' p6 ^9 D: o

2 }0 Q! s; `/ I1 {1 e  # N! s- b: S9 Q- o5 a4 v

- V1 \! [/ X# a/ w5 l

6 d& j# B o' D1 b         $upload->adduserid = false; 0 X4 X; y9 a6 g7 B& A

$ X3 G" s* y5 l5 r! @5 Y

- _8 A2 q& B8 M! M9 B+ ^  - W! m# q5 I" c! v/ u

8 ?( q+ y8 E( {

% i* N7 g% r+ r Z1 Q. Q4 T% z         if($upload->save()) { 8 G; f+ A0 A( b% Y. Z" }2 J) p! b

U5 Q: p# V" s* @

% P( b0 D# E3 I) `! j4 ?             ... 7 i0 w& D5 ]; {) }4 X" x

3 s* c* e$ |( K2 Q8 t8 u! q

! f; Z' m/ W4 F+ x         } else {; ^5 Q( w, _# [& p' h

) A8 [& t! Z$ J$ p7 g; J

& p2 k1 L* R; E; B: r             ...& T( X% \9 V# p }! p, \1 q# j

5 e( l0 H2 S/ J+ c% I/ d

) }: ]# T4 _* ~* j& }+ N         } 8 t6 D6 h& U' S: c7 [

3 |( M! f" i3 e1 R f6 s: ]

( `; D- O6 ]3 H" E' m0 ^     break;! H% P! K3 t' `$ c$ e

& K" f+ a2 k! d$ h8 J4 R2 B4 x

* g1 @4 Z1 k- L, q* Z; r 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。' y* _7 A. n* Q, A9 }6 e# L

" O0 r' J5 a" G. x+ C) e

# r5 W/ m' _, v" h upload对象构造函数如下,include/upload.class.php:25: 8 r- T( [, M/ W; B/ P( H

8 ? X- i3 N! \0 t) u# a8 I/ O

* a7 P: O( o% e <?phpclass upload { + `( Q+ _) U. ]/ j

7 b% n0 f, ^# e% x% e/ U5 I9 q

0 y, ?" f1 G" I d" V0 v     function __construct($_file, $savepath, $savename = '', $fileformat = '') {7 [1 y9 }( Z3 O8 t3 _- ^

) G) ^1 A/ Y$ w+ N7 q

* a& r+ O5 E/ L+ i: O5 g         global $DT, $_userid; . A4 o! q* X# R0 x

' }: ~+ M4 d% h+ d: W. L8 A

% \ Q; S' R! b& f         foreach($_file as $file) {6 k8 N, }# P& o5 F# M3 \) E" f

3 e7 g# J) N- m' w$ {2 |6 b3 r

9 R" q' ~) q1 }/ \: D             $this->file = $file['tmp_name']; 9 I" B+ ^3 X2 w/ Z: o: P

# u/ E6 D# t7 |$ F3 m

/ M. L/ S+ F" @' M7 R: J7 Y             $this->file_name = $file['name']; & V: W4 P; r! X& [4 p! g5 x) v

, C7 T, K' o6 }6 c" v* J

1 b- t: K6 L7 f, y+ w/ N, k             $this->file_size = $file['size']; 6 ]( A1 O9 B4 S3 x; o6 f

9 O \2 u. _& Y% e2 E& {

$ j4 ~2 j7 g+ A! `/ ]/ U             $this->file_type = $file['type']; " v: K. q5 t4 I

9 ^9 Y, M9 k/ k

2 W0 j2 O& u) V# d             $this->file_error = $file['error'];' K6 B- t& L+ S3 F0 H+ ?

$ c O8 i& `; Q) M7 c! r

" _7 d/ Y T9 }8 P; @  0 @& A6 e9 D1 g. w

7 C6 q! J3 n6 t0 x! K! X2 O

( E f- p4 K+ {& Q" m5 k( j+ q% n         } 6 p2 S; F z) |2 H( e

! ~+ ^4 T2 c( h) C0 C' t. D

) r* f/ I1 L/ V, e1 ?; `         $this->userid = $_userid; ) w4 \; v# B$ h/ R2 U! j) p4 ?

8 s, H6 J$ q1 z* l$ y" d5 Y- G

( Q$ H/ n! T2 q4 ~& U8 H         $this->ext = file_ext($this->file_name);5 g8 _. @6 m) G P& A* ^

7 w3 w! z7 G/ ]' h- ~8 ?( h

' ^* G+ ~8 Y5 z& T         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; : X7 `- m6 m4 Y: D! r+ `

$ \2 {( \$ ^, b% L5 z6 X7 v

) q+ O) [: O6 j2 z6 ]8 g# |         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; / N, d8 v* u2 M7 X3 q- F

( s- D \0 t2 i) m* M' |

$ C4 w. [/ D. O4 o' @9 o         $this->savepath = $savepath; 4 X8 k4 I$ w4 n6 N

- }3 b0 M' V$ E& I5 G

3 |( Z0 C1 U9 `7 H/ B         $this->savename = $savename; ; P, y0 ~2 w3 I5 o- T6 x' N

. l% F( `( S* @8 {

3 e4 \: U' @; D: Q) @     }} * D" N; b' `7 b/ {' A; ?% A! S

U( i: q: L1 ^( Q& v

6 {) g* e- T: h1 a) R( K 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。: g5 s0 N" V/ G" G" C, I

. E# A* v3 m( L, k7 e* X: o

7 F6 R* @4 d& p; U7 S 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 7 M, ^& K1 `" F, _4 a1 H

/ x e5 {) f% P; c0 n' v8 @/ D

+ e- N& r \+ [0 t3 w $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 ( F/ L2 p( {$ z* F

) M) z# m' }0 F. j

4 X' {9 H) G6 e) I* X" P4 n 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: / _5 W n, Y( h& Y, J' Z; |. [ T

1 {# f& d" n7 f- L8 P; J4 a

; X" S9 ]' \2 H4 X: A2 E   ) n8 Y0 E+ v( S# x

4 d1 B* K$ o ]

1 d8 r) _8 U8 T1 T$ x! D 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:4 X" ~" }( I: o- {

2 ~6 i+ o+ |9 g& C1 o: }3 n

+ w, F& F$ h$ e) a) x <?phpclass upload {7 v* ^' p; o# u2 G B5 s/ _

: K1 r! C+ A0 ~' P" _1 F- @9 h

! i. C! A U1 i' f% h     function save() {) y3 T0 d- E4 v2 A+ @% A

( l5 v$ j0 V+ V9 h3 H. u! g( A

0 e4 T, L) G i         include load('include.lang'); 2 J3 [, e5 c7 p+ g

/ E6 ^" ^6 X/ N) T0 q9 F

$ x8 }; Z, W4 i3 }& |0 J         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 2 d; R4 d9 d4 r( P% B

3 l( q& y% t/ s+ t+ `+ l7 A* o

! ^8 z" u, C! p" x4 _9 V  : o7 l! f/ A0 d4 d- |

4 M0 K$ d, m5 u

6 _6 U. }* J; g2 |         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');* a7 x! Z0 Z* `0 `& x+ `, v

+ e+ D! o& Q$ c

4 ?$ n* X; b5 x# y. ^9 E   " G) P/ e. r: W" \9 [

/ A" Y- R2 P9 a$ ]7 E) I8 |

( K3 }* k# f4 J; a1 w4 |         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);5 E( W6 M3 }2 K. E- l `9 t

7 g5 ^0 {/ W, o8 A9 Z& V

! z# L0 S K6 C1 U- s8 b! [4 x! E   ( l- O& }; t) s6 g5 \9 _" M

9 w7 g2 ^4 t3 b' K2 q% Z

3 P# h0 Z2 {. N- Z. ]8 z! N1 W9 j         $this->set_savepath($this->savepath); $ K( B( B9 w3 }% a3 _* R+ R

5 V4 _! R& F- |2 _2 \

0 u* T9 x/ G- V( K4 o8 e; L+ U0 l3 Y         $this->set_savename($this->savename); - U: I1 B& P1 ]

) x3 F: ]% }; ^/ x3 U/ N( _& ~1 p

+ G+ Z6 Z2 |$ X0 ~  ' J+ W" @4 y, `5 ~9 k

Z- t: Z9 V3 ? ]

; X" ^3 J* ~- T. Q. a( H/ o         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);8 a) j# q9 l0 L0 r

! p* N, {* k7 x5 i! b2 _

: d+ s7 O$ c* {; S! Y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);4 Q, Z6 K; Z% x' I# E6 N

0 u7 X# L$ P! b4 V9 W

- T: r" I0 b( `         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);9 i8 O/ S/ k. }

* T3 d& P# B* G2 h; c) w

) k! j* {! s' @/ G  : L- T, u# _# p( h; c+ m0 }

) p. K% i- R! x, m

3 C1 \1 [& q9 l, L( k1 j         $this->image = $this->is_image(); ) } Q" a7 ]. Q5 v1 h3 b4 H: q

) z( m/ c4 ~# i) G& Z

. {% O* J5 e4 K- N& `: d         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); & j4 l. l3 U: t- R+ M

4 P; c e: }; i* q( Q, V

# B* _) l# i# g+ s2 m! k% ?1 b: o         return true; 2 k9 C4 `4 K; Z7 a% N! v2 w& ?

B1 m& O+ r1 ?9 E& i9 B8 B

7 \- a0 {- A2 w+ q5 m$ Q/ `! p     }}1 D( m; f/ p' y, H- Z! y& O

' R2 I* e, V1 V, h/ n

* F' ?) F- a7 R) c! q 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 3 j" z9 ]$ a- y- h

4 r& t& i( x% R3 p* j/ R

# k) |& K! z% V( Y' D/ k7 ?" T <?php9 I9 |: S5 @7 P& e

! y( z( J- Q5 x7 g

. E- @3 ^ J$ D3 G* b     function is_allow() { ) k' }% u9 u2 Q/ G

3 H9 A/ |3 X. J* V! E' l

# _, A+ [" D8 i! F! ]         if(!$this->fileformat) return false;( g4 K6 A8 L1 P3 V1 S$ h% W

5 T- K- n0 c* A5 B) y

2 X$ L. e1 z. ?# w0 E         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;+ |6 C( v0 S$ d& X9 k, A

3 S; E/ p0 d1 c8 P9 s) A$ t

3 _7 _3 _8 |* A3 v3 l B0 y         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;3 t0 P6 B5 M# J" }% ?$ o, Q7 P& o

+ t8 x5 O+ T/ @

( `0 A1 B! T4 N: ]         return true; 1 Q; \" R' q# y7 t9 W# V& i

1 Y; B0 E% o4 y7 |0 e2 R

" O8 D* D& b/ w& M/ t     } 6 @6 H% S, C! M* m: T) d

) }! O9 ]1 p" q- J" o

; W' e2 T8 P. W: ] 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。6 S* p& W: r; U0 Z; i

) b1 m* A! O& x- _' y; X

3 ^: J& `% `- Z 接着会进行真正的保存。通过$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文件。 4 }& e$ a3 B% W B

5 _! N7 q: R, q+ f7 S8 ]2 n1 l

( J3 O4 a; e( }1 j9 j, P) G( a2 }& z. Q 漏洞利用 $ o$ K. P p6 l9 l0 ]( @

/ [4 T, `% E* W; r

5 @ x, r4 w+ X! |: f 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 " [3 N: V( D/ l- ^3 P5 S

) z$ r# |, u9 R* S" h* ^8 I

9 q; b$ n3 |% B) L. X   . U# d$ G+ ]$ a- ] }

( L# } A5 L9 a8 Z5 [2 r4 L

9 V$ n& Z' \: k" t- a 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid1 H8 Q& i1 x6 f, Z4 h' A( S

' s+ j$ V# o$ |0 w: S

# K1 I" Z% ~! L1 a" R0 }1 M 不过实际利用上会有一定的限制。$ [ i2 b( J4 R; _1 s- j

5 O! R( g R! y% H

6 I, \. l, W0 c 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 1 a) a5 [, U3 C1 O* P

! Q' b \: k6 p

! O P9 X, P$ T8 q( O   $ f! r! j3 h) F- d) L" R

0 ?! g# f& Q, G4 `( _

. y9 j6 E5 K" \% { 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:: @( E6 h7 P" e

$ [4 W8 ~$ o0 A! S0 S$ m; }

4 g' u' p0 ?+ ]! w& P0 {, G0 v- j7 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]);省略...5 C+ g* R7 I' z9 i k. o9 ~

. S" q! a Y. N2 }- C

$ U1 p- |. N. V2 y/ S4 z) W! Q6 P5 f 因此要利用成功就需要条件竞争了。2 |; u9 g t$ r+ G- C

, W! r* H# ?* C5 K6 M+ q

) I& A4 s# T/ ~3 q, q4 |7 A 补丁分析1 U" R7 p4 p& J# V4 `4 F

$ w% e; w9 @% K- k( S3 H

1 Z3 J6 G) E# M   ) E6 F* S+ s! g! `) h

4 C+ r+ }2 B+ f) \

+ T$ [; t D2 r& A b' p 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: ( y- `' z6 p2 d9 W

3 s' w! q" Y. `) t, s. Q

3 S# X& y' y* |9 |' i6 ? function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}# T$ m& |" g+ Y [4 [8 k7 u

8 w0 N9 b: F8 A- p3 y

9 F0 l6 J4 R5 W0 t& ^6 y3 f4 R   3 N( f+ [: V7 x8 Y

) ]7 { y" G/ }7 E

% M" e9 j3 Z5 L 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。4 i8 R; z6 B) {8 l1 |: i* F

$ f4 l+ N4 _- X8 C, v

2 m3 D% u# s V! E+ }# W 在is_allow()中增加对$this->savename的二次检查。2 B3 J3 l! V5 {6 O

9 ?0 x2 j( t( ~4 c

; G7 T+ ~; J E% _( l& a7 q7 } 最后( P% `4 A1 H: q5 `; x. G6 l7 V @4 {

~7 n0 q/ ]2 J9 f$ O8 d

9 d) j% ~: g% g. \# ]# \# U, X 嘛,祝各位大师傅中秋快乐! & @* ^8 x6 g, Y

: w+ x+ \% R4 ~, B% ?6 n' |. Q

7 n6 j5 u5 n* N2 g* ~- H$ a4 V  1 @& f- b4 K+ W6 Z9 F( C+ ^

+ s5 u3 V: Z% j
回复

使用道具 举报

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

本版积分规则

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