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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
, y' E3 l9 i( R. _( N

' O5 P5 [+ ], ^3 p: x

+ u5 N" A% o7 [9 e* O

; g: ^! u- f' z( ]7 j 前言 2 r- t# v& v7 A1 }

7 b9 R" v+ R% y

. q( b9 I" r \8 w+ k5 \2 W: {# A 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 ; f# S9 y+ R$ S2 P$ l n. F

, ?6 b! q* l5 s- P

8 }' J: z6 ~, X. c5 x, n8 t) [   ' @* b! S9 n" q$ |1 i) v( C# i# L

! y" q$ j3 s: q1 R) j: Y( j

+ r+ G# V, l# c; { 漏洞分析 % i6 t/ p" z+ d1 f

! W. K% E: }9 H5 f2 H

+ x8 t! Y) `. l2 X) s+ w1 c9 } 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:. ~' T. G5 b8 a8 L

) Z- r X- B- ^

4 b. y8 W: C2 P5 x5 _. B* G  ' A4 i0 s$ p) u) E: u

' @3 E8 G j. F

* B# p& f. F2 B9 R 对应着avatar.inc.php代码如下: * A5 f" M! W' s1 G; t

M) @" ~0 C2 e- I: P

( J% m. }6 {, k( {3 o: h <?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) {! @$ E( H( B8 x7 |: K" x2 r: P

2 J& g; J8 [- \

) W. O; K9 q+ s5 |5 I     case 'upload':( B' n* w3 j( B$ p5 ^6 i8 {4 [& H

5 H8 l7 l# g+ V$ E& W* ~

+ D+ [5 }$ u' J         if(!$_FILES['file']['size']) { / M6 f5 t5 i2 P$ \* n0 S) U, U- C

' [5 Z* L2 w9 y0 F

7 F; g( s6 X q5 O3 U9 s4 ^3 d" n9 v             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 1 r! m3 B: l8 i

9 l. Q1 z9 o- m1 g8 h! M6 e

% k5 T0 d# N8 a+ ^             exit('{"error":1,"message":"Error FILE"}');2 y* O" w* i) }) S- G0 w

! _* `- D& `# H' c& @8 _

8 ~1 ]5 t. x8 ~; W' H- x5 v         }' m) g( D: X9 s5 {; A! V( k8 v

; V! S1 h- a7 Q7 z6 I# {

3 b% O( ~) |7 W2 e o; c6 v# h$ q         require DT_ROOT.'/include/upload.class.php'; 3 W" U+ F/ O i. P

9 V: b* p9 N8 K/ B6 l

! P) p3 x. f% }4 X1 Y* x/ P   5 H H7 p6 [" V6 b* |6 e" ~

* U& H, U% ?. O- h" Z8 O

, |+ j5 t2 U4 j K; a. z# K& k         $ext = file_ext($_FILES['file']['name']); : y. a7 t t: ~. Q/ x: |, k& w# o

i7 N- L6 E1 J- f1 U2 n

) ]# _8 @6 C5 W6 |# Q& M         $name = 'avatar'.$_userid.'.'.$ext;% h/ S0 |* c, s3 r

; c( o, x9 _& k( k3 p

; ]3 A+ N5 p& R6 [5 o" |         $file = DT_ROOT.'/file/temp/'.$name;# s: I7 O8 |/ G p/ A) Y

/ l9 q Y* L. q/ e

6 }$ w% V+ }* w9 C  3 M4 a9 n' ?* `% _' b* W

& D; b4 g$ ^ R D* s

: B, {: U8 p1 b3 o1 L+ E& \         if(is_file($file)) file_del($file); 9 f$ ?2 S+ @4 U* b

2 J) Q M! H# I3 N; K& O

" w* P$ E- H$ P1 s         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); # v, P# j" p& b2 q2 g9 T6 N

3 ~- e1 {3 x/ |) Z* h7 D7 H

6 ]5 W+ v) ^, ^% ]  7 u; @1 \$ o2 o r. \9 V

# @& U3 a$ H j. E5 h8 ^; d, I

& X- [0 [; H' R( x5 L         $upload->adduserid = false; & p% M: N# v- F/ g" t

) ^; N |5 N0 A# t" W

K$ G w \: w0 A. t; |  ! M3 v8 x0 ~- a$ Q' [1 s/ h0 I+ i

7 _6 A! I* r8 f: k* S

2 c, ~2 N! J, P8 r! u* ?$ d# k         if($upload->save()) {2 Q& l1 r4 h1 i# y: O, N- q o7 x

/ ^; b% x% L$ x+ X/ _

( G+ ]- N7 t+ w* `- m             ...' Y8 G/ K( I" s" _( i

* H$ U9 p$ r% d" Y' W4 g9 m$ x

+ `3 d" T$ M: {) n1 O& H. x         } else { ! {6 i8 z' Y/ a" ]6 t! x0 Q

