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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
' p; Q& a' U$ R% J% i5 ]

2 W; f! S. ]1 n2 b

7 l" S: a M( H6 c3 U. A

: V: k2 s2 b, d; p 前言 * p& W& T' P0 g8 ?4 F

8 |! `+ t- G/ Y o3 k

3 Q; {) l8 P& t1 l) o 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。' l8 W/ b3 Z$ ]# K3 e

8 ~) ~& F3 q! R

1 b/ r2 C" b" T) T* V( u  / ]5 r0 h3 N2 e" A. p: W

0 v. F$ k, p1 `

4 h" o3 E6 S1 ~3 s# I9 Q+ V 漏洞分析 ! a' `6 ~( S" `

+ w( R$ W9 U3 f7 n, n

^' m" m/ c) B- L% e: P 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: ) a* k# A. d, M m1 @. Y5 }

2 V! D0 ]% o0 M. l) _

* w7 Q; @$ Q7 {7 W   + h1 T" E0 ^3 f# f$ h+ `

2 g* u1 U3 ~6 i- \

& R0 M" D, j/ m3 K* h 对应着avatar.inc.php代码如下:8 q, u" d0 |- \1 x

8 z0 s- A0 d' Y

9 q6 `, b4 v4 {( t$ 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) {% j( p2 e3 l! Y C1 ^9 A4 ]0 ] A

% }3 q0 q) z L8 H i: Q

3 q# [/ K& W1 e) S) }     case 'upload':% K' K) T# {9 g8 B

' {1 ?5 k' U/ o8 D: V. V& Y5 ?

+ g7 b1 L7 |) N5 V* f. l' Q8 `         if(!$_FILES['file']['size']) { 5 I3 Q8 V; T. ~/ `$ K9 v0 ?0 |

) c, X, a" q" r; y2 d* K, i6 T2 `

( t) e4 W& \6 l& q, d             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);+ r! C3 ~' h! r, f' ~# j

& X9 V5 E" R0 b; H

1 ?; y+ |4 W4 P5 }# B1 H- ~# H1 S G& i             exit('{"error":1,"message":"Error FILE"}');. w. B4 f0 ]" _

h% {5 Z$ k/ i I

, s+ E1 }) T1 C0 @/ e/ I z" U2 r9 z0 G         }, B; ^: Y& g, a1 p% C* A

3 I( `' m+ p/ ?) e1 A2 y+ e

0 d( R8 L" E0 K% A* c3 V6 S, R         require DT_ROOT.'/include/upload.class.php'; ' W, X" R; t( |' {* l

& m0 i; L0 x K: k# g' A

6 D! E9 f# b- V  . f- [% ?$ c8 F& h, Z; y. f

8 O6 G% ?6 Y0 Y4 I

* r+ h' H/ N {& c N         $ext = file_ext($_FILES['file']['name']); ' F |" R, P! T9 h4 `, y7 @

5 x3 r/ r( r7 P: X) S

$ W% f& X" ^* e; o }) k; B5 v         $name = 'avatar'.$_userid.'.'.$ext;/ ]1 r5 q: l4 u% I# g# h

. t" M' [7 r: h. w. k: ]+ v) c

) V3 e# r, g. I6 i) U. J         $file = DT_ROOT.'/file/temp/'.$name;# z ^, q& s. N- z% p. A

$ n B$ a" ]; I8 a1 c

^, J" r4 M! l5 ?- y G   : l0 u/ Y) R9 d0 v H+ ~: x

s! Y7 z5 R' Z& P* x

9 @8 q2 R1 L9 u" b+ u/ e/ \         if(is_file($file)) file_del($file);1 ?# C0 O0 j* H' ~

/ a# H% Y7 B; W

& W; K3 v& R# c4 d         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');$ s' D" {- r5 a; u7 ^+ T' P

1 C& q, z. }0 ?. n' h

