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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
% J7 n, o5 u [9 `% K; z

& _2 {. S/ V# ~* G; {% G J, o* k. c! e4 h

. k0 T+ u9 {* Y

x2 e; }+ O5 q+ q 前言% z) V- ~, q6 f; X. q

8 c, {' M: ]9 L5 D! F; j9 ~

# q! e" n" E% W- \ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。# A/ K3 I" n' E! Z9 s" t

/ z2 ~1 ~" I l

5 I" a$ i" ~4 e a' ]* o: I   " k2 i- x- q7 _5 o) T% @

5 a$ r' v% A7 o' @& r4 |' |! Y

. {0 T; `5 J$ G; E 漏洞分析 5 j: ?- D4 d. B" ^6 B/ T

; R% R# m6 n3 n

$ A3 k) r* i T3 v* g( [$ j( h 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:' i8 b/ o! L7 ` x/ y

* s7 J/ @9 D' } g: ~4 B& u5 _

' ]5 Q$ E1 a( e' Y( W2 @   8 I% Z+ J/ |5 J1 _- }

. o L e' H+ T4 Y7 \( {) n& ~

3 C9 q) O/ x# B7 G" b 对应着avatar.inc.php代码如下: 0 R, r+ Z, r9 ^. c. d* i2 k3 a/ F

' L5 _: Q. H8 w v5 f

; w; N B: o7 Y8 M1 T <?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) { 7 R- B# Q1 c# y8 F: A8 j

- o% H9 r, b4 ~ k

& K- H' z+ H' R! U7 E' K( O1 T     case 'upload':* y; T7 Q1 t( ]. {/ A, [% O

- U4 Z6 E% m& h! q; i T

& g* x- a Y. v$ p         if(!$_FILES['file']['size']) {: z# ` y1 |0 C. T1 Z0 W4 d+ {$ @

6 p4 F5 l/ u' E" q% a

) u$ {7 X: Y* q" P' ?& A             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);2 \$ y+ ~% a2 a, a7 I

/ N8 ~4 O" z c" d

4 [5 o) J! n3 f0 ?+ j' x- p2 B$ s             exit('{"error":1,"message":"Error FILE"}'); % n8 I$ Q4 Y) i/ I

1 }1 A0 B) P* A$ X

% |% z, m' E2 e3 Y- @; D         } 3 I* p0 W% q9 ] Z+ y% L

$ K$ b X' f5 X. M

9 B: Y. O, F8 D# |         require DT_ROOT.'/include/upload.class.php';3 V+ f. f- i4 j" c) e

8 l1 X Q' r! j0 [7 o1 [$ N

/ ?8 Y' E( Y! f M  0 f0 i6 a( k U1 [: L% c7 C

( c- m$ X- a* ?$ K4 ]! v* \- Y! F

* j' A; }& g0 Z: ?5 e( ~+ d+ ^; l5 E         $ext = file_ext($_FILES['file']['name']);. r5 G* \3 W* v4 o) Q0 m- G

8 o. N0 P9 {! w* l% j8 k

2 x2 x7 r3 Y& j- n         $name = 'avatar'.$_userid.'.'.$ext;; \7 v' L/ R- r% E- n& T+ L7 d% |

9 v8 L- x; W* A; o/ q$ J

" c, n. W6 l- o" x/ v6 ^# w6 v         $file = DT_ROOT.'/file/temp/'.$name; W. v- R2 [( W( M) H* B& @0 M

7 L* B; U* _, |7 a8 }

; v! ~6 l R+ m( Y5 t) w& F  ; i+ B) S c! P. X# l9 I

% R! A' S' B1 L9 S

