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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
+ y. }4 B4 N K3 _; M7 D9 {8 A. }

# I! q$ \# y5 T0 s' a# t

/ |; b) d, l1 U# N- h, t3 U$ y) \8 H

9 D* H% [+ r2 ~$ ?. f1 A 前言 8 f) @) Z% o: |% N

/ x- J& K( s' q" c

/ i/ u% b3 N: v( T# w) ?/ E4 A' ?2 k 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 $ j" Q% u- q% ^

, B( O7 N* ~2 a: l8 x' R

" k1 y6 p9 i+ y4 |7 n* T   ! n- D4 R" e: A

& x7 n' }1 E' c$ b% J

4 b: S; M3 o$ N S. _% d 漏洞分析 1 N- R# }3 Z! {: B

- {5 F8 X. e+ N' n

$ M Y& N5 D- V5 c$ x* L 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 4 a; d9 l" E' u6 V

, I1 Q! P8 Q7 ]/ T

% i8 U. W: g1 B) R6 @/ \  * U% b% h+ W) E( J# b! o$ C

3 ~: x' P0 e# I% b% `# [( `9 D

! n% l. O; {+ A0 Z3 _! t 对应着avatar.inc.php代码如下: % C V$ n+ n0 A1 J

3 J( l/ f) G9 a1 q: v- i/ F4 Z9 ~# F, P

/ m) v! r; _% e5 m; G" X <?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) { , o2 n5 a: V" e$ X

- i0 d" D) C5 P1 i, T

7 S! N% S* _& i% S) O. D9 T     case 'upload': : t/ }& S* A! B

$ z; y7 G2 h( z1 K. y5 Y" l7 d# H

. [8 l) q) r) j4 p         if(!$_FILES['file']['size']) { " ^3 d2 k: o1 t( O

3 W/ T- D4 i( [- }0 |# z- A6 t

0 ~7 z9 s) P5 n% I+ ^             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);: ]0 F U @( t' Z6 o* F

6 T: }. F+ h% r9 z) y4 `" e

! Z. U" l. ] e7 J/ R             exit('{"error":1,"message":"Error FILE"}');1 N) x! k% b( r5 Z" r c+ F U- `* w

2 F r' ?6 J- S6 A& Q ^

# C. v4 l9 f! ?( _         } 3 v6 i0 J/ ?( W) K0 m8 @, D2 f

6 |6 q0 Y" c0 N2 f* g3 @: x

5 Y# M: H# \ O# |, n' z; w( {         require DT_ROOT.'/include/upload.class.php'; ' k3 w7 r& k( `( b9 B `- i' O3 p U4 n

9 m/ A& D5 O9 B9 {

0 H4 E& H7 ~% C   # y0 K& n! f0 X1 p" h

1 V) ~ i7 @8 Z0 m' m3 j

2 n; p0 O7 T, Y& E, M/ x. [         $ext = file_ext($_FILES['file']['name']); % U7 V- ]( }* t4 p7 ]: J+ j/ u; N# W

, p$ a" `- @2 U P' g6 i* {# t0 h

- V/ q; F4 _5 C! g) L$ n9 t' E4 L         $name = 'avatar'.$_userid.'.'.$ext; " L3 E) y- v0 G- r ]+ i

# O! F. f! L" E3 N/ h# d