8 M8 n7 d m: B7 v; ^   , Q, I& I2 O- ?! N+ X7 [% C8 i1 i, t

1 \ i6 G F2 H/ l9 G- y

' a4 o( C$ h* j& Q2 s ]7 E P( A         $upload->adduserid = false;7 B: L+ O1 ]% W

$ ~/ F0 R, v" U B9 ~

6 U- S, E( V; Q  2 Z8 Z9 N A) J: W r/ E

' C% L `9 n0 U

2 L2 v1 m7 H& G/ Q6 L3 ?         if($upload->save()) { 4 y2 C$ @0 O( ^$ A+ J/ D. z, V

1 F6 T) {! M. I5 W. W

* W% M( N6 M4 ^! _             ... ; x | |- v) } b6 _3 z

/ o f. {3 R7 v5 |+ c% v: Q0 s

: p4 t2 `( r8 G6 A         } else {9 F5 \* r. ~1 z3 k, q& \, k

) ]' J- b& S+ w0 x+ h' d

( R" }2 K; w9 p* z- V$ K7 m             ... # ]1 n) ?+ C. W4 e3 \

: m3 O8 a8 q9 U4 U, ^

) J y9 ]% p7 P+ |         } - ^$ t2 V) l6 t7 d4 u5 n% @

; t: @# y$ V2 _! n4 k1 n4 F. Q

/ |- l! x7 k. m3 o' C4 J     break;) v/ K" H. B/ ^* D( ]

5 N' A Q. h6 u

( |3 N; I% a' k5 i& _* t+ H3 L% Z" _ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ! t2 X8 G3 c. V6 M) J& ]. L' X

5 ?' Z; q6 S: ~* |

# F/ I7 Z5 c; Q4 l7 K% F upload对象构造函数如下,include/upload.class.php:25:$ n5 B& }0 H5 _

5 S5 X1 |0 b# A$ k; K

% n! d, H* o2 H3 [2 `$ k. @+ S <?phpclass upload {6 Q2 A T: D( E% I0 q- A" c/ e

1 A4 ^8 l+ s% I8 J

2 \; K. x* O+ T& ]     function __construct($_file, $savepath, $savename = '', $fileformat = '') {) o& Z% R7 f! A1 T$ [4 \' F$ b7 J

, m+ ^# W3 N- T/ B3 x

y# R1 N# |0 G; V         global $DT, $_userid; + V( d& h3 |4 I7 W& g. P5 ~4 F

) S3 P1 L4 i2 @- m, `$ s

% X4 A+ z9 s2 K; @- ^+ O         foreach($_file as $file) { ^0 S3 \/ E: ?- E8 q5 K/ s

, h2 |7 s8 N- d$ C5 R) k# F) [5 J

' r% R) P3 Q% F1 Y( f) }9 K             $this->file = $file['tmp_name'];5 s$ Q3 n, ~7 t- I

+ U2 [: O9 ~4 G2 [: r% {1 Q/ x

9 V& I5 y- K: Y6 W/ f4 q% ]; i             $this->file_name = $file['name'];: ^7 W4 p; A+ a; Q

: [9 g8 q0 C' q; u E

7 t6 A, |/ N; V, v             $this->file_size = $file['size'];+ H& }# d, M" e. g( B% Z

2 b0 N) q+ s! w- n( x& g

( ^6 Q* d; \' o             $this->file_type = $file['type']; ' s+ d5 Y n, n

/ M. X$ _( n" j# h5 I* Z

6 @0 o0 o+ _0 \" M             $this->file_error = $file['error'];; O9 B, |9 p$ e) ~* \9 U

/ `4 W/ @. y& K! A g! D

; @' y; ? p9 D, }0 w, Y   3 S6 ~" L3 n2 g N

+ ~) x. p7 `, |) l" a+ c

0 u# T4 S1 S/ t; S4 d' _% D         } * I, t- |4 x. I; E: s& U

* U9 k/ j# Z" O0 f. S

5 y; z6 Q0 B3 l" a. E3 n         $this->userid = $_userid;' k+ q8 X( b$ \8 n) W

8 ~2 X+ O- X7 ~6 J0 s" o) n3 x! G

' t. d. Y v9 }3 f. W         $this->ext = file_ext($this->file_name);7 N% p5 i. v: o G: k3 I, U" Z9 C! ]

) I7 _/ [1 J1 p

$ U! j; J. \% H$ W0 S         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 5 x& v- n# R' `! T+ c8 I1 ]. I

, ?) x! l- J5 o

" u) R# Y6 h+ H# }8 A         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;% j. e+ Q) R9 |. _. y( v

* O& [. @1 A& ?2 |/ y3 k+ C

9 o) ~3 l( V- D/ ~+ o- r! e         $this->savepath = $savepath; 5 a) ^4 h/ r- U" ~

4 f2 o/ B2 c% ^

, [/ O7 N+ Q& m* @         $this->savename = $savename;1 @+ _' F/ R0 A

5 K( u# M: M: r% G, y! _

" s1 J$ P8 c2 {0 m8 S* |. e2 e     }}+ g+ i9 ]. i$ T, w5 F+ o

0 M8 G; @# g* r [

_. n1 C. ^+ M0 Y9 L m 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。+ G& E6 j, ]. E' G% q# K

9 @9 V+ [ I+ G5 y4 a; c' v

/ d( R' q% S3 o1 P) P/ n 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ; W$ Z: l1 g" V$ o# n% O

. b$ b1 p' ~$ v3 d5 e: h

$ x: t: G2 |+ T5 o c- B! }% k: p $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.php1 r8 X% S4 W' ]4 v* F

$ i/ e* ]* A) }+ ~6 b, c