! G1 ~; e' D5 W$ G2 X# E& T

4 K' D2 Z+ l6 }8 V# d7 ^2 }: d& J             ..." h7 ~8 @5 }* U- J4 i1 U" [

: M: d# }2 Z4 L7 a7 f' }% _6 g

+ y; G- R& y. S8 q         }* N4 i+ O7 b. C* T1 b

/ t. ^0 T5 f8 G" c( _5 ]. u

m9 b, F+ J, q6 z- f     break;3 l) `& U/ @, r1 M

6 X1 o7 r5 ]$ g- t

8 U9 _; @! H9 X3 }1 B5 B4 O5 h 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 4 y! g9 g- _$ d }

8 X5 G& T4 o7 F0 B

4 ?- O7 b- @* z" \& V6 f9 r h upload对象构造函数如下,include/upload.class.php:25: 0 B, ?& D& t% h( y& R

! m9 u4 f- p% Y3 ~! _9 {* }

' _3 Q* j4 {2 m3 R( Y <?phpclass upload {$ N4 o# d* x5 a# M0 w0 o, d0 N

; @- S. Z$ A* s) ~

* v1 n8 h7 r7 ~) D. X$ O     function __construct($_file, $savepath, $savename = '', $fileformat = '') {9 Y2 s, W, L: V% V7 J% m5 v5 j

( s5 A* Y X. T+ G

$ o+ d) @: r6 K: V5 A8 U         global $DT, $_userid; 8 R: J/ z) U4 ^9 k* B9 G

( Q" w( W( r" s6 ]5 m5 r

6 \& u' ~* \% v# l         foreach($_file as $file) { E( m+ C% f: G6 Z5 ?6 M+ u

: Y* q* z) { u' u! @

. q: J [- X+ k' `- B- A             $this->file = $file['tmp_name'];+ r% [& t3 Q( ~+ F& W% I- O1 I' G

: g( R8 s1 s4 `

" j* R8 j) C, l0 S% c             $this->file_name = $file['name'];& j) f0 S$ J' X& o/ ]

1 _8 [% S% K2 d9 _+ Z

' q3 M s/ u- @" I4 h, B             $this->file_size = $file['size']; 2 t5 f! `2 B) A( _0 `8 `

$ C, j$ G5 K; n/ O- s$ H4 s, H: j. ^% u

& u/ y( W0 ^' u' E5 c             $this->file_type = $file['type'];4 K" B, B2 C- V

( P2 j, Y% S7 y: \

0 `3 ?6 K- u' n" Y2 L6 U             $this->file_error = $file['error'];' d% [1 z' @/ n1 F8 E+ Y" k3 m; B

/ H" g5 m0 w# ^9 u7 F7 E. e

& i- G( F1 G* S+ K* t) X+ x, t  3 G8 U9 V, b1 V8 ~

( p# Z+ _8 A, d* K& R

% C) c' r: v$ `! g6 z5 p         } ; m+ h) W- e! p' E: X! f

. W+ E" I, V7 ~2 c ^: x/ U6 [

. _5 J, f/ m& Y( f8 V- n) g         $this->userid = $_userid; 7 R9 f; S7 l3 t2 Z5 M9 f9 A/ E* @

! \" \( @, X3 ^5 U o a! v! ^8 s- a

0 ~' ^, ^3 c7 x/ ?5 \         $this->ext = file_ext($this->file_name); # Y k5 [8 K4 L2 n# n$ s8 \' \

" @' w7 ^0 Y- w$ Y" n$ w

4 `+ X) ^/ S% J9 v) O         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];# ?; U, l" s' y! j2 O

: v5 s* b! v! M* E3 S$ ~) l$ J

- f7 Z: {9 j* Z5 v         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 8 A& [5 P6 ]) t: h" X

- y' m# W, |+ _7 _% W: i+ L% T5 P

8 y' w; q" g' Q- A' Y$ t         $this->savepath = $savepath; 4 `; K) k0 m) G

% }, s* N. r- ^* f

2 G+ v5 ^# D3 I+ l- E, |         $this->savename = $savename; ' e; q3 e' v* H% _

# [& P z1 A' v: u8 @2 d2 h* C

/ e8 v5 \7 }( _- S     }}- V5 R% x d1 c+ ^6 E& B

: Y% @% P8 P" _$ f9 U

