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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
: f% x. p! ]' y1 T! f

1 T3 a& `4 b1 U" v

' X, g7 D6 |+ A: ~

2 I/ e6 V, Z5 n8 P+ ^2 w2 _ 前言 # S- A) e+ Q. n, F, o! ?( q$ @

9 i1 {0 E7 i, b; o# R U4 l

! `+ Y. k! F3 q# `) R 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。( H. q) V. Q. ~) \; I) @) J

! P; t/ I+ s* W

7 g3 C9 [* m/ k   1 u7 R! D) K: D& u1 `

/ p1 i6 @" y m* W2 K

! P" a% H q: F$ x 漏洞分析4 c9 ~$ p- z; p2 F

1 S5 ^- `* J5 j7 `9 f

5 M3 g9 B0 a9 `3 d 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:. c7 A2 T2 Y7 I( a6 O

( w2 d' |1 `! Q u' ?! v. Q

+ N& k+ d0 |9 T0 ~1 U7 \; p8 E& i: C   8 }4 N3 ]* q* y7 Q; M8 b" q6 Q

5 L6 e o/ l2 t( M5 q9 G t) z

/ V* X8 Q( \2 O( c+ b0 \% n8 j 对应着avatar.inc.php代码如下: + f1 x6 T& P I5 }1 h4 z

, T/ ^4 w- R# w' ~4 c6 g

( k F! e) o! W: C8 R& ]( k5 w% R7 M) o <?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) { " f" x4 s w2 p8 D+ l' A- |- _

( _' _% u! ^2 i3 t

9 H R( |+ f% p- b. P( X8 E8 D     case 'upload':7 J2 J2 _1 F* g, X

C& ?$ Z' d. O! c" v) D( _ u1 G

6 Y9 R6 ]6 M- k [. q: T( u* Q         if(!$_FILES['file']['size']) {$ {" b, w8 O/ U; G5 Z2 E

- V% l( V: i; Q4 b) V1 {0 b

0 p* x$ a$ m9 [, d3 _             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); # c5 E, E r# c/ S3 s6 r. Q

: [3 p6 i$ k' r3 ^& ]

7 i. S2 ?, K9 P! X             exit('{"error":1,"message":"Error FILE"}');% s7 B2 `6 u* C" e

9 K5 R# d% |. t" H; b* ~8 y3 }

7 A g- ]* _6 B) f2 b$ J         } 0 _$ H: | m* z8 l" |) S

- e) E/ A( q+ [9 F6 s# W/ p

2 c o" K7 n5 O( J- w2 g         require DT_ROOT.'/include/upload.class.php';4 b2 J# R- x# j b r

@" x1 g6 Y! {, N3 q; ^6 T

( J# H( i! Z; I0 W2 e   7 i% l' q4 x! C _+ }7 V

O; u! z. @* q' t2 q7 b( k

: b! U$ a' Z. o1 f6 B6 y         $ext = file_ext($_FILES['file']['name']);: |( M# S# ?, h" u

+ W8 I1 [3 D9 A* k7 a$ |8 V

/ B# o+ T5 B/ Z; r7 V( h9 E         $name = 'avatar'.$_userid.'.'.$ext; D& E- E" y2 p2 a

+ c7 e! y* L/ j; w: u+ Z/ Q5 {

, x* @( S; f3 K; G. } U. j f- i         $file = DT_ROOT.'/file/temp/'.$name; 5 D+ H' T7 C( X

& C. f0 }# R3 y! \" z

) J* Q1 P# l4 e! E   % Q9 N0 l' M. y: d: G$ O- n

# X0 H4 [7 _3 f5 q& G

5 q w# m. T7 P         if(is_file($file)) file_del($file);! T+ z q! \2 R, Y

! A* r7 v: S7 x! W% _

$ d& q/ c& ?- e, _/ `; }' ~8 \; m         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); 1 E; V$ ]- x0 K1 A+ B. Y" o

9 Q7 k/ J4 B. m+ V7 [# D' c) z& B5 Z

. ~% S/ d( i u7 ~* x# H  9 Y8 i+ g' t9 e5 p

/ r; U) r7 K9 l1 K( E/ p

4 p, L3 f) J1 G: T) b         $upload->adduserid = false;; E3 M" B6 B7 [+ o+ E

9 p- L1 H, U0 W

( m! U4 U3 ?3 H0 U( |   + g. U' O# w5 p7 i! I

7 U7 H) J: }1 S$ c4 O: Z( T+ j

: @ ? l" \3 u. n. S# v" d         if($upload->save()) {1 K |5 P' J( g3 X/ i

( [% `# j8 T0 n5 G; G

+ ~+ T" ~7 C: M2 a1 d8 A1 {% M             ...+ s; C! G4 ^; ~- o

% ^- X# v/ G" {3 F D" `

% e+ O0 f5 L2 o$ \5 V* a6 L2 o         } else {" q; Y5 c6 k Z) ~

2 q7 G6 H5 t* f- D' {

! V) n9 V h8 G: w. i             .... i0 V' f2 @* I

- n* I3 b5 @5 D& F' k

9 `7 ]% X- A# L" L8 _) e         } - A! D5 q7 t$ N0 [2 j. _ w

/ n" \. {, }8 m4 ` r

" I* o6 y/ u3 f- P3 | F- e- ~     break; 7 W, Z; F$ j* w1 G6 [6 W) r! I

& b0 Q$ U# y3 t5 ^' X, i

3 j m, l# Z% E1 D# ?0 \9 ~4 |) y% e 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。" e! ]$ n1 T! C4 I# m

* b- Z: o3 y! m0 V+ Q

; E/ v/ t5 {: ?3 R9 a6 {3 a upload对象构造函数如下,include/upload.class.php:25: & u& C+ J& k+ s. D( _6 ~; m

2 Y- d* x1 |& J2 i: g3 n

# N- b( |) i! {4 e6 }0 j <?phpclass upload {7 F" b+ S4 w; L) o

8 N- `1 `: x" G" s5 {

: B$ `5 E- j* u; ^& C- J6 g; e     function __construct($_file, $savepath, $savename = '', $fileformat = '') {0 B9 r* u) E) _1 l. l

4 A! z1 V* W6 o8 H

- W. \, _! M V/ ?# }+ Q         global $DT, $_userid;, ~/ o$ Z+ x8 A$ @/ f. J

( b: V( f- w9 P4 t0 `

# ^" t$ d6 M" E. d6 n0 P         foreach($_file as $file) {8 T4 b8 ?5 t M2 ?; t- k) t

. [$ o: A& j$ ]7 F

) Z o* ]/ H3 Z* y: N4 W             $this->file = $file['tmp_name']; 9 X6 y) J! b5 ?1 n( s. N/ ^

! v/ G F9 k0 z0 c

) [0 ]" }; k. Y9 F+ z             $this->file_name = $file['name'];. p- S! `$ x5 k* N2 C

' z" A8 E0 E% f/ \/ E( t

; `! ~8 H% T9 V+ v6 s; p+ m             $this->file_size = $file['size']; \- m" E+ x- v& v1 ~3 [

# ~; o* L, Y6 p# D6 B

5 p1 `" T# a% _5 K! t1 H5 _             $this->file_type = $file['type'];. s. L, b- p% L0 k

7 x$ b/ H8 M. u' ^8 w( i9 E

: @, P: y* H. H) ]( h) {0 u& I             $this->file_error = $file['error'];- @. g, p1 R+ Z& ?

5 b- K1 |7 }$ ^0 s

$ J9 v- @- t2 X0 u6 N' B   3 u1 @6 d& q* m

$ g: r8 U# O+ W! l8 u# ^. U' `0 g

1 T! N8 }% s5 h         }4 A. a9 ~( y ~7 j j6 R! C

5 k3 U, U' D" t& j

2 b7 G! E) L8 S% }5 M: N B         $this->userid = $_userid;2 G2 b5 s% Y' c; R

/ P% D2 N( L( ^. E& }) H' T

# h6 l6 |' g1 e         $this->ext = file_ext($this->file_name);, c) [. T# l8 V" C8 c, T4 N

' E- l, a* X' Z( Q3 }- O5 n

$ L$ v+ k1 j& O         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; 0 @1 f( U: H5 `3 _: R6 D, m

$ ]9 {( B! X0 T/ d8 c2 k7 H( T6 O8 O

) C3 U G+ [, A7 z; e7 r; y1 }" L1 _         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;5 k2 J* x2 t. n1 [8 ?/ W' m

! p# V$ n5 ~& j5 ?. K+ ]) K

) F+ D' E4 j! i# l v$ [         $this->savepath = $savepath;% T9 u6 V- W' v9 [: h/ ^/ u

* A# z1 _0 l5 b2 _

( @) J/ ?+ l# z& R         $this->savename = $savename; 4 a1 s2 E+ X: A" K- d

0 a' N0 M& }1 V2 ^: o2 M

! c6 J) ?. I1 l% n* @. `     }}! |) w8 t8 i/ _4 f1 j

- ]8 c" A+ ?1 n q2 I

" N3 R$ z9 ?$ P+ P9 @6 T+ l9 T# \ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。+ [) Q; Z4 B* e/ N! f6 m* I9 T

7 e2 ~" Z0 C3 d8 C: B

4 N/ W( d" {$ @ R" r6 Z4 f, e" c 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ( j8 z; X C( d$ z4 f) n7 i

% k. Z3 B" S! W5 Z# _

1 ]2 a, w3 O e' V. U* r. o $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) }/ w4 V0 L9 ?2 X

' Z* ?' K! j8 ~+ g) u: m

" ~0 L9 O& J; D; Q6 [: K 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: ) Z( e4 ^) I! {0 N" x/ Z {( T6 f

$ Q G: e8 Y% V e y% ^5 Q4 Y( H( E

3 ?7 H7 V1 z; x3 @   : m3 Z* o! G7 ^" W6 e* `" ^2 ~

$ Q. {0 H9 o; {

$ u% F' j7 d8 R# w 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:. v, ~( {4 b8 x# d" k/ @

+ x; ]' j ]4 X% ~8 R; G" I

! F3 o0 q0 P ]2 w2 ^) c/ V, s& P) o <?phpclass upload {0 [& U" v1 i, Y

* z8 `: c' x0 F5 H/ W: b- y: `( V

3 M" ?" Z# ?, y$ L: d7 ?     function save() { ) k x6 l5 m& b" G

+ }! c& {- {! z. S; H) n* s/ p, b+ ?

3 H9 t6 y7 y2 `5 ?: \1 @- N9 h, e7 W3 g1 P         include load('include.lang');/ r6 Z: d ?& E) p% b) u. i u7 B8 @

! v* K/ P+ G1 g& b

+ l- k. t4 ]- | g         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');! y' L r, |/ [' v0 u P8 [

0 M1 q. ^# [* B

% u4 I- ?3 _0 p  8 z. w" W) u/ R# B

! j( @$ s% p( p

# L- h9 K: x/ ^) H4 o6 Q9 R3 s' e9 c         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 6 y' U8 i2 s! t5 q v. l, n0 C

! S2 }) _: k% ?" U

8 W- v# R$ u+ s6 W+ N; F g" q  1 a; }9 l% x o" c* m/ F3 ~- B! _

3 v7 D2 K" u9 w3 Q

+ ]7 T% ?) u2 s( c* P5 g( o; _         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);5 ~& n! P; ~% g5 e, B- ~/ p

8 r; r1 I. `2 D* k. t3 A+ Z+ `" N" b! p

7 X- e3 E0 _3 M$ K3 E' r  ! _0 c% @' a( \+ n9 f* t5 N

$ t5 R8 [- v# |, T. c- N

, C. l& n) r7 P& Q) X5 ~& R; e# j/ [; B         $this->set_savepath($this->savepath);$ R1 B0 j) P0 ]2 y" y

) T7 V: E1 M+ \4 I, \6 u8 Y

6 l) ]: b: x9 @9 M, q( r4 ?         $this->set_savename($this->savename); , T: N0 u2 h" X9 J9 d

% k# x& k1 u/ A& R0 |( x

w* w! K( l2 L8 G, D9 G9 L7 l1 g   - D6 F& d7 |3 [: H _9 ]8 A( n4 c

% _4 Q, v& _8 w

S! {0 \! F8 i- o' |+ I: {0 a4 f         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); # d$ z6 O1 e* } D5 Y7 F

& _4 R" j R3 G! U6 W" j& R \

& p7 ]' |7 b. G/ r |" k+ k         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); + D4 P& z' |& ~# S

0 H" I% Y4 e+ G8 K" i1 I

' E N# Q5 [- b0 k! P) x5 _- R3 |         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); : l; I! W, [( ?

' Y9 _: p+ [: [& k3 W2 v

# v; [8 Q0 K; n2 G   S( G2 U# w" x! M" b+ g

$ b& T0 P5 U: D

2 @7 U" i! M* v( H7 j( Z         $this->image = $this->is_image();3 Q; ?1 a5 t4 Y0 V& s

7 U2 ?, W2 \5 r0 M4 N* n4 m

6 i" Y7 O# B3 |" q; F0 Y         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); Z$ C# _- v7 Y7 W, r

8 R, c+ P4 v: I6 Q0 }2 }

6 h* _- W! T! ]5 O* M" G& c% M         return true;: O* m% ~% ~# E% A7 I; X# @$ ]

5 v$ l; Z9 N( \% W% j$ l

; Q/ k) c/ M3 w7 n+ \) t" Y     }} 1 I6 \0 c- A8 {1 o! x/ w

* B- m+ K" Y; v0 T2 u- u* H6 y

) r5 H$ ?+ A; z; `5 x& n: a: ] 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 2 n- I0 K4 n0 R% c6 U: b( j+ \' Y

& _: v$ N- k/ l( C, q G/ c

3 t$ j6 ]" a, [ <?php+ x! b! F3 B1 c

' l; b+ O, ]. ?; X5 y

! n& E7 T/ J/ ]! F, J7 b     function is_allow() {2 B9 T8 W$ q# J: u. o

7 Q* R, P$ |" C/ u6 { C& j

4 [& h8 O: S- W9 C# x1 E         if(!$this->fileformat) return false; 3 X- ]9 s6 K$ L* C1 J

' t( ]! z' o2 g: @( h9 E1 a

+ X2 U6 M) L* }* v8 M+ A3 `         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;1 f6 Y+ P p' l* x( Q) u% f4 ^

# o% s# d) y: Y. a+ {+ ^

. H4 N- c6 y8 }3 \; d4 W' 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; $ I, y9 P* k, \

$ }5 J5 u# Q/ n; `2 L7 r

% ^, D8 U# s5 D" B$ O2 `         return true;! ?) P. f! @3 [

) h8 N7 A) T5 ]( @( z0 G

9 c# V" a3 s8 H( G7 k+ r/ k     } $ V* _6 s- G- g E: b, ?2 }0 f

( G( @. T J- F l

) U: W- D, L6 L/ F8 `$ \( K+ W) I 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。3 C0 l% e4 p3 @# m

! R) H/ d+ y6 d# J5 y7 Y

4 `: F# b/ s$ n7 Q: 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文件。" c8 B6 t n+ d

9 n* Y+ |; n% F: L

* g8 W7 K j' N1 ]) H, _+ h 漏洞利用 . L! x( h6 z4 j5 r. R8 `+ D

. \6 {5 r0 s1 x, E) x: o/ |

) ]# \! ~; H& n* g9 m$ l 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 4 L% w y( h8 i3 C" k3 |

% }( {$ u: W$ i$ N2 \: t7 b5 t

2 S( I2 \' q1 f9 N: w$ z- z3 S   4 e+ K+ @. o4 M* }( ?

& z+ P2 b1 r/ P$ l% a7 D& r4 J

7 K" \* T- z' G( q# C( G 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 7 z4 W5 A& d0 x& {% ~

/ J, r2 b' s' U. v% S; k- h

6 m: f! t' G1 Q7 @9 H9 Z! |) D* q 不过实际利用上会有一定的限制。 ; ~; _5 H6 g7 A

& ^4 F5 ^, O4 ~8 B

2 ]# L% ^( w- e2 K8 Q+ n) C: ^ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。( @5 L0 `2 @* v

8 C4 l4 _) _+ m0 B7 E

- N+ I/ b' z' _( W& ^+ l  * b8 U% \9 `4 X* j! @% X3 x. o

2 j. ^' r* j1 Z- \( f; j9 r+ n! k, h

; D: N1 K! [ w$ O0 n& { 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! O, D. G! i9 \$ q! H( Z8 I

* t# {* K: ?& X8 p

# p2 L! @7 g1 g5 m 省略...$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]);省略... 2 h$ t9 t* C. S9 e

D6 J* l; `# ]& A7 V

* y; C0 q$ I& o- G9 y) N 因此要利用成功就需要条件竞争了。 - C0 \% `$ [( s

; u3 g; @; A6 g9 H

0 }- I! o1 r _ 补丁分析( U* F2 ^# V3 d. [ Q. t

0 s% k: b/ r6 ]' G! U8 G& E+ ^5 ]

0 U4 B1 b7 D/ F- q( J( o  : ~; }, r8 @6 J2 M! B/ G& x: G* y

! M* R, S# p8 L- p

( s. h# \+ b( \; N- @1 o" [( e) E3 R 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: - ]0 ]$ W; u3 z3 q0 r. }

0 z8 H# M3 ]- O$ u/ s

3 d2 s6 ~0 y" i" e, j9 y function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} - z9 @$ I9 [7 ?

: P( A- L3 y: Q& E1 W2 e

t" w% k5 T0 U   j n6 t [+ j0 H8 r0 |5 `

9 r$ `; e5 r+ q; W" w- ~+ e

' j) {; H3 E3 q' A8 x, P' d- t. a 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 l! k$ x7 k4 G& D0 g9 N

' b" B4 k, q% |! n1 C

! B" i" h0 K+ y, ^ 在is_allow()中增加对$this->savename的二次检查。 T" \& |2 S6 Y& b" U0 B

% J3 a3 y' X8 \# U' G: r m

( Q0 G6 k# E3 L5 P 最后4 ]- E' K! X' a. U/ C

/ N: Y" V; m4 C$ k$ [1 m2 E' P

! q' X2 ^, l* l% ~1 W: N2 q$ V# J 嘛,祝各位大师傅中秋快乐!$ R# o1 ^# {3 ~$ Z' T; c

8 |! G* ~- p+ l {. e/ e0 N6 @9 |

9 l8 i: W$ n8 Q h3 l   " m3 F- s" s' k

( G( n+ J! I9 A
回复

使用道具 举报

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

本版积分规则

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