: M: R6 A5 a7 S3 I( r         $file = DT_ROOT.'/file/temp/'.$name; 5 Z4 V8 g* F: {* t, }* i- s+ F" I J6 w

* x( ]" b8 P3 m* G

" n/ j& o' g; P& r   ; H% k+ Q' E1 @) F3 W- {: V; g

7 {2 G8 H2 a% a$ t& N

# e: K8 Z" S) _* E         if(is_file($file)) file_del($file); : w+ V0 g; _9 @% C

0 q% I( v* `4 r4 `1 k6 b2 F

; j4 N7 _3 B0 n0 G7 g5 e' F" T         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');" I$ H8 V! |# v1 n, m- o; ]

' j/ ^4 x7 B1 K

6 O `+ ~3 x! ~8 \( Z6 |. a  / T* [8 Z: ^$ n

) T. c0 H( I. Y: Q9 C2 W: g

+ Q0 M) u \7 }9 R9 ]         $upload->adduserid = false; 6 v3 e# b7 \- Q+ b) {" {

1 B+ n- t+ ]% t4 I! g9 w

& S/ B* ~( ]: h: [ n   , u( {/ u! F1 [( M, J6 R! x

' ~! \! p: U6 L6 r6 S) Q/ V

4 ~2 A$ v6 }' Z         if($upload->save()) { % t1 L, a7 u( r$ q2 D5 A3 J4 e

: P0 S2 @* V# d- y

K; |3 H c) ] L8 k- Y             ...# [. H: A$ o* s: Q: ^

5 j, K- M% W* \8 W E- B5 B* r

* N, ~" b' y4 r3 d" U         } else {3 @( B% N: H0 o$ F, D0 m& Q: D0 U

9 j7 Q+ S0 [+ _0 u- d! n$ _3 ?4 i3 o

& E/ O/ a l3 g3 v- U+ a, }             ... 0 {+ g; k1 y X/ U9 x

: w8 f- g1 c7 W$ ~. g' y7 t, d- R

. B5 d4 a) `' ^% P) L         } % u Z- G9 q2 E' r: ~

9 z, a6 V" E' X* c& a# Q

2 C- Q3 e, q) m' }7 @     break; " F9 e" X+ o4 N8 U& t- I* H5 C

+ |2 j' n. R" N1 }8 U5 W0 R. |0 `

1 G6 L' M0 |: D& ` 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 3 V, L8 p a% q0 A: C. s9 n) n

# c! j3 M: G5 I N9 D

4 K6 O8 c( V. \3 l upload对象构造函数如下,include/upload.class.php:25:, i, ?0 j+ @) m$ A8 g( B

8 I% O) Z' }8 |( ^. k7 [

1 Y! d8 ~# G' P% p* { <?phpclass upload {4 A: f$ A4 U% C1 r& v

! n& z, V- A4 |' r8 L0 t H7 ^

3 s. F5 W9 ]% C8 E2 x8 m     function __construct($_file, $savepath, $savename = '', $fileformat = '') { . F1 ^5 ]( _2 C+ l7 J" H

. Z& [! N* @5 I2 w) {5 ^

, `; l0 J5 c5 T- A8 o% |4 H0 k         global $DT, $_userid; ' V$ g8 s5 Q0 M: O) l B' J8 Y# H

& h" I1 a: L: Y( j' m4 u+ o

3 g; {' N8 R* K; V         foreach($_file as $file) { + E, m: X( f0 K y) h# T

/ p, o' G- Y. T- W9 l

: C" m3 X- @* b. V% s             $this->file = $file['tmp_name'];6 O5 y$ C- K7 J" p. T2 I

0 t; H9 ~6 z# i3 G+ K

) O% N! |( Q3 E1 e) u( I2 {9 I             $this->file_name = $file['name']; : B, e* S; b+ c/ T# P0 ]% ?/ _; S

. Z: A/ H2 d, r- f3 ? R! H9 z

$ y. n/ W( Z. |. y" I1 t             $this->file_size = $file['size']; 6 P; Z2 r9 x8 J

- R5 r; y; H- k: P2 _9 I& u

8 {, K$ {- O3 p& h             $this->file_type = $file['type']; 9 b" b0 o' v- I3 S0 B# n. [+ B, D

3 ]( b( w6 {& w9 F; M

8 P5 L0 Z/ h& d% X1 ~9 ` _             $this->file_error = $file['error'];' A' f7 F: q7 l4 B: {8 s Y

& c2 m! s1 M0 v1 Y# {; K) F0 E* A

( x, B( L7 a0 N( N/ L& u3 H  : i/ @$ h5 C* H: P

- l/ x- n- R; x2 N+ r8 K$ c( R" T

' L7 ^7 f A" \4 W" i         }! u2 L+ r6 d& }6 m; H( D f7 g J

. l0 j# C. ~' c( J

- j9 g2 w8 Z7 m! p9 J- k5 E& A         $this->userid = $_userid;( A( Q! e5 x5 D

B2 x+ _/ K+ M6 \

+ ]5 Y0 P" T x" D         $this->ext = file_ext($this->file_name);7 b$ ~# o5 g- Q3 ~: b9 z

4 _- w+ J+ r# _1 l3 b6 ?1 p, @1 t

: B' v% P; ~& i4 r) r! J         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];. k) @8 p8 ^: k/ t. e( |( B

9 L: L/ p9 C! D4 C3 k1 m

4 r# e# d6 {6 `7 h6 U         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;; Y+ v( l9 m# z) V

# { U8 R+ u2 a* S9 [

2 N% A: ]4 i- F s         $this->savepath = $savepath; 5 g( e6 f3 `7 a1 U$ c% F

1 a0 y g, X$ ?: z- e u

6 Q6 ^7 c9 K% H! S         $this->savename = $savename;8 \, Z+ g: u6 g/ ?7 ]

; z q, n Y! N- Y

; Y4 S6 A" T* R" u8 q     }} , z1 ^+ x. _; A" i- O- l: R' e

5 p% r% Z( T+ P" A! E; p1 d

2 j% @6 }% D& x/ H. b 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。! P" H* i: ^- h% L6 W

" J5 O& ~4 X7 X& l

. |$ ^1 d/ N: ^ 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * P' n! f- c3 l& I; z& [

5 ?1 l3 i( T1 g# \

) R; z/ |! H% 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& T$ ]- u/ s& Y, [

/ U ?5 S- } x0 z2 F! N% O" O

9 T( j. P& N$ F! F 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 4 T, p4 G! a! ]. k, o. I0 b

+ y4 u+ s$ z+ B% Y

( e1 i, v) J; P8 j  * E8 N J# _: k* x

- Y9 E8 Z4 T: t

9 d# A: C1 H# F+ i 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:% ]8 `# y- m9 E- e1 l

& c" }; Q! }' u' r

$ \% m C- b% s! H <?phpclass upload {# l% F% `+ s' A1 s+ A6 ~* E

, V$ ]* Z1 G/ g, F; d/ C, \

+ p- ?" B7 t8 L4 Q" g/ f5 Q1 r' }     function save() {4 A% n8 R- N0 j. [% ]/ k

: _8 D! T1 Q2 B/ A# @% q" q' T2 s$ `; l

* `; k+ S: [1 d% C5 y( x9 b         include load('include.lang'); 0 a+ F2 K/ G1 V! D

! q- m7 `# A* k& i

$ `, W7 {" g. r' \; S$ g* s         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');6 f5 S- ^, F4 L& o% Z

5 _- E/ m% u2 M4 x$ u4 A& f

: s& e( H3 Q% D! H0 e   3 w2 A8 i9 `/ c" O: ]

; P2 F! f" t5 `6 ^& L

9 j; Q5 p; }( ~# s         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');# C- t, ?2 g9 d3 M$ I: o

: z' y* T0 Y& K, I* K

$ x9 W3 n# ]2 H: y: v# ]  2 h7 ]# U$ _3 [8 E0 i4 }6 ]; ~

1 o, A, R7 g) Q: J) ~7 p5 {, d

' V8 W9 J; K. n2 d7 \; p         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 4 i0 h5 u# W8 [" @2 f6 J' [

: d) ]) R! D* v3 n, b

, D. |( c! E; Y+ [* F# ~9 v9 m: Y# i   + _5 ?: p* Z5 c: q8 {

' z, p+ R) Q- z' o2 B

: ^6 p& w2 M* L# M. W- `- n0 ~% X' z         $this->set_savepath($this->savepath);7 A8 _. ~$ ]5 @

' m1 R5 _1 k$ A* u p8 v

" G. y* x( w o; t3 i% y         $this->set_savename($this->savename); ' Q& o- W+ V% }# c$ w2 v |

: F, W0 \+ t, w0 j( ~ X) V2 M( a2 g

2 Q; B" c' F; @' v& j: G6 u  . K% e9 r! a& c1 L5 ^% {& B

5 }# {0 q, S* ~* T2 i7 U z4 Q, Q

" A2 \* T4 H- g         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);+ G4 J2 v. q" C) Q

5 t9 U9 j' f5 p4 U5 i/ _

3 c) U& ? b$ T' s5 Z0 z- b+ Y         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) g+ C- o" O7 e) q* d

^/ E7 h) l! h" P. t7 t: J

6 f5 g0 [- M1 O) f- [& F& t         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);3 C w1 V# [2 y, I. S- \6 M

$ `6 @: z3 k) y; ?% [( @. `

0 O8 t6 X* m; ^' s   ) p4 C R! A: r

2 s7 g1 r3 b2 Z' r- Y, G7 Y! w' V2 [

6 U& `, w' D& e/ o0 r8 J+ q         $this->image = $this->is_image(); ! E* A6 c& S T% O- i3 c1 X; Q

7 }8 G% t$ C4 m6 u

1 M4 y" O1 J1 `# b         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);- k* b1 F0 f: a" f# ^

# Z" `; n" |) j

5 q: O: N5 j/ {' R( F* C         return true; & G/ v: ?' @2 ?2 n9 _3 k

6 }/ }: y$ D( R; }4 h6 o

$ @. k2 u8 y( v, e: H! g     }} + z0 x, {( ?+ X- t, Z0 d4 c: s) }

0 }# m6 H" L" W8 ?; G% D

% c8 u2 D w% G+ R 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:6 ]1 y! [. q2 N9 h% \

0 G" {: m6 @' ^2 V( s

1 q U7 |0 W+ } <?php 5 @1 x: p8 ~3 y

: v3 F! i! d3 T4 G9 a

% T$ a& [2 H/ U- W1 O     function is_allow() { , u7 L3 P6 v+ d5 ]& q2 i, }1 q0 k

6 i. L+ H- l8 R; @

9 Q5 ^; |" D7 V4 c" r/ R         if(!$this->fileformat) return false; C5 m) F a- B