3 Y4 V0 H: E) d2 L n; u 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 3 B7 h+ S E( a" m9 k

4 E& @2 l+ U9 S! ]0 c

% |) v+ ~% u8 m% m. v9 N. ?  * G0 K6 }4 m$ m* d' m1 n

) T& Y: Y: |1 k/ V

8 l; `' S' {3 F' } 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: 6 S/ \; }( h0 R! \& z" m5 V

; J) e6 N$ n j( r

1 {' W3 X2 [( G( a. |2 o- V <?phpclass upload {* |3 W( H( S. H0 i: x4 b# M. }! o

* V4 p( K8 y- X& i( c$ [& D

) P6 N6 r8 d- `9 N6 M; H# s( c( [     function save() { : ^! o, d4 P$ W& S0 l

9 z B' j- e( M/ ]) t7 U

& R- A2 l; ^% v* X* J" f3 d         include load('include.lang'); , _( k P. n2 v. Q# C4 Q

; v- |0 l4 ^$ A4 @4 T; o, w

: O! p2 n$ X. N( u         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); % a0 d& M& S a% n5 h4 [

+ V' G' o0 G+ z

3 J6 a; L) l+ K  4 q1 m/ N+ ^! C" ]% e

. o( X* Q4 a3 z C& G4 m

5 S0 z+ Z- v3 Z. \- W9 P8 b         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 9 B8 Z1 a9 Q4 h A+ S1 ^1 B

3 c% f' k6 p& z$ Y4 n7 v/ s( f9 A

5 g3 ^9 c8 f* v( O7 [) h( c! V   0 ?* @4 {7 {, G, C% R; d' ^" z

, G2 h) M, f( ^& a

1 i- [3 b7 r s% n+ x         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); & T: j. _3 S" a& M

, p, X3 G; N% F E2 e B

3 j5 @; q, Z. y# q0 o  6 k' e) @% X. a/ v9 D( k- f1 _

3 \$ O; x* c B4 O. o" z# {

! G- F! ] l' n1 Z& X& a) D& K         $this->set_savepath($this->savepath);0 C* T+ B+ ]: V$ v

- _ w$ [6 m8 g$ i# s

; a. s1 ?2 E& k0 q' `1 u+ [% n         $this->set_savename($this->savename);3 k9 m3 M1 S0 l

9 z! l8 {9 W% k- ]: c0 j& m: {9 I# {

9 {. `8 r5 f2 G   ; V6 {4 t. q; `% N \6 u

E5 s1 G" c) s0 ]$ c+ M* `

$ ]4 O" b+ J3 A% o# T         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); 1 |( s# U( Z" E# `& O& z

R3 N+ p- H8 z9 F: S; h( P

8 @& J* }1 b) ]. G/ l         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); 9 F; w) ?/ L! v' Y" d; H C- N

: Z! u- H* o0 n5 n* ^1 w

9 d1 ?$ Y5 [4 ~. F! C) O& H8 y3 y/ V         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);$ T* n- K3 k, B6 O) D

# U. z9 e- A6 D0 Q4 Z. f" z

" I( A7 N- J" U" L   * P# }7 g- ^* d" B9 i" F

+ B* @9 h' T. L8 O8 Y% }( ^

. v" i9 ^# V' p) Z8 A* p }         $this->image = $this->is_image();0 m& h% r* v8 X

0 g, q3 G- x6 o9 F% N

! y* o1 B7 [+ ~1 G$ ?         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);$ i7 T0 u4 F/ Y% o3 m% _

# f: }! q# e7 e

8 U$ d1 K4 @0 m" o# I; j5 p0 l         return true; e4 R* ^+ |) t' [

. c. F$ ]) a0 p- S

p% l# |- U+ b ?+ V     }}" V2 F+ F1 e0 N* o* t

. p3 m. r; U8 a$ P% s

; _7 K. X" z- ?/ D' X 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: : n# e( @: U) d" A- J/ M/ E

9 S6 Q6 h+ |% O7 ~8 ~2 p

