找回密码
 立即注册
查看: 1655|回复: 0
打印 上一主题 下一主题

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
8 N. I1 A1 @! H% x% h" F0 S

5 Q7 }7 f8 B- U/ v/ `& Z8 @6 P

3 O* ~, L) _6 x( W, K

+ H# q7 f# Z3 e \ 前言 ( p! C# Z1 ^1 k6 L n7 Y8 N

4 Q; c9 i+ {6 E- R& {5 w3 j8 y! {# ]& m

, [- X' f2 x. N+ t# h# }+ c+ ~$ B 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 1 q- o: A1 I7 R

2 _7 v, B3 J& \, G

; |, t. T0 f$ l0 H1 Y9 v+ b  / z* M, i+ B" r1 c

* U& f1 F( o0 o( i4 o

+ r" z- I4 e& ~. w& m+ s+ V7 ~ 漏洞分析1 f) k4 d" W7 G9 D! k

4 ]2 S8 M" c* ~

! {1 a, X% ` L( i5 ~! K2 y* ^" s 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: 0 x# e7 U+ [2 N

8 T, y: J1 x, P+ y# W

2 N+ u+ f: Y. @5 t c   ' F7 y1 O9 w( M" o# ]/ `7 O) j. f

9 O' F# Y! q# Q) C1 f

; M! K! J' z, o" ^+ Q- @4 D 对应着avatar.inc.php代码如下:! c9 [' V% ^4 `% ^

0 p1 Y8 |! s9 L9 m) ^

) c7 B1 x0 F# _' y# p0 u <?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) {! O0 c0 A; o; u2 l1 p

$ N8 [. G' v: e4 b1 f- N

7 r; A5 Z# n3 w* P3 ~/ R     case 'upload':3 m3 Y y6 ?5 ]& \7 e7 h

2 a& u( n( ?3 _4 M! F+ `) O

' W4 _- c: L3 `% @! D& C" }         if(!$_FILES['file']['size']) {2 y7 L( E M, t, N) k

4 w$ z2 t* `# w- _

( m, }: g& Q; T5 @3 [- E$ J/ x. f             if($DT_PC) dheader('?action=html&reload='.$DT_TIME);2 S- T+ N( m/ b% |

* c* L8 ?+ g% T& `$ S2 b+ ~

" N+ K( E4 Z, W' w- [8 f3 G; Q             exit('{"error":1,"message":"Error FILE"}');# W8 w; S2 g" K8 e: D+ N: x

' `5 H+ _: z4 C1 R k7 B2 z) k* w

* M/ W6 b6 K2 V* \         } 8 j% ^6 m' y- A1 A9 q

& h+ _) z( \! J9 p8 A4 ~0 z+ B& q

6 S" Q: E1 d5 v2 _9 z9 F         require DT_ROOT.'/include/upload.class.php';7 A$ d2 H0 Z* [# Y3 I

: c d( @8 V; Q! M, W

+ S3 F& |2 M% E8 p# h6 p   " g4 k! D2 v1 w! A% L6 Z+ y, X. H

: M3 s6 q I) i1 L0 l1 U. X1 H

, R3 \' M" K- Q         $ext = file_ext($_FILES['file']['name']);4 |# A. e/ P. B- E3 g

6 K! ], Q1 u) V# e* z6 M7 Z( u

: d, v) ~! m3 q" D1 ?         $name = 'avatar'.$_userid.'.'.$ext; ' b' b8 O5 J* w& x* X& y) w* `3 [) r* z

' x7 B; A. y( N" j) } f4 n

4 N# g: F$ v2 M9 ]5 E         $file = DT_ROOT.'/file/temp/'.$name; 6 u$ g6 }- e _5 |

) N9 z; S) P p: r1 o; D- ~

' J+ e, n$ q# I! d' ?+ b& h  1 J. ?" I" _" x0 G* e" H: {

' j& \2 c5 F1 t4 `% G5 m' Z

! k& w# S! f* y& l         if(is_file($file)) file_del($file);# K4 B- C: A4 M* d7 N4 ^

1 o4 }! n1 R' K4 b- {

2 ]" N0 [. _% o R7 k v1 \         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');7 I- T" S+ A Q1 s* I0 j

3 e X$ x8 M$ C# m! o/ Q

