中国网络渗透测试联盟

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

作者: admin    时间: 2018-10-20 20:13
标题: Destoon cms前台getwebshell
; l9 m4 C) X" k

$ W# d7 m& J$ t

" i% \5 F1 x' F: ]0 d9 u# Q5 G* H

5 l& W. a H6 b: s 前言 2 T1 Y5 r8 N! o- l

8 `9 d0 b! @" i7 W, v3 m

9 N5 Z) f1 H' D1 z1 [. r4 I 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。) p4 l4 p( r' i. ?5 x) Y

! n: Z2 t& i+ i$ q5 A

1 J. L9 X% _" G  % Z) G4 [* P7 u# a0 ]: `7 Q

7 l0 Z* {' z" i7 ~

% R, C- |7 ^; ^; @$ X 漏洞分析' X, e4 @0 m! [6 G6 X

& V6 ^% W/ p1 ]: y; Z; X# i" g

# W9 \8 T9 _% R 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: - ]" b% D8 s' n: }1 L7 N" ^" j9 X

* X" u' I( A4 V9 D2 F% U, Z7 b! F$ T

9 u8 l, H0 O7 w5 D3 Z' w  9 Z" {4 u6 \) I3 l0 H8 w, j

" K8 l/ ^" I! F1 ?

8 u" ^/ V7 A1 ^! |( w' L 对应着avatar.inc.php代码如下:: [5 Z; s! f& n; {: Y! z/ E0 S. U

& s# {& z/ ^9 |( w$ ]" B$ _

$ V! v0 r4 T( h3 A0 L# ] <?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) {" y! y8 I& w- M4 i+ |; B; Q3 i( ^

a* N" R2 u! X% t

: C6 J7 X: `! p; V! j. k( q& e, ~* c4 B     case 'upload': * I* a2 V% J4 H* W

- u* p2 I( Z+ b3 H% z

( s9 @: j) r0 R! Z         if(!$_FILES['file']['size']) { ) f4 J) l" n- n& T& \

- G! ^' S. T3 a( ~! \0 q& i

8 w9 I& a$ [* L4 p2 |0 K             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);2 [$ v3 R9 Y C0 D& y) s

3 V, k1 L" A6 z% [" z! c# Y

/ E9 t w, X# U8 L8 s' l S             exit('{"error":1,"message":"Error FILE"}');' X; T z6 W2 N; e7 M

) t2 U$ K J9 a' q2 Y) J

8 S6 a% A" r9 L6 W# M         }4 T4 f( P: }! j) `2 O

1 K: q9 m8 O6 @6 w! ^/ X

$ K# b+ ^; _5 |4 U6 N9 J         require DT_ROOT.'/include/upload.class.php';$ d+ F/ o6 o- Q* o' N( W/ j: ^ f

4 T# S" o* D2 E- I( D& f" r

1 `/ p! U' G/ Q   a+ v8 W) G, h& L, z6 a7 c+ v# N

/ d$ r# \1 ]& D

" n/ ~, q" q- j" i         $ext = file_ext($_FILES['file']['name']);, [5 T& K, p" {* n, E0 C ]

( A4 l( O! P3 G& d3 N- P; q

; B+ j7 m8 z3 V; Z+ f" w0 f         $name = 'avatar'.$_userid.'.'.$ext;7 ^ [% m& K& s

n* k% n- D6 M

0 j0 V0 ~7 @& M         $file = DT_ROOT.'/file/temp/'.$name; 7 _2 O5 a; o5 b4 Q

E4 V7 {* o. \

5 U; s2 E8 s. G5 t( @6 N; _  8 ~, J( m, Y& q5 C' H5 e& c

( z9 \; U$ |9 k$ d; [' L

; }0 I5 I7 q, T         if(is_file($file)) file_del($file); ! }! _+ C$ e, a6 f5 E* O

+ Q! F6 H) d9 d8 q

" }, g# E: H3 l8 u         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');0 N# V6 a+ B% P! s$ P

6 ?% j) |* l$ k( x) }

" R! F% s; y: w a& {  5 f8 W$ S! t: _ i. E0 w9 p

+ o6 d0 T: l- P ~. s$ B* L5 N

' g) J6 H7 Z6 ?5 W6 D3 F         $upload->adduserid = false;" U- @' h9 N L W2 ?5 s0 m$ z

/ [, W, u }( ~- y& W$ q

' z; U5 x% j0 |" h   ! l! G/ S. A7 h; v( ]6 X* H; F: Z5 V

9 g5 q2 \; h c/ ^/ Q3 V& {

& t) k! l5 o _5 o3 d' p         if($upload->save()) {' _& [/ y$ x6 N4 r0 ]

& G# q5 L& F& {# F) R6 h

1 ]2 C; y+ p% R) n) S' _( e, `6 o             ... & f& g( G6 z! r! X+ Q6 _- r

8 ?9 J' M L4 W/ A) A/ I2 u

. O, b6 G7 @5 T S         } else { & T) y1 z0 _5 H6 e! D

4 d* e7 U8 ~2 T X. j4 I

* H3 b1 Z4 d" }( D; c9 p% Y             ... , j2 @9 Y, P' r4 E! |

5 S8 s. t' y7 M& X Z/ j

8 e) C: U% ~2 Q1 o9 a7 Q* o) O         }1 k5 G5 b7 S9 \

