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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
& y" B9 n A* I* ~$ D4 h) |0 _( h# t

& X- F0 t4 z/ u

; I( V$ n; ]4 A2 Y2 V# x' ~! N

9 c1 t% ?$ g* }+ y. p" K+ K 前言% D* m* ^4 E: W2 C6 ?' x) b9 l

5 j6 N- w( i, T2 i4 i; h# ]$ W( h% Z

" R7 o! d" v7 c, f( {" S 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 / y' s, m8 K, ?0 `# D1 E6 p

/ }7 J% b" C7 {4 \0 ^

# M# z O3 [. g$ g  . O9 h3 }2 Q9 J) W, K

6 X* D( a- Q2 a* t; i+ e

. E l, B3 k/ }- F 漏洞分析 * b/ m8 ^( D7 R8 v: ?

! L3 r. u) J5 Z0 p3 ~+ a7 j% O

, n' C( y1 p5 E1 |' g: D1 C 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: ' l; A. \ n- X; k6 `, K" f& B

( \9 U, g8 h7 {

0 r, ^ `: q! l4 f   , l5 C" n b) r+ s7 f) l

1 G5 u: I+ G% z

' W( O7 t$ |7 Q& i" E6 E* w& A 对应着avatar.inc.php代码如下:# |) n" c4 |& X, c m7 o

8 B4 Y+ Y/ w$ p$ J1 m/ q

( K/ _5 `% g& D, ^, h$ h" ^3 ^2 C <?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) { : V: P2 r O& B x" j. q

( T- r! T% q8 n' y

) Q2 h- F9 W4 F( [     case 'upload': . e6 b/ O o5 A) t) V+ b

) z5 W; u8 e: d; [7 o

9 g4 Z! w: R) _& K, K         if(!$_FILES['file']['size']) { , p# i* L3 l. a9 ]9 U

" L# t! \" J: [9 J0 v- O) Z

1 F' G" U* g( V/ |( T$ I% n4 d             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); 0 q/ E* r7 d& A# K4 q& G1 Q

1 K& _/ c" B _; X1 N) V, V' |7 S

5 G1 D. b* `9 x/ n; R ]: h ~/ p7 _             exit('{"error":1,"message":"Error FILE"}'); , _/ k6 k# R/ p

0 f6 S. A D2 ^/ o( e

0 N* n# r/ p* E0 x. B% T         } % c; `) k% O1 C2 m: d

8 \7 @8 {5 F* }# k2 s

2 m. ^, J( ` E! B         require DT_ROOT.'/include/upload.class.php'; 3 ]9 D7 |' e( n

4 ^4 m' Z" w9 C

/ E$ @- l) g1 p5 O  & Y/ l1 Z$ F& Z. l6 R3 O

; i& c+ ^! S J1 j0 Z% [

' t. c# ^* H: o1 Q' r; l         $ext = file_ext($_FILES['file']['name']);7 t% J/ s% N4 i* Q; a, `

/ p$ |6 W; o3 x: z: v" [

2 U& D5 l4 O0 a# {9 w         $name = 'avatar'.$_userid.'.'.$ext;3 G1 V6 W% P3 a

0 V2 v+ b& a, W4 D( {: u9 D [7 _

3 O: i1 ^+ a5 z$ {( y+ O         $file = DT_ROOT.'/file/temp/'.$name; - Q* R: M+ ^! L( ^7 x# \$ ~/ k4 E

. s: A& T: g6 ?5 Y$ x

. p% n7 C3 x' h E' d  7 [% _% X: \% l1 W- M0 q

6 T$ ^! V3 R7 i2 x. m% L

3 b+ x% T* c& F4 b; ]1 C; }2 I/ A8 V         if(is_file($file)) file_del($file);2 D1 A) j) ]& _, }! V

2 N) u( G, o2 Z' h7 H

0 {: y. I3 U% S$ x" f% \0 ?" M% d; s         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); / i4 J" D- s& _7 M" x! u X

* m" s3 J9 Q/ i4 S

% W9 s- G1 \( _ M0 i2 q   , F) Q, T# ^; @' Y: G

) v% X( B4 B2 C1 o5 X

N! ^. q5 z& s6 {$ H6 d. @         $upload->adduserid = false;& \2 L0 b5 t% l R* ]: n$ i% y

5 W& ~% J. X% |" d

* R$ X. ^0 S5 g8 E$ G& @   0 j* K3 a" r0 \$ U

* u( S& Y! v2 O3 }

6 J, B; i. \. e9 |, i' M" _         if($upload->save()) { & ?- `4 U- \+ A: G/ R: A3 y2 ~* S2 O

9 e5 G. y7 h" i8 K% K0 G* {$ a

" A1 k7 e5 ^( n( R- @6 ~ L             ...; f8 G% W3 f# ^: N# T

" l# U5 m1 u$ e. Q' s

) e, o. J+ R$ k; h         } else {* }+ b: [2 u7 E4 P" h5 f& I

+ b: w$ r# K# X) n1 ~0 l

) M5 @, N8 a" y. Z             ...% `3 g3 ^5 k! ?1 g

) [* E6 C% e1 I9 J

c5 x5 H+ A1 S9 \; ?         } $ I' C/ ^% ^, l- A" l9 J& ~. r

" D, I% `6 l4 X" C ?8 |

- {# Q0 X1 X) I0 ~4 Y     break; 3 R7 q2 D* g; B; ^9 U

' l' C& n- d* u/ L2 a

6 `* K2 }1 _& r$ S 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 ) m v* H( ?; p- f

! L/ o- P, S2 t

# U% d: D4 y6 A% U: i upload对象构造函数如下,include/upload.class.php:25:7 O2 |( N& N; J( ^ [' F1 H$ A( a

0 |' O% w9 a9 Q5 q+ _1 K" B

: z& s# v# z1 i <?phpclass upload {& {3 T* m4 S( K; W2 ]7 T W8 p

3 f- t9 L) Q& t ?9 o# C6 W

6 `* K$ M. d7 m |# n! U! k3 E1 |, X: M     function __construct($_file, $savepath, $savename = '', $fileformat = '') {0 E9 `2 ]. P0 W- f- w. T

~2 m ]) f, _/ U

( `3 ^; }# C% u         global $DT, $_userid;* Q# T4 H& s o

- ?, u5 j* }, m R7 E

' K, m6 ]; L+ n         foreach($_file as $file) { 2 j4 M# V9 S. r% D# p- G g

, d/ J- E( d$ u# B) s! D8 M) C! e

8 v1 {3 g9 ^3 g8 V2 z             $this->file = $file['tmp_name'];& ]8 ~: W/ j: Z. H

w! r; B. P+ n7 }* H, W1 M% ]

* e. `6 d, x) o1 K4 d, h |             $this->file_name = $file['name'];$ }# f( Z3 f( R3 t