$ l( G- Z, R0 A% ~6 y$ W         if(is_file($file)) file_del($file); 0 W( _8 O. }+ p5 j6 g$ P

" L$ v# R1 _8 e, b+ y

; j3 B; r- y. g4 H+ s# P         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');" Y: }2 ]" Y& O: z" P8 m

# z* `; A6 h5 q! y4 z

1 s3 V. I7 H" U& H% |. u+ O1 U  $ L1 O5 _" Q L. X

& ^( p4 u" d* t1 d

9 z; n. d. m. L" W" n         $upload->adduserid = false;) c! I, H$ h3 |( G

. e q& v+ h: o3 f( _( T

, v" S7 P( ^" k3 N3 T# t) L& W9 q5 j  3 @) x8 p% r- e+ Z Z/ R

4 H0 `5 C0 [0 |8 u* T! W2 @4 W

9 Q4 k' ~. l% W% u         if($upload->save()) {+ b- h! D! x6 ^5 i( e

: s" J4 l) n+ f

G: I s; b3 D1 U8 ~6 @6 a6 [             ... 6 j: k* v) _# D8 G$ b

$ j- ]$ \$ f [& F& L# j% n

6 @. y' Z$ A- y+ R7 f& |         } else { 4 O% n$ `1 r N2 }, G- [: i

3 _/ Y: Q; Z6 K7 _; \) X# A& M

8 k( z) R& t$ Y, y% F             ...8 J; j6 n3 h" ~% Y

3 X- m0 G/ C8 w& V

: Y7 d; y. }! X( Z         } \3 ^1 U! o0 F- z

0 k* a$ P& `) j/ b

q0 [. @. a( B8 e     break;, ^" `) M/ t( ?* q6 m+ F' o

. I$ Q1 R1 m3 `& H7 i2 B

$ }0 ^% D) B4 g( [5 f3 F 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。$ c4 c- L: R$ ~9 a b/ ]: H/ ]

- }( U! k- s, H; {% ^

) f6 {' A, e9 A- Y upload对象构造函数如下,include/upload.class.php:25:% c5 a: B) j4 i( d) u

, w, M4 T6 S2 B/ S7 Z- Y' K( j

6 |- j v( t! r4 Y+ f- W <?phpclass upload {8 ]6 r7 h0 o( I0 W I6 C1 V

* Z, D: J/ u; y2 k2 ^5 ^/ ]1 \

8 v4 u! X1 v* J& z5 P; p" A( R8 e8 e     function __construct($_file, $savepath, $savename = '', $fileformat = '') { / X4 l' |3 b: a L/ \" @

/ D& M5 v" }4 |* ]2 M( [" X

0 p/ j" L' V6 {! k         global $DT, $_userid; & D$ b+ t0 P9 g4 K2 p

' U2 p5 J. d3 H6 _/ L

) j1 Y" X3 J; y4 d/ b8 \         foreach($_file as $file) {9 I/ q8 U, @6 E- }

/ U. u t/ F$ X; T5 {$ t F$ E' O

% S7 u6 x3 E- _$ f+ H             $this->file = $file['tmp_name']; ! F; ~ f3 p+ ]; L C

* \' b1 R: [- _; Q, i9 ~

' U8 s6 J) s) P6 m( ^& h+ M             $this->file_name = $file['name'];4 Y0 S" P) a: V

! k1 U, }# ?' a9 Q: i" @

( |! z, ]) c, B' Y( R" r- g7 L5 [1 Q             $this->file_size = $file['size'];# J2 Y4 Q* v- }' D% X. F$ Z6 i& ^

3 J. k: k; e8 Q* b

8 g& ^; i! A: |& v# e, K             $this->file_type = $file['type'];4 g2 h2 V. c9 s2 n* m

1 Y; u% n9 c4 d+ a: s7 e( g& n6 H

+ [& [$ L6 A; s* b             $this->file_error = $file['error']; 2 T0 i4 B. e- Y* V& }8 k9 U

% e7 |2 }3 T; A) p" G2 P

* F. H5 k8 c* c" ^   7 X. u: R* } h

3 m" z8 F' W6 w0 R" L" Q: r

$ _$ X- N" ]) B1 M% T3 X6 i# F         } 2 t/ q7 B0 F: W

- _# H5 i: b9 [2 z

# |1 o2 V7 r" _- y ]         $this->userid = $_userid;* V$ n) W/ v4 \7 z2 _% |# W

& n/ p3 K& D9 _2 v7 f

' q; Q2 {1 A% q         $this->ext = file_ext($this->file_name);0 u5 L) s. ?- z% m8 }9 n+ A

8 K: e7 C' p, G' I

) v' E$ b0 {$ S         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];& R2 r- G' [% F+ x0 U: K' B" y

% p6 L9 p% X4 O) C1 g ^+ ~

3 | A m' @6 W         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;4 N6 K% R- q- [2 |- Q/ z

9 f) M( t1 C3 s4 L. s

% s% z# A: `6 W; Z! _         $this->savepath = $savepath;; q1 V7 i8 o( i

* \3 n v9 d6 b5 l$ `1 e5 U& z

' W u) E ?6 Y( E. Q         $this->savename = $savename; : d8 |6 d. E8 `# K4 {/ K6 v R4 Y

+ U# A0 j5 l% ]: O+ N* D6 n

9 I' g- i8 S$ S0 \     }} 9 X3 [7 ?/ _: r

# x% d$ Y% V X ^4 O m4 g

( n% U# _' l0 m6 e9 W! M% Z5 _ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 5 w- e! [; H: }* |' y5 e, D# t

2 \6 _# [9 a5 g. L$ y

; c" o+ ]( c9 B7 }. {" N4 D 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 O& k, g+ L: V7 |9 c

4 b( o( r0 P( y5 y

8 l: f/ o; a* G' |' _+ 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 7 h: F8 Y& f3 w: @" ] f) C

. s/ U0 t- z) |

9 t/ W3 s5 _3 E8 N& o4 @ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:; x% d: ]- L2 B" p" U0 c2 e

v5 _1 P/ j: L1 u

# ]3 W% J+ [: M4 C" f  1 m% ]0 Z1 d/ ]% h* z

; w. i% w3 `" [

1 w5 q$ V7 X" F9 ^, C: D 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:9 ^1 i6 O! i+ V% G5 P; q; {$ m

' ?+ r8 y$ ?, ~; F) h

0 Y6 m7 u& ^( S8 o; v# q3 } <?phpclass upload { . L0 l2 c- p$ @7 U- q( `

6 x6 h2 P/ ]5 k1 M, b& b

# y& u; U: L. d1 g; O/ C     function save() { 8 C1 N: l0 P6 t8 I

) R: S/ f! I1 h7 V7 [2 i

% X6 ?. g2 `8 w! {# l         include load('include.lang');3 ?; ]3 b* K, l# Z% d2 \. k0 z

) `* K. Y8 P/ I; K- @3 ^

. V" t% l' V0 f, g# {         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');& b1 [( D0 J \

2 v: z$ Y. n8 h2 k

' H. \% Z4 _+ O3 b1 n   e. C& l; E$ D- H# j8 E

) d; z* X' }* a$ c: Q& w& D

9 g# U" N7 A& s9 D d3 H6 x9 {         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 5 u& b5 N2 R7 n, k

! p, x( \* l- R3 j7 t0 O

; z: k: a% q2 s# ]   / V8 d. ]) Y7 ?; i9 M

, ?+ V- @ r; D! u) A

5 W: ]5 |$ Y" j% x8 Y: l/ X         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 3 X" U0 ?# |2 x4 J- C2 M

4 p% M4 E. Y5 j6 _ E

2 ]- S3 A3 r4 c5 x( `  5 ?3 H" s: G% c4 W8 b6 Q

9 G7 K" w; G/ j* F: f

% @/ ]( ^- L, a! o/ J( k         $this->set_savepath($this->savepath); 1 [1 X0 g: X" S

7 p' a8 e1 j, p f+ ^

- e2 m0 F# p. T( p         $this->set_savename($this->savename);' {1 r2 z+ `. @* w, s m

+ y7 c. e$ _' w3 T

9 B' [& `& W8 H0 W* w  & d! X% C6 I5 f' C" n( B4 H

b8 a: C4 p3 J& e

& R) D0 N% q+ s         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);% R. S2 @) c8 U) m% b