* u" P3 r/ `9 K2 g4 S$ P

+ N. q: }7 G3 p" J7 j- u     break; * b6 {' l/ H" k& l/ N, ]& j K

, G+ {3 t$ I) V

9 M0 s; a8 S w$ ~/ s9 \ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 1 p+ w9 S" r7 }: {/ L5 V

& Q# d! i& N, `. J g" l! S3 v3 R

. U4 F% f, l' m upload对象构造函数如下,include/upload.class.php:25:' l/ s& Y2 Z+ f! @ d4 W

3 K% L& a9 B! G* e

; ~9 E5 O' j3 c8 [$ K; R <?phpclass upload {- \4 a1 V, D+ x+ F, z! X0 z- r

8 C. a) y# l/ o4 s

$ Z3 h: N x3 d1 p     function __construct($_file, $savepath, $savename = '', $fileformat = '') { " h' k. _. A) O* ?, R1 d

! L$ ^" a2 o! s+ M+ M0 r& \

3 p% ?! |- K; T6 x1 _2 g         global $DT, $_userid;; W: s6 m* _6 e4 e

+ R. x* D! D0 v @ }

8 ]. m, K* _0 o+ T7 ?6 _. q         foreach($_file as $file) { : F1 D3 @( H3 \* j/ m4 P

6 ^5 V; u/ \% y5 ~' S) v+ c

6 ]* m4 m! f- b1 R G* Q" t2 Z/ `- @             $this->file = $file['tmp_name']; ) Q& g/ k6 `+ G/ l! U0 P' B& Y

) O0 n+ l+ \& u& U

2 I Q- x; H7 p. C6 L( G4 X. O             $this->file_name = $file['name']; - A) q8 B8 z. a z2 F6 @) i% ^

/ n2 t% N& Y, s7 F$ {% c

5 u; e6 Y7 _7 X& Y- ], ]             $this->file_size = $file['size'];8 S: Z+ R* Z6 w, m

& A6 j* _7 T: l! D

* i$ L* O) B3 O+ s' k. U! b9 {2 d             $this->file_type = $file['type']; 5 j h& T6 x+ b& r% q

, l2 b2 n! Z' V! _

& N$ r; {% s' Z6 W             $this->file_error = $file['error']; 7 M3 X2 f1 Z+ q+ d) H2 x

$ x5 R2 U+ J3 k. ]' c( r9 t$ H

1 @" e! D8 D# |' _# j) `  5 Z5 P3 i$ A' E V; g. Y" M

5 `/ G0 _* A4 r/ F& ]

0 r4 \6 l/ I% |2 j2 K/ ~         }" P4 R# W; p0 V: Q4 ~+ t* w+ u

2 ^- O8 C7 [# h9 k5 r" ]5 f J! l

9 p5 h7 @- b% K8 t2 g; j0 Z2 P* D         $this->userid = $_userid; 8 b, v0 H8 v3 g( T+ ~- Y9 y

) o# ^1 W% Q% N/ n9 ?* e" @ Q9 \

: N" v$ X# P( b8 b* ^/ X         $this->ext = file_ext($this->file_name); % M( L. _. P: r: F0 \3 h& z# \

+ J% }" ]+ ~$ N7 p4 d! x/ O2 S. f

6 P0 x7 }6 h8 Y2 U         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];. P6 L* J" L2 s* r

3 A: \% y1 V7 T* ^, y: E4 S9 y

) Y. K' |3 T, c( w( D# d$ @) r         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; . [" E0 X a9 H" i, f

- L% x8 ^( F7 [! o& S' _: e2 V

, J" Y3 T% a. N) f2 M4 a& Y         $this->savepath = $savepath; \7 e: C0 t# f9 n: Z

$ G5 K7 h! Z' J- p: {- t

: G8 c4 A4 A5 F( x. C         $this->savename = $savename;( X, O4 h# z3 _" @ d1 N$ o

& R1 c4 g" U( O0 m+ \: }

& v" X8 s7 t! a6 |$ m5 R5 Q     }} . w5 X# }+ G* P( E9 w: t6 l n5 J

( {. o9 Y' S a" j' w

, B& r; _7 Y$ x/ Z. h' W; ~ r' s 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 5 F/ S1 E V' b7 ~% ?

0 d/ ]4 A$ A1 d( U5 X: K

/ ^; u1 t+ @+ z3 R" J% s" M 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 " w0 O2 m# ~, O2 B0 [+ @( C

7 A7 p# i, O; t' ] L" }5 i& }$ K

5 ~; ~! N2 g$ s: ]$ ~3 }2 J( x $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$ ?- r9 X! e6 m& k K

% k: g$ q2 L9 X1 V& Y) U+ _

! p. `) u" u w# e6 N( ~" z. g, ]* ^ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:1 Z0 c' z! f& g5 V a) _7 y3 N" n