0 N' r, }7 \$ Z4 `1 \9 S

/ X* P! x* G' m             $this->file_size = $file['size']; : o; ]* H2 @& ? ]

# a5 V0 e ]% J* ~4 _

& y- u; b% M. s; J" x" g3 G- ]             $this->file_type = $file['type']; 8 f t) o! h2 M) q/ j6 H: n

" O( J* b5 h8 F$ `

# q' i! I0 t* u             $this->file_error = $file['error']; % t# i6 @5 x) E" |" V5 V

% l( @6 T0 q5 S8 y- l: [

) G4 L3 f, P3 d0 E/ W   $ }' b$ c& H* q1 y; g

( m# C8 d8 A* t# O: l. @9 P5 U6 A

3 E/ E' G/ a2 b$ \ x+ x         }6 F, J& f; q. c+ d: H

+ t5 F& x O! z: z+ J

7 Z: y* t3 n. j$ J/ P         $this->userid = $_userid;8 o- F6 t5 }: _- Y/ n, p$ i

) o' C! T- ~ r5 O/ L" O

6 D' ?1 _2 i3 E7 h, h3 x         $this->ext = file_ext($this->file_name);7 S1 j! E& o! h% v, F# Q* C4 o

% L5 ~9 Q5 F$ u; Z& t @# G& Q: i& Z

) | T' f! m$ I: ]% L         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; ( T4 Q- @, D8 b6 B

, b0 k7 H& g; X/ ~( W

# v" z! m* S! {1 W j$ [. q5 O         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;+ l! M$ n1 h/ M. |

7 L8 n- o* L) ^$ k5 E& q

8 o" a1 Y& O! D, K2 i5 P1 j         $this->savepath = $savepath; ( I. x3 p1 O% A1 ~3 j4 _! d4 U/ n, k

" a+ `3 `" b/ h6 z* F4 `, ~. L

7 h8 ]! ^8 _8 ]- H8 q& r& w! ~- A         $this->savename = $savename;" Y3 L8 U: C. W+ U# a/ z- E) l( b

: X6 P; E# N) Q2 p9 j

6 w% [$ F2 u0 K) u     }}7 d3 g3 o* |2 k8 H! D

* [3 ^2 y) x% _. w ~; J. j5 ^

$ {( V5 r$ u' V8 V) H8 q; I) p2 u" f 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 * c+ T7 q8 a6 |- |

3 x" D( `. _6 ?. a