4 M/ L8 X. Q4 k4 u <?php d9 {& ~' O/ p( ?

5 Y5 d( r6 |5 f9 v3 k9 h3 k

4 p, @9 ~; ]# Q5 R8 m1 t     function is_allow() {! f, [5 q% x" n

4 b) w" ~! Y$ D: {! W" q! `" h

3 L: X9 m# w+ ~# Y6 b) I         if(!$this->fileformat) return false; 0 ?7 }6 q% V3 H7 M

8 ]! U ]: V2 e& E0 j

. t! H8 u, G: X! D         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; % }) s' p9 e: b- x$ k4 b7 R

$ {. s) n- H8 f0 j% M. a

# ~9 z- {; Q* ^1 a( a7 B, H9 S         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 |8 L0 K0 f1 K: @7 D

1 |; O- j1 e) G; K+ b3 i

3 q. _4 F7 \* \         return true; " J" q$ l6 Q! n" k( ?

3 L% [3 |6 A' s7 e1 N% h

6 H6 \& t; z! u! T# q     }8 ^- \% ?% Y$ B1 r

3 z. y u) S8 f

+ e4 N# F, X7 R/ R, O 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 5 e/ t' Y& E5 G. i6 y

$ n, @, o! ?$ S! X9 I4 D( X7 R

+ F) ^2 S6 |7 p) Y: N3 V. j: y 接着会进行真正的保存。通过$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文件。 * u8 m4 w8 U" F

# l$ F/ T4 a0 C# c

6 Q: e" t4 c7 Q 漏洞利用 7 Y2 e0 g7 T0 F" T

- G+ A" y/ t2 j5 H; S4 U

U- L8 G" ~8 M 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。8 D0 F+ e& b# ^& p; t

1 n! H, I2 \' l. ?- z# m

* A1 D6 m* w! W8 ]! j( _   - {6 V3 ^" R6 U) D

, z4 Q/ _4 b% r9 s w

6 i0 g' e* U$ U- O4 F& V 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid- B5 P8 O" x- \9 O( L% ^

7 e; s% H' q* D

# t N* f/ H) I# } 不过实际利用上会有一定的限制。7 L+ q6 u5 H1 Z: M

6 ?& N' d3 E3 u& _ \( o( T

: v+ L2 ^" s- n; D* Q' U, H 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。. J- r. l/ `. l

- H6 p" ~! l0 A! \1 ~7 u0 J9 ^

' D1 U0 G, t8 E5 I   9 T, X) l H. @2 m& ^0 N

5 y9 y- R7 K" a( e, |5 R- F1 e

# m- L+ V# G: Z3 L3 ?3 K& a. y 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:6 p9 R- l6 T9 S/ j7 Z

8 U9 [ F: K" K+ l/ Z- _( Z

0 D9 \8 F6 ~, y& S6 S0 V( | 省略...$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]);省略... % @, r" L# W5 T4 ^9 \+ c

: U" d# c: w( A0 q; m7 z

, c9 N/ P; g" c$ s) a 因此要利用成功就需要条件竞争了。 # K# ?6 a1 |8 G* P1 y

. |; z# H- I' \+ ^* S2 @

+ k4 O7 @, N) ^" `1 @: H( B8 W 补丁分析' n* s/ `2 w' k9 v# U5 ~

& q" M) ]$ N. Q- x7 I2 u w

( l. i1 @8 j0 U+ ?  0 u1 `% I+ \+ r" H0 E

& @2 S" Z! y. Z

* b/ q1 x4 K7 P2 I D 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:2 l. ~! R1 _' y0 w. N( k

6 F3 p1 f. W5 C( P5 \$ N2 A' u0 G" y

1 p4 \" ~/ _- N function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} . g; s; D" |' u8 t

* X$ H) {: S; `8 u& Q

A$ [7 C' ]. ~; H8 ~# Z. D9 T  . c E; J2 F) r; D- @9 V2 I1 J

. h4 m$ L% X6 r

; _' c H& S2 |( T- i7 r 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。* \8 D2 U* Y8 N& G% ^* w0 q

9 L5 E: p6 p; m

4 _/ d& c5 ^* l 在is_allow()中增加对$this->savename的二次检查。# v3 E( }8 g2 g. H1 j9 K# F& c

" O( R9 d& w+ Q

3 r: Y% [! p% z: D7 g6 I% J, O 最后6 P6 O9 l( |9 S$ @5 j+ S0 q d" N4 Y

- K \+ t- m7 r3 t2 u

& B( N& R" U# x, v' ` 嘛,祝各位大师傅中秋快乐! / @4 S2 w* t5 I& L/ [/ q) Y

. R$ t, n% B k# L

6 `3 f2 v ]+ W$ B  8 ~5 y7 j% j9 t; A

* S7 [, q. R% q& Y% T! ?/ w
回复

使用道具 举报

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

本版积分规则

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