& B+ c1 a; D4 i/ B% u- V% z `, P

6 ]: u; `8 w. H         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);' A/ L; ?0 r0 p

6 D9 v& o* ?4 \) e ]7 T

+ \1 Y; K ^6 `' U         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);, I2 g& J% r- p: Y( N" J: a) {7 s% @$ {

6 ~7 p+ ~7 ]+ Z/ z+ ^

" g3 w: `( q, u% Z- A  7 o5 i1 c! d( K6 V

* R4 L, F3 A. X# P5 p

; j1 B; e& g @8 I- t. h         $this->image = $this->is_image(); / n& O1 T; ]1 l$ D& [" g7 O, v

; ~# |; }. Z; f( n& x. y5 N

/ S( G4 o! v! N3 R* R% F         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); 2 t% O+ I4 G' ^3 a9 P7 r

2 a. V! P8 T' u' e3 p/ I

$ b% U( k) n1 T7 ]0 P" m         return true;2 `7 b: a8 J0 [% s3 m: @ V+ {- t6 h

1 k3 s! k# y# t

- p: u6 e' h: h6 D     }}! i# p6 R( x/ {, i8 w

$ B8 m! {" W3 M2 [4 W& D# M# f% [

: S& n3 V! W4 U- b 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: " l4 p' g" P; N j

; `+ o5 I0 E; u; C" T2 C% L. p

* \# n) K: l6 r9 g; t- M: J <?php - O2 H5 E8 T( H/ f

& N4 \. V' J8 N! w% G; |

1 l F) v/ ]* \5 h9 Q     function is_allow() { 9 A, k5 w: e4 y7 A, Y4 u7 y

. t d3 `' s$ y# ^% c8 I

, c7 }2 `) l; T: `; q U) E         if(!$this->fileformat) return false; 9 N/ X3 N4 [% q5 ~

6 g. b/ Y4 x; K1 X

# D. Y" |) V R/ h$ i0 o; j3 ~         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;; ? n, P& D) [6 g& D# o2 e

* s1 \ z3 X6 `) b l8 b" K

/ p6 t" [! T T; I9 G/ L% x. W4 ~         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;6 X6 }. t' h( @

# _9 }! ~. v8 B

8 y2 I" T' ]% U! h         return true;) F) B9 J u8 @/ K

# a1 t! g/ i' J8 B# T

' q. X: k4 O0 U; K" @! N     } . S) N2 } R/ K0 ^$ t

! X; x& B5 i2 O; S9 a

: t: i3 e- c$ \ ]7 d 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。' `$ F" t' w/ ^% K3 K

8 `* o$ p: ~( z

: X- u( |9 i" b0 ~8 _2 w( l3 H; 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文件。 , Z* k+ u' V3 ~0 S, T2 c

b( G A$ h1 W% M: L$ p3 h

$ t8 g8 I/ t5 u: U0 S# U: Z 漏洞利用 ) a% D+ ?5 o7 ?0 A" g3 }

8 r5 I% R8 O( {

# U' L ~! s- C 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 : _, }) m% e7 T

& ~: F. K* R5 _4 K5 V/ O. s, [

! V: h3 e/ ^$ N4 f; ~5 |7 f* P( O1 V   & C1 L5 Y# X4 P3 d. y1 N: y

* s% M# S! S9 u9 {' G; z

+ b: p4 t/ D4 M7 j- q" q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid* Z+ o& n% G; s7 @ h, r0 B

* \+ Z- a( m$ [0 G( M1 v* _

" }* ?; ` l' F* U. n 不过实际利用上会有一定的限制。 + u! L6 U( S8 y" J* g& t+ h) }% {

! i7 f" p7 q0 s

6 f+ t4 @/ n. P$ a 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。- S; Q, e M9 r3 r2 _2 _; T

& d9 h) t7 e; r7 Q5 s