# z- q @/ w9 Q# f/ x3 H 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * G6 ?3 P& r4 |/ W( f) k. p! X

# W$ @( }+ j. f

. e- t. {# G. w% 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.php ( h9 ^, E. S3 T$ R$ J/ O

# b6 e/ J/ c' L% J/ Y' `

2 f" Y* o2 k0 [6 h( A 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:* {9 f# {# D2 {: _/ N/ ^+ |

" P, B3 `6 S: G8 U$ G

3 e6 _, G8 v. W% `( T  $ I7 |! w# K& m

* \, Y0 F9 y& J! U2 X

& \9 G1 E; v8 G A* f1 @/ i- Y 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: ; J3 u/ |: q6 h6 b& K5 N7 O3 A3 D _

0 M9 b' n9 v4 h5 p8 V$ I3 x

( t0 _, |0 j2 q) f6 ~ <?phpclass upload {5 ^3 f8 R3 V9 s T: S2 n

- y% I- {8 s- z& M2 Z/ z' ?6 j

7 h; B V7 j# n5 W3 o; C     function save() {& ?% q3 A0 \( Y

' o& m# l: b! |

& N2 F+ C8 u4 `+ ~5 H         include load('include.lang'); . S4 V6 f: S! Q2 m2 H" j

; b; j- L* J/ G# {

8 h% u# f9 F( w& {, A' J         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');$ r$ e: ?- S8 J5 ?( J# g2 w

( r$ `& q9 x% H0 f

7 q R8 Y9 h; S9 t- W2 Z6 O# @   9 ^! X4 N9 d4 D6 X# c o' c- H

/ x. `+ S* {, g( P) j- F

8 d! J( u; ?! e( X         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 8 I y1 Z d3 t+ i

3 h J1 D% K6 l$ T

1 }% e9 @5 n0 {9 K' l   3 i8 g8 t( T/ N w1 V

# E% ~) y$ V+ d

6 y$ S( b, E* W: M2 G6 y; Y' k/ \         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 2 ^; ?# b3 g8 s7 a$ Z2 I

3 m7 K+ X) K$ Z! P3 H, T9 c

; z+ F+ K6 ?0 \) L6 j2 o   9 ^8 \9 h+ c7 [, s, ]+ Z

8 g! i# F) Q: P7 p

* G( D( _8 w& K- Q. j% o9 f% W5 }         $this->set_savepath($this->savepath);$ A8 U5 P0 J: u. e- c

6 |, R* x3 w* U. Z2 L: m

, p& u+ `$ J1 Z) I: a2 o- N$ j         $this->set_savename($this->savename);, x- D/ [* q, D# e5 @2 S6 B3 U

! D; R( W* [) W j

\2 [- W u& X6 r# A3 r  ; {7 Z& ]: _7 K% D6 k# y$ S

: @8 X5 @1 I" }( l! K

% j7 B$ Y7 Y+ r `         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ _$ L$ S5 B- s# Z9 z

% Z. V$ D0 _9 R6 w3 N& c; h

# S; O J5 s2 v! e; U2 i         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);2 L: n$ U4 o6 m0 _) A5 x, Q

; w. R6 S" J9 x: C$ r

5 h: G9 T i$ `# ~ H         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);: A7 ]! \3 Y$ O8 A5 `; E# B3 B2 P

. n0 f0 S0 V) o. d' J- w% ?. j; x

% G& A, ~) Q. a9 M8 `* t# w   ( e( b0 ?0 D( y

6 a3 Z3 _6 t3 e7 D7 j

* [+ _( {/ w# [         $this->image = $this->is_image();8 V8 B3 d( T) Z7 e" z7 a

! q# ` D: `6 y A; P, h

& N9 S3 d. A0 n         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);1 w: G+ ?) j/ ^5 G& f

& j8 q7 F2 H7 Z" h5 g

. U9 }0 _2 A1 b, @1 W' M4 t1 ^         return true;* F0 k* b+ I3 u9 \- C

: w% W# Q" \( _* p0 Q+ E

; G( J2 O! Z- |1 b     }}9 K1 D2 `8 k% @% R4 f8 G1 M' V2 Q

& b. K \0 M( \+ J1 y; y# O

! K. j( d: Y9 z4 ^ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: & _: V8 |2 F _! g6 ]6 u9 [3 F

) y: o3 }8 Z& I# o) E8 P

" N1 g' I0 D9 } <?php + q" h" ^' Z& m

/ B: [/ I0 M' Z5 C" u

- s% Z* B: d/ f' { ~( w     function is_allow() { - o J+ H' z5 Q& C0 a( r, ]

3 C+ N {; d2 u0 E+ e* l

- q! R T. u3 x" W, d         if(!$this->fileformat) return false;' B ]# z3 x% `% u, ]

9 Y0 _. ~* f. D

- K9 z. h# t( @1 `9 n T         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 D3 c+ W3 }7 H+ G% c

+ d1 t6 W! Y- X# R

: Y1 i0 M! @' F0 z; 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;+ t' X" z; z8 g- F2 b

. Y# N- s H6 T2 j0 Z

! M( {3 i" _; Y P# n ^+ o         return true; ( T | n0 [6 l( X' \

" V* w% a+ a1 V' h. ?2 L$ I& t

3 ]" |" _7 Q6 Y4 M* ?* S1 i" ^; u     } 2 T# Y6 O' }4 I; e2 m

$ D; I: n i* M$ {) O& y

# Z% \1 x* g2 ~9 q2 h1 P 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。3 K G c$ q' G. H: ]

8 b: E* K. p* w }! r$ j

- W& a6 L9 ]! W! D1 g C 接着会进行真正的保存。通过$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文件。 q/ w7 e- j' ]4 e/ Y% N2 H

: T' g, c. Y. H _

( q" l: x" [$ H8 F4 f* D 漏洞利用& ], P' c3 a. r9 b; r$ S& |

& p5 ]; `+ K1 l

9 N4 I/ }1 Z1 Q3 f7 x( L: b 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 & t: J% J, `- K$ J5 V& d0 G1 `

1 k& _! E+ Z2 X

' ~# s; Q l& }# X+ `   ; k4 u P+ g# G7 y* n

3 f7 Z! l1 n8 E; v& {5 Q

6 K4 q: h2 A9 m( `) r6 g5 z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 3 N, ^4 w/ Q* q$ \% B0 ~% U# j

1 M i: Z% l$ Z6 g+ t7 Q

) J* Z* Q* T7 I" C" h% M& }5 [ 不过实际利用上会有一定的限制。 2 u7 ?3 u- p* f& C& n+ g

/ ?) p# ]: y- R) @9 \9 T* L

9 R8 o% i& ^7 C/ M! G3 x 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 5 O7 R `9 `0 F- v4 U; N

r& H- }, a1 x( R5 ~9 U9 |* v

, `2 {5 t, m k  % W( {' k: O2 m* `4 L. l

8 S& h, |8 J) m8 R, u' w. W/ \; D( C3 W

- K) N/ K+ O! C \2 G2 N: H i 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:) W' R3 P8 r* u! j

2 [, Z; {( O2 \3 L( F, t

- N1 ~" C. e3 K2 Z& T: y( e 省略...$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]);省略... {" W# O9 v/ y1 O' R" o

3 {+ r9 f! B3 k& k* l

1 t& F9 N- p' J4 q 因此要利用成功就需要条件竞争了。" k% i7 E/ o. f @" b

9 P' B+ Z1 T7 p8 R& \; o

; R# o4 a- ^4 |% s. \5 @( M% } 补丁分析, N& W' h2 b5 |( C; a

! ^3 L9 {9 J' X+ X1 I3 {) N0 I

" ]: Q: j: T+ } n7 L7 v; h   * D5 n/ j p3 U0 z5 k

, Q. @' O Z. ~1 l/ p

, d3 Z, w9 t' e5 J( { 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:+ ~, _+ G9 W8 [' i8 P' }: S

+ N% |( A# e, a: ~ u

0 E# R' }- w1 l: D, p% Y function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}+ H, O8 l& _+ j) Z% ?2 f3 G8 k

2 ^( Y3 f L7 P7 m# A: ]" j

2 n3 U; E7 {9 I   # j9 t1 H6 s/ ^1 _& y" n; o

) x1 v4 Q% Z- \" x: {/ W$ J. Z; n6 o$ g# Z

7 v- o' U \1 y/ v# S 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 9 j5 X4 ~2 K, d+ g: O: u* {: H& f

6 f- Y) S+ L, |- U; p$ J

/ b4 b5 x) t1 n& }' d# Y1 h5 ]1 | 在is_allow()中增加对$this->savename的二次检查。/ d. X4 t- ]$ d

* O+ k- X* V9 c. H! {

" z& `' q8 l: ~7 E3 t 最后 5 v# M6 h p X/ q4 x8 k0 g3 R

" H4 m5 w3 H' w2 w, t7 r- d

3 n. p; ~" I' ]! ~) i% ?% \% t 嘛,祝各位大师傅中秋快乐! $ c+ f1 m* f5 I P* e

" Y8 u- m0 B( v

- K% a2 s- a2 S$ |, w  # A+ Z7 i I7 c' P

( g) {. @# `' v- c* v& w+ g1 ]
回复

使用道具 举报

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

本版积分规则

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