中国网络渗透测试联盟

标题: Destoon cms前台getwebshell [打印本页]

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
# T% a- O/ q' m+ k/ ^! a3 s

0 s% H" b8 Y. g: G6 ~- D' D7 P6 e

# Z7 [& ~! k) b5 t. j

0 ^. e5 F, y* } 前言 , W" D0 w/ A6 F1 [% v' j

- x. M4 l: {5 R: @( q; r& g5 x( v

8 v/ Y: @( t# ?4 f; k) Z 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。4 K" ?" f# j* }7 r* h

4 A, q. Y$ O3 O

0 ? y- R. Y2 S7 Y   . m( S; \+ ?1 F

2 n b9 D3 H3 x7 y

8 c; }# F. u9 l% n$ C 漏洞分析 0 v8 q0 q% X. _' J- g& D

$ v4 O( \4 S2 u: O8 i3 W" [

& y4 I8 Y3 }) z! U" l 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 3 Y( |+ l+ ~2 V6 p5 c

' Y7 u! M4 g% v3 x j; j7 o

3 ^( c+ v- t. Z1 ~/ D  : }' K ^9 J8 t5 E

$ C( n% I7 a9 B% L3 r$ D) x8 {

* V* {0 \/ E7 c: J 对应着avatar.inc.php代码如下:+ }& [( _3 ~8 o

8 ~% r" S/ r' E

# |$ o. @* {3 I+ p/ v9 M <?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) {+ @( \* K5 g. {; E& d

% d8 q9 N* S7 R- j: v* B* ~; ^

5 `' t5 j1 j9 D- s9 X' Z9 l7 y' @' I     case 'upload':9 O$ b# H, o4 t5 l

9 Q8 b1 c+ S9 x- [: U6 o& Q

& \* B) ?2 ]1 [, z4 }, P6 x         if(!$_FILES['file']['size']) {, I- G5 b! M) j5 t( [& V- ^4 G

3 r: Z" `2 Q1 p! C

3 w- X: H$ {/ L) V' j; V1 V             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); + Q0 B/ k- X& p$ A+ ~7 a; {

7 y3 x. v' ]7 o9 ^% t2 k

2 F+ U, B# S2 D$ r$ q2 V$ a             exit('{"error":1,"message":"Error FILE"}'); 9 w- O6 a$ ^& a

8 p( d7 m$ r7 Y. N4 A+ N3 L

* r! w4 Y5 ^8 ?3 H0 s' w9 b         } " i9 B, s0 h% @

# Y" C2 y Q+ L, D% a+ Y' P" U

" q# L- J' O2 `1 ?; v- F2 g         require DT_ROOT.'/include/upload.class.php'; 5 I, M/ F* ^7 X* |8 V" ~, }4 a

: ~9 t0 o- }* D8 K' Y' R# b

$ L. F4 G0 u0 c+ I6 [' \  - y8 i( o! ^: g

: }3 B# }3 W+ B+ P) S5 w

- U4 c! N# O' u         $ext = file_ext($_FILES['file']['name']);% A8 @/ A! k. b7 O. I' g

' F+ d: P. Z9 z6 g7 s, N. y

# s) [! d3 k3 S/ p0 W1 ]         $name = 'avatar'.$_userid.'.'.$ext;5 Y! l c7 S6 t5 G2 G

~) {: r- `" W+ b! L7 U% b

) f3 |9 V% b- V4 ~5 K6 @# o         $file = DT_ROOT.'/file/temp/'.$name;+ X* {6 S' ?9 Z0 |/ u( w; o

% r3 P& \6 K& N6 }/ ^1 ^9 e

+ S! ^3 M6 E/ c2 g0 \1 v3 |2 I   W! S0 p/ R' A# i/ F

# q; j, O' Y u. E; Q0 o; |' \2 o9 a

+ f- X3 c# ~& s5 Y2 X9 c         if(is_file($file)) file_del($file); 2 L9 d- [3 O% j3 j* r5 H V: l. s

( u" P, s2 j% E. J3 K$ K

" }8 s( A }7 I+ t$ U         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');" F' K0 }2 A% p

, p$ H$ @; d6 ~, h8 P

3 q# Q( M! z9 f9 I  0 O+ O. ]1 `$ `$ Y: ]

, Q) I& c" z4 a& P9 h

7 |) Z4 z: H7 Q5 n. y0 v, e# H# F         $upload->adduserid = false;6 p6 i5 [5 h+ _" x

, f1 p% ~5 r3 a* J9 f, j

3 V# i: O' R' V) ~  3 P0 H- X* o1 N/ `0 Y' S: A

' d3 V# U4 t% d' \( \# H: y- l. `

% F/ i2 k+ f- D6 t( N" K         if($upload->save()) { : X: _; f* S1 |' Z: r4 _* [

6 U: z( m/ R8 m1 H& |( M! Z L

$ ?: [$ R4 _4 Z- n6 f# F6 D             ... , h% v! J9 d3 x- Z3 K, D3 b% V

) X9 m# g1 x& J, w; w! M* y

5 k% j0 y% K' l/ d/ P4 g" T) t' J         } else {9 u0 L) t$ o* Z3 m' G2 f+ \! {

2 j% F8 ~2 G- Q. [

! l' `0 [- n6 b. k$ M             ... ! p: @8 O% \1 c0 d& N$ \

5 K8 s, a0 ?( z F8 J

% h; M0 g% J Y8 \0 f1 A         } % f4 v1 i4 s2 @* Q

' _1 T. ]; X" \) ], h" W

" O8 f! Q8 F+ S     break;' m! }) \# x* _# M4 x. p

; R0 _* Y! D r" ]' o9 I0 f

0 r$ k% u1 M8 n 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 $ K2 e. L& Y7 ?8 |

0 i# {" {+ M9 D& O0 s/ @) _+ z0 f

: x4 h. d5 [3 F& a9 K9 W7 k upload对象构造函数如下,include/upload.class.php:25:2 X3 e2 |3 ]# P* h" K

9 l! c) q/ g h& i1 p

& Q, o, S' S, G, |& m6 _2 D6 F <?phpclass upload {+ M" H1 A F0 D. x

6 J; R0 D& C, m( `! w" o) F( f; w

: J8 |" G- S# p9 l+ N, C     function __construct($_file, $savepath, $savename = '', $fileformat = '') { 3 j: ` F8 d* c6 j( W# l- N

7 w2 [3 O% \9 C) q* p

- L. l( g: e ^1 y         global $DT, $_userid;1 y2 R$ _- d' F/ z4 e% {# Q

2 r2 K0 M% O( L) j3 Y

( K2 b3 _6 W& y, \7 m         foreach($_file as $file) { & u& P/ c% T7 l+ b7 q3 {5 d, N% v0 |

1 Z: J4 [8 i: s! I! w1 G5 R( @

0 [! a0 b3 n" ~0 d             $this->file = $file['tmp_name'];) G9 o& V7 y3 O# ]. z" G9 o" z

9 _, D; F8 ?3 g0 K! @% s

" ~/ s; }" {1 B' t2 ~. D/ y7 M             $this->file_name = $file['name'];1 H" s) ~5 S; ?

) j" {4 C' X/ P. E9 O9 s* G

8 Y2 U% a2 p) E6 k             $this->file_size = $file['size']; 9 z8 u- o* v7 I6 z+ G% i

0 ~5 y( G/ c4 ^1 I. o

& I P# K& J* J3 Q& ^             $this->file_type = $file['type']; 5 z+ y+ R) k9 v; d/ ?0 O* B2 @

1 }0 i3 d% A0 h* n7 t7 s9 ~/ Q

g& L5 {$ M( H5 [7 [3 [             $this->file_error = $file['error']; 5 K3 T& H) y2 N; Y

5 e2 i+ k: g* g# ]* o9 }& z

& g7 S9 g/ q& K- ]4 {  + ~# _8 J( q9 g. }& o

/ D# R% r" t$ V) i8 e7 x4 q

' C" W7 u% L: l$ N* A         }! q, @! n Q4 P0 ]% o+ f

9 K& q9 B* p5 N4 }' v1 n8 v6 P

+ S5 I% X: ~8 P- v7 S         $this->userid = $_userid;# [5 ^2 m" \7 I9 P* V6 C

; e8 W3 | A7 q! R8 L f

9 Y& \& c9 G$ `1 e         $this->ext = file_ext($this->file_name); 4 B6 e1 T! W( h+ w+ y

, p( g1 L7 {4 T. A: e1 a

& g; K4 q$ B& d* r$ m         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];, q1 X+ s$ P0 v' K$ s! G5 {. \+ l

, z3 G, z! P3 a2 k+ z3 u7 r1 |3 T

/ i n; [. c q( E$ n/ x* {( U         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;3 r7 @* ]* \2 ` D7 P8 S

k3 r+ i* ^, H3 F( v3 E; x2 c

- l, S2 }7 Q' [- h' _9 u# j         $this->savepath = $savepath;# c$ f& C6 @$ ~/ d# R. i

7 h' {% r% Z6 W7 t0 z

6 g! @2 }- A" j6 K         $this->savename = $savename; : a3 z6 Y4 Q& j) V, w2 A% H8 g

, L3 b" a" L. w/ V* X8 ~6 O

9 T" w5 R$ E( F" M     }}! P) `; O/ c' Q8 ~, I3 O& a

/ M6 }* _# Q# B; Z& a

9 ^5 ]6 B2 E6 O7 i' _ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 2 D. ?0 w6 a$ @$ |3 i9 ]" z" R

, `: x# e+ o+ X; C3 W

3 A6 T, V7 B" B 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 0 p+ y$ o1 k( Y1 N

4 y& `6 |. e/ C

$ M% c, f; t6 v$ r# M, \& s $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 ) n; Z4 E" Q+ G7 t. p9 Z9 ?; I

5 I6 c6 _, t" J

0 `) N3 u R" f, e s; _ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:2 t. H0 U7 d. ~! T- M

) { p# p. F) L# v9 I8 m

- k, t% v0 h9 q* V) D* i! x   * b" ] R" O5 X3 u$ R% K

2 G8 H4 c9 o Y* v+ M5 W: X3 c

7 o- J5 s* P* ^. F( l* U 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:5 e4 k/ n6 l. g

) H. Y* K; x. n* |0 J3 E m; W

( V/ z8 g' L y3 h) v <?phpclass upload {/ s" X1 N2 V) i2 i0 |

* t- Z* P* [$ W2 D5 Z! P. R

. ?9 f6 b! R; E2 Q     function save() {, F$ U1 Z0 Y; X7 \, v9 V

. I3 o R. a0 x8 I3 ?; [+ l! D+ g

) a& b2 l7 k! r1 h         include load('include.lang');4 Z% m$ V c1 x2 }

1 U5 l: [) B" c) ?: t+ F: o0 b

- V; H* u6 e, s, Z5 j1 A         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' X7 e; P, T* V

; [8 m3 \9 d4 M

& }' B' U3 L0 E. y2 k1 i5 _# Z   ) ?' v/ `+ B; L

+ Y: G! i- Q: Y$ \3 L3 ]

6 D, M: K7 Y @1 c$ V; o- {2 v         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');1 S! s/ M7 l5 ~1 w

. @+ p9 g6 G8 x/ ~. Y, y" d

# L% \) Z" N: @, v3 b  . q2 D3 ]; r, O l8 x

; t; ?! x& y9 F& e

9 f% q) O2 T+ ~6 X- C         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); : d. g8 t4 `& u) m% ^, l

* s6 Y4 E6 T/ ~ k- [2 @( }( j

4 |, u* B8 O2 D: M8 E! `' f: L  : ~* E4 _4 C# F0 x% O+ P

4 J4 l. o0 {- Z! J) D& s$ O# N

9 m( T8 w- n6 C' R         $this->set_savepath($this->savepath); " s' O" _& f+ C. `

% M$ _3 G P. |# }* _1 V

4 c0 ]& A; j6 X; s- m. g2 b# P         $this->set_savename($this->savename); 2 z- R k4 h0 d) X3 y4 V1 g

: ]* y" w3 e9 X) W6 K

2 T2 g9 f# I' g) l: e$ n+ S4 x   : U7 b9 x/ M, ~% F/ `$ ], ]

8 u% @! ?( a( Z2 v, c

1 R5 k$ A) i* [( v' |! e# d d         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);5 a9 e9 O' |) W! Y) {& Z" Z, |

6 B$ u7 n! ~8 I1 B* ^

7 |- _! r4 g- G# k6 K I/ o         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); $ x) ^( L* U0 Q* u

0 }7 c- t( |5 D4 u, J i7 o- N1 u

; v/ F" t5 g- Y: [ H. E" N         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);3 G! t/ g; l" p6 U7 {

, C+ ?& E* w- B8 \

' ~5 c' i( }0 N1 [' [* H- r# _   $ P/ h* G* E1 w) W' E4 b9 h

. J8 y- K+ F: `5 ]

* Y( |+ d1 O# s6 Z5 B         $this->image = $this->is_image();' k/ [2 X' n1 ?/ A! \; B7 z7 [

4 M: Z8 P& x1 O" Q! r

* R: L! p; z/ |- X         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);' T9 c3 J+ [2 r9 P

4 y" ]: ]) i8 M( G6 S: l# S

: X, |! C& W+ j: v, e8 e4 D8 S4 R         return true; 8 x0 V0 F* |1 R/ g

' D% y% r: Z* U

. R# E& I, R$ ?; J. P& S8 l l     }} ; v' p _) u: Z" r& j

# w6 E( @8 c1 X8 J

) B. @+ s4 |! F3 E1 }% j. t 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 2 q7 E2 Y* Y3 J, V% n5 C+ @9 r

# x8 e/ z) s1 w% W! V

4 s0 U. N% j2 W9 D7 p% |9 s8 A <?php8 X) C/ Z$ o8 k( _7 I" y! q5 }* ^

- G6 n' \+ v1 k# x: t) }

7 d. S K( p% H) X& a0 P z$ g     function is_allow() {( a3 b0 |0 @6 c3 s7 v4 b; k8 r3 b

8 M4 T1 F3 Z* w

. D* N3 _3 y/ Y1 H/ ?         if(!$this->fileformat) return false; 5 u$ S' `) ]& d

1 ~+ y0 h' F1 Z: v) A

& h1 U: h* z2 R         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. X9 p4 s) H8 _ ?, F5 Y1 E5 H! v

6 w! y& N* S8 c- J

2 b( h2 I/ T! Y( J% H M" `( Y) r         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 S/ _4 j t7 t1 Q( Y! P9 c

( k+ J8 E; T; D" t

$ L2 [$ P: G4 m E( A         return true; ; q; {4 _! y* E3 y5 U+ [

! Q5 t4 O: S, d/ ^. g; A

) J8 I5 q- ]: U; Y     } 7 k8 s9 x9 |' t' V q8 v D

7 m3 y( b1 R" t3 U# K' R

; \% J1 v3 Q/ ]" B: V8 p 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 & `& t' k4 f5 o% n

' O$ k+ L& F8 e9 z8 ]1 s Y

+ a- n3 P6 I ?# A 接着会进行真正的保存。通过$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文件。 ( {, G# {# s; D8 U3 T

5 s6 j+ c( e1 Y+ ]6 }

5 u/ E4 u* b" s 漏洞利用 ! l+ O o& y" I6 \

, b+ g/ y$ h" B/ Z4 j' K1 L

0 v; M" X6 X* e( T3 ^/ @- x' @ 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。( L0 F1 u7 i3 z) q1 v" a

' e& N" L$ I# r3 A( e. R

; ]! [: w( [, k% M# }- w: Y   9 t ]" O" X$ [

* m) O# U& |6 W" O0 H

$ H$ S* O1 }8 J0 M 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid0 R2 S$ x) v$ e. W2 Q

+ j1 r4 O. s$ S+ N

7 p. M" V% P7 w, m2 Y R 不过实际利用上会有一定的限制。 3 ]2 T1 X/ S% |! k

2 g6 C3 N% a9 u6 s; H4 T

" ~; e% b; U) E& [" h* p, ^ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 ( M- T4 g' N) m( h. f9 U; o

( Q7 t1 b. K) C# ~

5 M, z& T$ B7 E  % F" Q6 z1 Q% b: ~3 i7 S" e3 ~( H- e

, }/ n- w" {7 W* i

9 q; h+ g9 S5 g 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:: s4 q* ~; y' e! g

. W- ~6 p: K$ E9 Y6 M

$ g; x- w6 H. H& D: k 省略...$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]);省略...' v4 ?* O9 H' A; M" ?+ R

$ s3 i, ^: R5 F: F$ u8 |; t

" a0 m5 a7 M1 |* ]! x3 p: _$ h* e 因此要利用成功就需要条件竞争了。0 M7 ~8 u) f7 L! ^" L9 j- x. g

; U1 |( v" k1 B) |3 m' r5 n5 x! J

- b" N7 Z; [. Q( H7 a& `5 f, { 补丁分析 . n2 p, N% W+ F* n; W1 h e5 w

1 q4 C+ E$ a8 p. `

- f+ ~9 Q) ?" O' Y5 A+ _  / Y3 o# v, j+ V# F

( W& X7 `6 z O7 a& M

! H: u( T$ g7 ` 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:7 e9 Y+ f- }0 j, V+ J8 E8 {( D! e( J

j* b- K, i: I+ `& J. F

4 d) Z- }0 ]. e function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ' @1 c/ {# w. }2 d2 y6 C+ z

+ o' ?- v9 a4 F& `. I

; O) V% o; H$ ^0 M   % s( `- H: D8 _4 F0 e% _/ \

1 B( X3 ]& m) h

) d+ O9 g/ w' w" C6 @ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。5 n8 w; \* ]2 g5 Z! A

9 T8 r& y5 V* t+ J5 t5 r( T: x$ A

- g& i- |$ ^- [3 S% Y 在is_allow()中增加对$this->savename的二次检查。 $ w3 t8 e2 y4 |3 o3 Z, @3 [1 {9 a/ E) U

: ^8 G* s/ M6 X2 H5 R$ S

9 U+ L( }! L7 U" o& K 最后 - Y6 s: f2 p5 T6 U+ h: A6 b) p

. D$ |3 {; [3 A% |3 Z+ M

# `- c6 `' a0 |5 B) e 嘛,祝各位大师傅中秋快乐!1 m# W9 W* t& X d- f; k& g4 B

# T; _9 V) f! f, k

8 r8 ]! V- n: j+ ]* J) s( D0 K  + B- m8 r0 i# Z+ M7 P9 \

2 n2 p; H4 R. k8 E3 V- ~& p) t3 i





欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) Powered by Discuz! X3.2