$ O, p/ p, {& R* [' k6 V9 W- E5 d% p   ( o, y6 d& u3 z. u' J, X9 Q1 s

# D) e) S7 R( r1 F8 ]

$ G: @! |/ {) i5 ^         $upload->adduserid = false; - G& N9 p( i/ r+ W+ D4 e

5 g6 k# p2 o# f! e" E8 F# j$ L

5 v& v- k( ~2 O; T2 p, z* H- X/ } v   5 U+ k& P1 c' c4 I6 B9 G8 T' S

, t% _, ?* [+ E' U" ]4 \0 S, j

3 ^! U# o% I8 l3 ^1 F( g. D         if($upload->save()) {3 t! h8 _$ E* T( B! c. U

9 I8 A; z" }& R! A( T5 q

- y" v& d1 h- ?2 X$ @             ...4 V) O, R( ~ ~7 C9 U) L

. v* u5 a2 n. O; f# B% W

9 e% {! ^/ w* c# x; u& `/ G0 h( f- K/ X         } else {# X0 v( ?$ [8 y

, ^8 k4 q4 D$ [, X2 G" ?

; n e; K: f" d7 n( |             ...5 w7 `- i j! R5 J: _

' {8 e# m- b" ?+ @1 j# d& g

! `2 a& [/ b* P4 ]         }1 z- W$ `1 i. a: `

+ G* M+ v7 @2 K2 B( Y$ m( _

. w8 h# P" e# [9 X5 @) L     break;5 X9 y/ Z& f( f; r* ~& R" F

7 ~1 F/ q' |9 s0 ?6 h% W# S1 W+ Y5 f

3 z+ w2 O; u8 J- v' H 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。/ h; i! ]( P5 z( g+ ?& y5 c9 L

& A! U4 w3 u8 K5 b8 [8 e

3 [1 y _/ Q$ \7 K) i upload对象构造函数如下,include/upload.class.php:25: " }6 n. w+ M5 ~6 `

. D2 S0 ~5 H0 x0 r ~" G

% F; a: A7 v$ h2 i: ^# A <?phpclass upload { B/ B; l3 {- W9 q w1 w8 [

g8 y. a* R$ S

! X+ s7 f% b: G' B7 h     function __construct($_file, $savepath, $savename = '', $fileformat = '') { / R* e3 d0 I1 k* X# W5 h7 J, X

! N: g- K# ` O: ?0 u( ^* x

: p% [! w# h$ ?$ A( ^% z- Q         global $DT, $_userid; 7 G' l/ Q6 x* G6 L0 @" j

& v- v+ }+ p- E" p

- \2 f5 [( j, n3 u: F1 g         foreach($_file as $file) { 5 E+ p) {# J) _& j5 `

( U; e! w1 |, Y0 X- d* G) Q

: h1 o# N+ ?9 a* [4 r             $this->file = $file['tmp_name']; 8 y) Y4 A3 o" O! L2 y

5 F, E# P! l3 W( o/ `2 L4 S' U# A

& |. [) Y* W6 {' U' i             $this->file_name = $file['name']; ) ]( x4 m3 G. U9 A' _. y$ a

3 m, W8 R- D: @7 t7 ?

0 {# O O( c* H4 |3 z2 e6 U6 ^6 ?             $this->file_size = $file['size'];2 [' S, e( g9 A0 H/ P* B, J, W8 D5 k

0 j3 { C% }" ^$ r6 ~( a

' f" T, \; R+ e* i             $this->file_type = $file['type'];7 j+ c' B, T( h; w

. F |6 P3 P& e; T$ S2 P( Y

) j9 I; {& `" _2 |             $this->file_error = $file['error'];8 @( l( ]9 M9 Y5 m

5 L' {( X" E, _2 `

( P6 a" |' H5 K! k   - M. {. C: I" M3 p% D

# b' g% Z4 H; R2 Q, f' _; k

/ i" o& K- d- r" r         } : g! v2 d) C" Z

1 Q) a8 `8 a5 P

9 o; l' E8 g7 W; W& D         $this->userid = $_userid;8 `' D% ?) D% U

+ n+ e* e' q. v* c; ^

! O& ~" P; Q" U         $this->ext = file_ext($this->file_name); & z' ^2 h. S8 ~% E- n0 {5 u

) N" ^/ n' t i' ?$ E5 U

* B8 o& Y* E! [! v2 y2 d         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];7 G+ ?1 v0 M# r' Y5 J1 ~) B9 v

5 z- K6 \5 o: X6 ?; X, u" _2 `

2 s6 R$ Q4 Y. k$ l7 X* q         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; " v0 c3 }& x7 h- E/ H+ Z& [; `$ s

4 Q; L2 l& R3 H2 ~

+ o! Z' W: r5 U         $this->savepath = $savepath;, w0 z% n% G0 V. ^: V

, x+ R2 b3 L: V/ h/ q

( p7 B2 W' E# \% z$ V8 B         $this->savename = $savename;2 Y, y* V! d. b! n# r9 J' I8 }8 F2 [

* h' J4 S) U9 T

* t! k# E! c7 D, K( [) H3 E; g     }} # n- {1 V! j8 m+ \5 y

# U! y& ?0 S; o' Q# m

- V& J/ p4 T2 E, R/ e( O, u& Q 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。3 u$ L6 ` G+ S) T

& ^! _4 e1 l0 r8 A# t

+ J( g* E, \. h4 S* ~& d 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 9 A @' ^9 Q8 k

4 a4 C0 }5 M% }2 O: w

; ?8 V4 S7 ~' }0 ~+ m$ E $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 8 z) I3 O- g# w# v/ A& d

9 v. }: M3 H3 `; Z

7 z' k' Q- c9 X# g 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:! F; M2 L& G/ N1 k% v/ U5 m

0 M/ Q" U0 i* [4 l. Y) O

% O$ v: {. _, B+ p; {   1 m. Q; m0 Y" G2 v5 H7 w5 a0 y; ?, z; _

: v5 f0 F: R5 ^" e! k+ H

2 i6 W' Z) b# B+ X7 Q. k0 r$ j! n 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:2 d0 ?8 ]9 d9 B, u/ q

7 w8 y0 m" D4 i. x% x

' h3 U( }) M; o% S8 | <?phpclass upload {( N' B& M- J! K4 \

/ b1 w2 e/ ]6 a, |$ ]; X0 ?

. o8 [' j, s. L+ S, a! q4 E     function save() {! l& i% J, ^% W

d, _* z: }" X1 }, D8 l

0 M) k9 @- I' g: F$ M: T9 f         include load('include.lang');# I2 P" w6 d4 |

1 l. U: A' u$ j7 ^) _6 y7 m

, Z/ R3 v2 A7 O8 M: P1 r5 _, u         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');4 w0 [ h' `; s$ W

: }) u! A! | s& m

- k1 i# T; G1 G2 U9 U+ B; \. p2 u   2 b9 G8 _* n7 y

* O P8 D: h# R$ x% s Y6 O

6 i8 [, V+ u& t9 j         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); : Y! U n- E% h, \, [* J

( n9 o2 \3 l7 O( W' q# }: k o

& n0 X% k0 L1 F" {* ]; ]   9 K/ w' C& b6 h/ {9 y4 r

" u7 h+ ?% I. C0 x B! Z- E3 N$ u

( }! X& S' h! y9 d1 X( ~         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);& [) A9 Y9 T6 s- c, `, f) p

9 O6 n3 |* p% Q

1 I5 [8 z# q# U* {9 A7 M! x   $ q; p4 h7 o- a8 `2 U' I' p8 a' i

! w: q; g- G4 ?+ n% p7 `

1 V' G5 ~! i3 d( F7 q( x% j3 e         $this->set_savepath($this->savepath);" X- c" j% R1 N

) O1 Z {8 r' X- a i& s

2 ]* K6 w/ C* q9 P4 ?" d; Y" _, C         $this->set_savename($this->savename);% o g4 ~! P9 B% ?1 ]( O

0 A% E6 r) C0 g

w# e0 |, r6 T- ?5 s1 O   7 I2 `6 L' H! W) q2 I# D6 b

* \6 g; u6 \ w) u. {: P

* n5 ?$ J- Z7 [/ d         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);1 o* U- i0 e) J2 R6 `

! f" [( @; [% `! M- K$ P: t

2 K. G( o6 H I3 ~& u* q3 o         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) _7 {; z# \+ m. D9 q4 P

0 \. B3 A* |* D8 Q& \

$ S1 H7 T% f- d! J% w }         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); 1 J) k0 t$ y6 ^# R/ Q! x

' I( Z3 J0 Y7 p4 @: P( X+ Y

+ W: T! b( `4 F% X' H j( H! B   8 r3 x9 x; I9 R4 H1 g

% f4 U- n3 x) Q% M; h/ u% M

! A6 N" ?/ t$ i2 U         $this->image = $this->is_image();( p: X6 }4 _. K3 k$ x& N8 a

/ C4 ~; c! H9 ^4 V1 W2 R0 ^

( u( T7 v4 m& C2 X; p# U         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);( m \" s! |! D" p: J0 D

& Z* P% [) `* Z. ^

) a9 R7 s0 E/ B0 V; K; ^1 r: A% K* s         return true; 8 k8 |4 ~. \6 i) _8 ]7 Q

# @7 `6 y' j; S a

4 ]+ n9 K6 b0 x$ n( E% y/ F     }} 5 W d8 H) x% x9 D& j D

! h. E. Z) ?8 \* t

# K8 _/ }( V3 |5 j 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:" n: ?# O, t$ K' q2 r

# t" B# Z: L: f6 i* d: o; a

0 ~0 c3 W$ r0 G0 T( |7 H <?php - @8 a3 w0 k, p# [ S$ m4 @8 l6 {; s

% y+ k; c" |* E& ^: S

1 q' m, q' A" o     function is_allow() { . x, u4 f1 L0 G5 N

# f" P7 _. T1 `- y5 Z' J8 _

0 g4 s1 E; L& }         if(!$this->fileformat) return false; # Z' I* O/ n3 L! c2 j; ^* S

5 [0 Q/ f$ ^8 w' f: {

7 f; F5 R8 A1 e9 L         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 6 |* t, K1 ^' \" ?' {6 p

1 u, {3 f1 t$ n0 v: c; B% Z! h

# J6 |& f. D# {! 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;8 r: g' \. n7 Y/ E- w# f7 r

0 X0 @ p1 N X7 n k# L' F0 Y' g6 J

& G8 c- x+ {4 S+ R6 u$ x: J' x         return true; 7 g5 }; v) G- ~1 T+ J9 v7 W7 f

: n+ A0 Z& s- `, g3 ?

5 Y9 E2 j0 Y5 L: b: h: Q, o2 b     }6 h4 N9 {1 J$ b6 L) M9 n

+ b5 {4 A& {7 z' U4 Y* z8 l. b8 e

5 Q" {- \# V. n9 C7 S$ H 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 6 X8 ^3 b; z2 [

" J7 R/ i/ j0 A' h

- \- u9 A/ e! K& K n( C7 {! F 接着会进行真正的保存。通过$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文件。# S8 a/ C! s4 o: @' y+ N

7 k' a4 g; ?3 K; Q5 U5 ?6 G

/ p/ g8 H) e% y6 } 漏洞利用2 l2 ]! m' A# M D8 W

( r. n$ K- M$ M: s" S2 k* \; a

4 Y7 ~% R& o1 T; {: ?! A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。8 |9 [3 D/ x9 V, g6 c4 }

" P% Y, R. k: ?. ?

+ r/ i% w5 |$ E- Q4 v, i n   ; ^: h7 q2 v" v* Y- ?6 i$ L: R

- ^, E8 N5 z f" r4 D

, f& Q& V3 R6 o d 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid, S9 z3 E/ y% y+ v- f# _

( |3 R/ N& Z5 S7 E

1 {0 h5 T$ G* _4 C9 G/ w# r 不过实际利用上会有一定的限制。4 F* S7 s: M: V, e, H5 t* }, c$ L- M

6 i9 \8 b# }# m6 ~- F% d6 U0 n

" a# J# [3 F% L) t3 F3 q( X 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。" g/ _$ E& u9 u- I' Z: S E

# m: r) n8 Q' t- e/ g3 B

0 R9 Z C' r8 J' l& T5 |$ m  , U& p1 U0 v9 T( A! J7 V

) l7 }9 Q2 A8 c5 \2 P9 y

! D( M! G8 }+ _8 K7 Z/ g 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: ; V; ?; I4 w; V M& I

7 f; L) \: r, f

# o j; Z% w+ p% k* t7 S# S8 D 省略...$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]);省略...4 m- }9 q+ z& U* c2 t( h

5 D' \: p3 A: J; Q' J0 H, n! U+ c

* @! x9 S7 z: N! a! z 因此要利用成功就需要条件竞争了。- H& O4 O! I& h% {

& P! X3 t `5 J0 z, v

. p" m6 V& e7 b, f" W* H 补丁分析 ' \/ V6 s& z3 _- a- _# ^

% U: y& O* l6 F0 @& Z

; o; R+ Q" ?% s8 n1 e   ( h2 f2 V" s" I/ j8 c+ R, v

' V1 x# ^2 r7 `( c

- ]: B; ?0 d4 M# {* G2 P 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:+ l4 E8 |6 C% u. S) p

$ @* u1 M' }: a+ S

; p: p9 l0 H' H, X! _ P function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}6 X2 x% d/ ^4 f$ x3 s1 j

/ {# A. q0 p) k8 b( Z, g

9 }2 W# M4 _8 ~5 W1 i0 Q/ \3 i8 {  2 u, B7 `$ o* w9 {$ t U

7 L8 C! H& A/ k/ B

7 V+ G9 l( G! z+ L4 | 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 ; X1 x3 \ ?# {1 [. r8 i

4 |! ^4 f, z# T

) |0 m9 o1 I, d+ d: d, k* i 在is_allow()中增加对$this->savename的二次检查。 & O* N3 }4 U# y/ G+ C

/ C9 P7 Q4 u2 l1 j7 J3 h

9 Z( l, J8 n# m A& z. C9 |" H' J7 h 最后 / B& l+ x* k% J1 n. F5 I

/ w M+ D* e$ I' e8 e+ [% `

6 f& K l) ~7 K8 ?& \3 Z 嘛,祝各位大师傅中秋快乐! ' F( C: Q, u9 d; G# k- m

( O R2 H+ z0 D9 `2 @9 S j

6 @+ I1 K* i! k* v# z  8 ]/ N+ j5 ^# O3 I0 k

2 j) z8 u$ k# o* ?/ d+ _
回复

使用道具 举报

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

本版积分规则

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