% S: h" ~0 r% c' j$ o! H) ^, S

2 m- p0 E4 v1 i7 L  8 o: P; N4 } Y, {

1 D# Y5 }0 m/ R8 z8 q

% }. u6 ^$ ]" }; d: m( G/ G8 Q 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: $ Y: p4 d8 \6 f2 w

$ G4 a9 B8 b- K- w- T' N6 j( v" N% }3 `

7 {1 W S6 h9 l+ N <?phpclass upload {7 A: i4 X4 M$ h- p' B

7 `% H0 e( U& C9 ]* B. @

2 Q9 j9 V- a' e/ v: T     function save() { 6 g, O) J6 R# {' j( U

' ?# n: Z# c% n$ K6 m: U" ?# G

. I2 c* M) F/ q8 }3 p         include load('include.lang'); ! o# u" A. D r0 P/ F& t' S

" \8 {* A1 p& J; K4 y/ b

) u% c% A! Y0 {6 j! Z3 i         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); 9 Y0 v1 }- l/ Q9 I% b

! N. U% h9 v& ^& g& c1 z

$ Z) g3 q1 Z0 x& P* L* E& i) {% ]   + l3 L" R) S8 N# [) A5 r

! I% n9 c6 X. F& w

: j9 `* I' S" J. Y3 d         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');- F: b. Z) M( L$ ~7 ^& w3 p

3 \% _6 Z, w8 w' c( F+ g4 G

$ @: }* }2 X% z/ o3 ]  * k% f% }3 k. k