0 Q5 \" c% }3 b% v) N 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 # j7 ^* g$ k) P5 j- L/ D" _

5 p0 |6 A4 C9 V2 U, i) f

5 [9 ^3 F9 ?. Z" w- j3 B2 C% @) x 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ' d H$ I' p: T% v3 ?: a

3 x @( K" |0 p* x

0 j; m/ p( M# M1 N $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$ ?0 h( ?- `% n

) Q4 w1 j+ x7 X @

* ^# F u( N0 m 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: * H! y1 R! A+ s1 t: E; Z

/ R. ?1 T' x" k! G T

' t7 q! h3 q' F! O! o; y3 w& f8 J8 i   4 b9 _" l8 F0 e& i$ |- U+ w% e2 T

0 W% R, X" ~) k* Q1 M

$ {5 S( G7 q9 y# Q 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: " L1 N6 |9 ?% E5 A A6 t& a/ f

7 G0 U9 O# }6 @3 V$ M+ J

6 i6 `$ [3 e& H <?phpclass upload { ( N& h. I( _0 a0 F( x% }% R$ ]

: S5 c7 v- B2 J/ T, [9 W) @0 [

+ n+ m' d' J/ m& P( `     function save() {4 e' A5 z1 Y4 s

; |6 e" J3 z+ m' w& A' U

. m, r6 v, |9 s9 A+ p         include load('include.lang'); : k0 y/ l7 q; p

' a3 J7 a6 T% m& P1 L) K7 D

1 [3 l6 _. Q7 ~4 m4 |1 M         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); o4 S, Z# m) i0 k

, p& {; j/ ^+ ?4 E

, h& E: _" H+ z+ K" W h' e% ]  # `7 _- x9 Y! R& K/ U/ c* L3 A5 j+ Y: c

) f) r3 a: ]* }

! Y+ N, b3 V& M- _& e         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');% z8 A! ~1 [9 j$ g; ` `

# V$ y* i+ F |* J

0 i% A( m, |1 S" g7 u  9 r3 R! f+ Y$ c( o6 l

. r& [5 @' m; k9 }$ R6 S1 Z5 l

. b R1 q. T2 }         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);+ P8 p8 {9 A9 d" ?" U+ i) _

: }7 Z4 L7 [* v& X' n. H

( x g/ E Q$ r7 Y) z: q; p  ! x, N" p/ t/ S& c# k, c; h

4 _8 n$ {, y1 `

, `! |4 l* G& U         $this->set_savepath($this->savepath); 7 X( N+ d2 o1 f( \

" v( F4 m3 v( i2 v- Z

2 K7 J. y9 b- g0 r         $this->set_savename($this->savename);0 Z d6 ~9 C/ K/ J3 p+ j9 c

& }9 R. }! u3 {/ {% ^4 S

) A! Q1 Z, G8 r9 Y4 t+ t1 @   & R' v- {% y$ G) l2 ?: ]& l! f0 y: [

( h5 I+ L5 j0 L0 z4 v

3 m+ Y% ?1 V% b% A( Y9 |2 Z         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);1 q2 f7 U1 U8 c/ Q% P, X

1 f, X- S: f" u9 M. h* q1 q6 X

! u: u# w% U6 n         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); : I2 c0 ]7 r- J' ]0 p1 @

6 S d1 ^+ y6 ~! s4 m

/ U8 Z9 A7 o9 [4 D; v& t         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);/ R7 c) N9 B/ y

+ F- X2 Q+ o" v& Z) u" N

6 L' P$ R( ^6 v: p. b1 K  3 K- ~+ _0 T# }

( n6 k) w4 Y! u! p3 `( I

9 a+ S! C+ S7 G3 t) v5 {         $this->image = $this->is_image();7 U' u* p. a: h6 Z# U

4 H+ J0 M' [8 W8 k. [

, P6 r4 K$ Y+ Q: y- H" {6 I         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);$ @* v! X% b) A' K

3 z% ^2 p3 {" C

' O( v# ?7 V2 k) \0 [4 _         return true; $ q% ^2 e# n/ A$ P! `# r

) q% y5 V+ n0 [8 B/ I$ O5 H

, [" i# p5 Y8 b2 c6 q5 u     }}9 @* y7 b! i8 V

1 y+ ~. d8 Q9 q5 B* `

0 j, r! @+ o4 Y* q3 S3 a: j: @3 ` 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ( [3 E/ J; N- D# e

" S- l; m& ?; }1 ?7 v' [

$ U1 M% q$ Q e" h <?php- R0 P: c7 Y9 U& U& X

1 u; |* q( N* D/ {% N6 x

/ ~: ^3 V( B5 ~. I     function is_allow() { # @' J+ E2 b* K( c, f

( [: D: ^" M/ ?" v

, ]& Z: a( d8 s; o& S; j         if(!$this->fileformat) return false; / M2 G, q, O, M% ]. c

2 }- @; k' N* U# N

6 S) {$ c& u6 f$ y. r         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; ( H2 z/ f( L0 ?: G

! X7 d0 d" c) `/ o

& F3 {3 P/ j, V9 l& j* g         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; # D6 k! Q- c! m- e1 _

, e2 s6 i. i& J

! B! A' U: \! h3 k; f$ c         return true; # b; l& X. J: E/ N9 ~$ o" t7 Y

3 E% Q0 ~$ p! }9 L) |3 U

6 k" W( ]3 `+ {& K/ K, V J     }( k! M y- }' f9 m* X

# t- c/ O" F8 t# v$ ?

5 l0 v4 p3 t1 \ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。& W o) L: ?, f" s: n) Z& v

# H" ^# \. \ b# A, i9 J

: I% G0 R6 \- ?; ]( l; Y" I/ O 接着会进行真正的保存。通过$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文件。2 u0 j- d# u, M# P: M

' F8 j! S- W( t, b

$ }# Y! J& \4 r- G1 N$ A6 h1 |3 o 漏洞利用, _* y, u: z6 S0 I% E' H

( X+ g! {- A: o t

, F% i# q+ J7 _4 u5 S- Z0 y 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 ! [0 V7 ]0 w' H% s$ O. K6 m

1 B% @- P& m4 f( m8 n

3 l( s) j R' p8 u! p   , b9 P4 T& v1 B) X; P" |

; {6 D8 R) e8 n+ `. h

+ g2 K) R* A" g+ n1 S 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid- n) D& _' ?& I- g$ [% h

# r) R/ O" ^2 Q4 h' k0 m

1 F3 D; {8 r1 N: \/ h 不过实际利用上会有一定的限制。% s/ p% B0 d- b) I* x) A2 H

! J" u( W1 \! c

8 U* N" v) [( q( }2 f; a- P9 T( W+ q 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 . `2 x- c& S4 R" n6 l$ Z

9 B z4 o4 ] ]

0 w5 U3 S. ?1 y! N4 t# Z. T   & j4 S5 V6 u' n: z6 i" W" W' l

2 m+ ~2 o& ^) u* _5 s

: V% l+ `/ k% a8 b. U5 ] 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: `% ~# W: b0 D/ f( C

* s1 l2 w+ @, c& Q8 F, C8 [# m

' Q5 N+ S1 s) J9 J& |! Q' L 省略...$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]);省略...9 i5 @: E4 A3 N: J5 C1 K8 i

) n; I% z5 K$ d: K/ F

" ^2 R& _. P8 X3 T9 y 因此要利用成功就需要条件竞争了。7 `4 `$ U5 w& w- A

$ ^# i+ O% U3 u% J

0 ]) f5 F% E6 F* ]0 n/ c* v, a 补丁分析; \" z- p1 k' y( g$ s

0 E R. z+ Z: v0 t0 }

' V8 W2 J* ]/ O3 j/ w0 j  5 U: |' ?; J/ L! ~! g- M1 Y

: d3 A1 @* t. `

# L3 X" \0 y' l w0 O 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: $ g! M/ d$ _/ G6 O: ?! {

$ }; w+ q/ s& c& S1 P/ c

9 C3 d* Z' k$ @8 o function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}/ Y+ {) V3 A3 u! s6 N. w ~

8 ^& @3 D* C1 N" ^" U$ p1 w! O3 q

0 s: ?; k" I$ K7 \  ; |( N% M$ g+ p7 w# q7 u* t

3 w* f: a C- X- v4 s

) ?3 Y6 Y" ^: r8 [4 D" ` 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。# t1 S$ f* R) s. ?

, {6 j/ ^1 U) Q8 w% k2 k

9 V! F( y# F+ c' H) i 在is_allow()中增加对$this->savename的二次检查。) {3 j/ b# l8 J v

: \% c; }9 Z) q) F

* l; M7 k( O& m) ^' }$ x6 A 最后 $ O2 Z$ s3 Q/ \

% s$ _. T% w5 s$ K n# d. V

; t N- Z2 |( Y" G+ t7 E 嘛,祝各位大师傅中秋快乐! / F8 r2 S6 Y% }* T4 b

+ y4 B# r6 Z7 y3 d

- v( f+ l* U) m( Z2 b o& [   E- R1 a6 [' R

) `! p( w; x# T0 J2 o S d
回复

使用道具 举报

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

本版积分规则

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