+ @/ _# u" k! o, ~1 C; {

# J' r' R7 \! K% S7 _         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;3 f* g. Q6 z& A

, V$ y' K" D1 r! |# j. z B

) H$ T6 L% K1 X" U' `( C         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;/ S9 C: [. x( W; C) T9 K# e |

9 Z& U( K( V% a r

# J" `- K# X* B1 R         return true; b3 \: b. X5 K. \& `% _

+ ~! G. G4 G; ~4 H8 z3 h7 u, l

9 M" E0 j! L8 X" M' f     }( F! G H3 K9 W- v2 L; J

* S2 T3 S4 Y* g1 j! ]8 Z; {; D1 B

' K9 a4 Y/ h3 Q4 w( `- C) k 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 ) ~2 C2 V+ _/ |8 H4 M! i# K/ `

9 G( Z& v/ p1 J2 C2 z8 q' a

; |9 ~- i8 ?/ x; }. Z( j 接着会进行真正的保存。通过$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文件。$ w$ C( Q0 B6 Q5 q% S6 R

7 y8 A0 a1 t( i. D1 u3 Y n0 i& z

, D5 l- ^8 u' N2 o. f 漏洞利用 / v) \9 C8 n. h) ?

( A" W" [$ o2 S- Y! K* m+ ]8 J+ l

3 u! k. c- j' T0 S! b) j 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 ) E. i7 Y/ U* T+ Q4 |7 E

$ G S t9 q l7 ?+ J1 \4 q( i6 |

9 e- E: _3 h' `. U   6 Y- n, q- h& I6 a

3 \, U0 M7 |0 w" f" j

% U; C! B* I+ O" ?9 {5 Y9 o 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid1 Z; g5 y5 i% E1 P1 ~2 P) b' a; |

4 f/ R' g" ]6 N- k: s+ V% o+ d4 x

9 E# `6 V% E- A8 m* c4 N$ W 不过实际利用上会有一定的限制。 * G9 Q3 p* ~2 f5 e

6 }3 P U1 [; c

4 M9 t; D% ?, P4 z 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。: {) s' ]' X i. _; P

% A/ U# P0 _7 P) W! _) ^" z2 v G0 @4 n

9 h+ e# C: M! h- q7 I   / U0 T: b. \' u+ o% I B

( T Y8 k5 @. j: P" S* t# L

* |. z+ b5 H* ^7 c. O) j# i) n 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ! C' T& E4 m; |, [: _! s" i

0 ?: b" R) t2 q* L; g3 F9 y+ b

* l9 C# h( Y' 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]);省略..., a" A' A; D) N6 _. m- m# L9 n' x

$ w2 _: L+ ?" S" u" q( ^, T/ L

7 b; {# }, X3 Y! L+ b1 G& o 因此要利用成功就需要条件竞争了。8 }- m/ o5 @' m6 |$ Z2 X f

' F: m& B$ Y; Z* T! L

# o+ V. g7 W6 D {" ] 补丁分析 # ^! p8 ?" g5 X0 `" L1 J

& p/ ~8 i. d" }) a9 ?% i

- U8 N8 B4 l1 d* |. M   4 P. x( A) s5 l- h: [

2 {# U" A" m Q; o% r

3 j2 \3 E+ Z1 w0 e0 Z 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 0 t& x. f' F$ R" j" u1 P

! w" _" k% g! [4 X% D& [% d

' O5 ]! T* }- z$ N: {6 [3 f5 x function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}& `2 w& N( w b9 v+ e8 b8 }+ W

) h; \& M. X& N- R/ G- `9 i) A

2 S" J9 z; l! i$ e   2 [0 y4 _+ z$ K4 ^8 Q

& Q- H# u1 O8 N9 M5 G* k

: Y/ ^, ^, H% |, l& j 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。5 D5 {2 B: X3 u L

0 e1 x/ Q, | l1 R* ~9 Z3 X+ k E9 H

* G0 n# a, h: k" E3 @6 K; u4 } 在is_allow()中增加对$this->savename的二次检查。1 v, O: Q/ a3 ]7 K) h$ u. @; p

$ f+ P& C0 S8 K! @5 p& c

5 t: W- v6 t8 S4 v; I. |' G 最后 5 L; l7 i) x9 | N/ [; y

* o5 n) s$ n2 J6 D0 _

8 ?7 s' m1 J# m* j( ?& }8 V 嘛,祝各位大师傅中秋快乐!- d. w, F! A5 k6 @

& l: S/ }; G2 B) ^8 k( D' y$ I: H

: x- A6 g: R/ j9 \; X  $ \9 p9 _& u! R) q8 m/ V$ @

, k6 R) i. k2 _1 H
回复

使用道具 举报

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

本版积分规则

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