: W$ @6 j9 v: L/ [* V7 P, H7 D

! w+ c' y" L7 T! b         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);" ~5 Z5 x& N7 R0 F" k

- J% V3 L# W2 K$ H

1 Y% x5 I! N+ s* }1 q. [  4 W$ B/ A% {- \5 _ O8 L! i

1 L; @! u" u: i

% q6 L5 `$ Y. X+ A         $this->set_savepath($this->savepath);9 }3 o5 d3 R; T; F; G* y, x) W. b& k

1 a1 w$ F8 z2 F9 k1 O; x* T( W

$ S: z7 n7 G* g; p! M         $this->set_savename($this->savename); ( a; Y- ?6 `1 g. S0 K& o F$ @9 k

1 ~1 y; Q* T0 ]9 s( a& d

3 i% k2 @, W8 @1 f+ l  0 n( Y8 \3 b% R: G& O8 }% ^8 [6 F

. h7 l( X+ H. _: p* N

5 l7 F* Y" n( r3 U- l         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);, G' C" J0 w1 r: s% X

9 E# g1 j, X* E2 g8 L

8 G1 y4 [( l$ I0 ]7 b         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) x9 l5 `: x; L+ J

5 f7 E- O ]9 g$ p

- W) a {3 C b: Z5 A$ w% X. w         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 7 k" O" ~; V3 y; f1 c

& ?6 {/ ]% L$ u/ I

% Z- C4 f2 `8 P7 q! g3 O# J K$ h   $ `9 W+ d/ g: S# T( U; t# l! c2 l9 @

) t7 V3 e' H( e2 x0 K

# L+ y: c- V& I7 x4 A         $this->image = $this->is_image();% H0 _: T) b$ o

) f% ^- p' ^/ ^/ O, J2 J

$ t V2 ]- t! m: h Z7 L- n1 O5 T         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 5 J M4 I/ q7 k+ [! D# T

& z* a1 ^, \0 ?8 [& `; M8 i

& G+ i1 I6 |7 \9 j0 i1 h# {2 }         return true; 2 o- d2 ^& W1 c3 b

6 h8 x+ T L1 c% Y& ^5 I" z

' X- x( ^! f5 ?, A; a( r     }}# O( f: ], J& B% P" R9 U) l+ r

8 T4 z$ x' X7 y0 C: L

9 c0 @& W9 n; z/ v; ~4 ?. } 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: 8 d. y D. Z: ^* N. I9 `

X) X7 z& Q3 O9 e

. U$ s4 m: Y- c <?php8 I+ E7 k9 s$ t3 d- b, [ Q

* R+ c2 N8 ~: P4 X9 P# w4 f9 ?/ C

/ c6 G# p2 Q( N4 w) M     function is_allow() { . t5 K. O9 w1 A; R S

2 [' u3 z" q2 f$ S, k, `

- j4 d2 A' t- s! U: j         if(!$this->fileformat) return false; 1 B8 m' p( |3 }, Q

5 L4 E8 m9 G( F* ]' p2 ^* h, d

; G0 M Z3 g' N4 F         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;( B8 L$ v- X" i' E

) l! d5 A" W1 A: p) z1 X+ y# i

. V: S' P/ Q. z; J7 i6 Y9 i( f         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;( L; m3 C8 Q9 V* u' A: f: n5 J

( f1 z5 O+ L" U+ z S7 N8 y

5 q% t i" z7 W+ ?         return true; 5 T6 C- G w5 Z9 e% L+ K% Z

. ]: E- L. l: d( T# b

8 J! P, Y6 |/ p" X     }6 N i7 X* \% B

( j1 U; E* L, d( m

4 e/ Y! a- o( Z! Y& S1 J 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。% N5 Z3 R9 w/ O5 d

5 k1 n7 a) L% ]9 v4 S7 |: B4 m# T

: T& W: x3 T* q/ l: D6 X, I 接着会进行真正的保存。通过$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文件。 ! q8 ]- g/ t" P r- g5 Z- Z

! z' v2 l' w2 A( F R3 o

0 {+ \- g6 @0 D6 h 漏洞利用5 d* g9 p- B0 y7 e

- s8 x$ M, H0 h) ^

% A- `! O9 _) ^. U$ Z- P' v; S1 O 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。1 t1 L) R& o; [( ~

9 }& e* e8 D V5 Q! Q3 V# ^

" p: g4 x ]3 G( W2 r) B* h! Q   - f. c+ ~8 F2 a1 h _! c

& D6 ?# B3 J' G2 W- ?% _4 J0 R5 M, n

& y3 g c7 d) N 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid8 m/ F. u0 Q9 t' B# C1 o$ ]

t2 J1 x- i i, e4 v+ N5 y

* r6 I* I6 Q+ u5 V# y; J M5 F8 v 不过实际利用上会有一定的限制。 ! J9 W# U' U; e0 ?/ F

& m* Q9 i/ O& b" Z% z+ |

5 i: I8 c/ E$ n% {2 F* y 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 7 b8 F' y! R3 Y# n; L+ ~

/ T" s% F# x; E$ ?1 B; q$ K

3 l8 @, v, l- V  3 g+ J t' v$ n3 p) I

# g0 }! ~' f9 W! O% @ u! |& W$ P

, u0 `" Z. m3 [6 u 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: / W, b5 n5 G; Z( v. z) D0 ~1 h

# w: ~+ v. N/ h) s

# ~1 z& H* R* U0 } 省略...$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]);省略... , a" K+ u% ^" e) g0 F# Z, L

; G8 F+ @$ R- @% w- H- @/ ?

2 m$ }. O4 ~; E( }& n! z9 ~6 g 因此要利用成功就需要条件竞争了。& x N% H9 p% }/ D

3 Q6 r& o! W3 Z' G: x

p- v9 Z1 h' i6 N# r 补丁分析6 i: \2 D* R ^0 _, Y# n" l1 ]

) g% r; K6 G- z/ t* |$ @8 A: D1 F/ v

) O! D7 }) t7 R* B9 a M  3 E2 L, S$ e6 T. `

4 Q0 ]8 a0 V2 F

* \5 f; v* q- n 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:4 {1 u* i4 |* I+ ]) ~- Q2 C. _

I4 s1 p3 x) e! c

' |( @" D3 H7 |0 ~ h1 ^ E; ~ function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} 3 C B. x! T7 b4 n( A d

6 l9 ] l9 [! c2 q5 U, M

' ?7 ~* q. t2 N. j7 S# m  ! |" D# H$ Q a/ C

?3 x) |+ P9 j( b

6 U2 J# |7 d- V# X. G, ? 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。& x% c+ ?& @! `* x; N6 x2 X

. F/ x9 w9 Y+ D; j

/ r) a( l. f! n( Z3 e8 T 在is_allow()中增加对$this->savename的二次检查。 : f% |* ^" X( h3 ~

3 V! S; e, s$ _- e

5 Q1 G' ]$ P2 C n9 x+ @4 e 最后 ( f% m: T7 B/ j! y; e

5 v+ }# W7 @& q# g8 [) q

$ ~7 ?0 X9 ] p& A* o 嘛,祝各位大师傅中秋快乐! ! ~, H9 l6 F1 ~: x/ x7 t, t9 r7 Z& b

+ j0 o. p; H$ [

, f( Y2 K7 _8 y8 w f( d  2 J9 P- [" u8 q$ K

5 P$ _# l* }- J( K5 j" B" W





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