1 r& I2 d) z" m+ E1 L7 j   5 D& ^2 N9 h# O

. I# W) L2 I! ?4 M0 Y' [6 z

* t& E! w; ?+ A* W 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:5 C6 E! Z9 O$ r7 O; p

/ Z, ~ t4 v! U

7 q2 m$ f) L, Z. n2 h' O& [5 y 省略...$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]);省略... 3 \% x! y9 |5 D8 \0 o$ \% k. O

! p8 l9 m$ Q# g9 R& A

# X; {# j9 M8 v$ G; j 因此要利用成功就需要条件竞争了。2 k7 |8 ?9 T b, n

3 r$ I& T; g" C- i5 O2 o

# U% N ~6 v; Z" n& ? 补丁分析 # r0 r" l7 X7 v8 ?

6 M: B; |& G: s- T

5 o$ F$ K0 [ r  $ ]4 `- a& _7 n& D# a. n

) w! ^: k3 L# }: T( m

x( ?) g6 _$ C: f) x- O 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 2 n7 @! i& ^, A! E+ H: X

2 _7 J2 U9 o6 H H& a

: b4 \# r- v+ h" r" l* J7 n function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} ) i% Y0 X& d% m. X0 R& H3 ?$ l. R3 w

' g9 c4 i1 v; K* n5 ]( s# b8 f

4 o: h/ a, p s% x) q   L2 J7 ?* S# q

4 G* W& s$ i4 a+ b' w

0 M3 t9 I F1 ]5 C9 i5 L 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。" b; w3 ]7 c5 G0 F; z X+ A3 ]/ o

, g$ O" g. {+ i8 ~

* ], g, ^6 Q6 @& K7 ~ 在is_allow()中增加对$this->savename的二次检查。 : H! X& F' ]& y& _* \: E; X9 E

8 V& X7 j2 z9 A8 Z8 Y% r$ @$ U

3 M8 M( {3 _0 ^" Z* L" ]) M6 m 最后 * F. `1 @, N; G9 {! |- u( m) \9 T

# Y6 a3 s9 A# d

! k# |' ~$ U7 Y4 \/ n9 V 嘛,祝各位大师傅中秋快乐!" t6 p8 w" d( V. |* ]5 s

" X+ k& H& ~" {, m8 l: m2 {9 c

5 a* r7 K6 u1 w: A2 p6 c- q4 Y   " q6 g/ {1 h. b3 p8 M. p8 ]

1 V4 M o6 w' Y
回复

使用道具 举报

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

本版积分规则

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