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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
" f' J7 s3 @ }) I) }6 H

! U0 Y2 U+ m# D6 t- S; ~

8 N( x0 I* b, @; k" [$ F

1 h; ?1 m* M) ^2 C7 ] 前言 6 \! }. m7 R0 {7 n

9 E2 k' z! B* h+ {

: ^. H& q A* `' a5 U 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。+ i( V# s( J. {4 c& j/ H

9 c$ s" U1 o3 d% [9 G) d

0 k; F% x$ k/ y; s   ' `' X0 u! r3 m: A% m+ e4 |) l

. w9 | ^+ Z; p% K6 H# ]# p2 I

6 x9 M& v. ^; S. a* ]1 Y% P0 [. @ 漏洞分析 $ |; K7 p* T, b

5 T' t* {( W# V1 l1 X

6 D* L" m8 X$ ]3 h 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下: H! ^3 [& I2 s2 F$ G

$ l+ A+ s( Y, m& ?

: e- c# e' x; q, Y' Q   ! W3 Y6 k Y: r8 h" x& c3 F( \! U

: F' b2 o. j% x2 o& j" s e

9 h3 F; c+ u! g( J! c- m 对应着avatar.inc.php代码如下: 5 @! P5 [' `+ Y1 r

2 s* D+ \/ X& @" G+ i! U( p3 s f

8 G( M, v" x4 |2 F <?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) { ' i- U% g, C% D6 k8 M

! }* u2 q; o- R8 P# U

" P- X1 g+ S1 R7 ]/ e     case 'upload': ) W$ r6 P; F4 i- s+ O( z. v1 ?

& b& m) b/ d5 V2 E* B/ |

# `2 a/ x- `4 Q; f* `% @; r         if(!$_FILES['file']['size']) { 3 v K: H) p; p; O

# }3 G5 H ?, ?! p

, X. M' p1 n8 h! F t             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); : J& {2 [3 m$ c

/ L5 X# H5 ]9 B: O

+ c A5 J7 D+ x1 I4 G5 B3 o             exit('{"error":1,"message":"Error FILE"}'); # S) `/ A' D# U

1 R( X0 Z0 |: ?. q3 k( n

' ^/ e: E" p2 B2 b% I         } ; O) H0 F3 ^/ f$ r

) @3 q: f: f% }8 I4 P3 f# e0 w

- @0 Y( Q, m0 z. V, C \& `: t$ D         require DT_ROOT.'/include/upload.class.php'; : Z% Q: s1 A6 H# ~) U

8 p& a2 G2 |4 T- P: {* |9 v; ?

) m/ X9 E; B2 z' c, ?6 U _3 ?   % t& g3 z( N3 p+ n& r# Y

+ y( i, n/ o! j2 j

) K' |- Q. w$ I         $ext = file_ext($_FILES['file']['name']);9 H8 @0 ?. Y6 Q# H. L0 F- d

! d- f& P* B" [% L) A: }. O, V( C

+ f+ a* y" y/ p$ D O, P         $name = 'avatar'.$_userid.'.'.$ext; " U! Z: i: M. ]& b+ m8 H

/ k& d0 |: |* a: k/ K! i% W

/ ]; b. c* S) \$ E3 X; L7 t         $file = DT_ROOT.'/file/temp/'.$name; Y( v9 ?9 @. w+ B, @

# Y) {1 @! p4 s% d

9 M! w, |9 ?) ~# q  7 t7 N# X2 z5 N5 U/ i6 y1 j: b

4 ]: `% v' k: `2 p$ ~2 m

1 l6 G5 @1 [( e" i7 }         if(is_file($file)) file_del($file);% d! g1 ^' |% g4 m

, ~3 A1 \# u5 \9 y0 }$ _+ o. l

+ B0 c, e! F+ f; H* B% Z8 b0 d! k         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');# X: e0 f. {7 H

# B/ L% _( I/ |8 ^. b7 T" ~7 L# o

5 x8 L, J& E/ f* a9 ~9 N   - r L9 z0 c+ C* G9 z/ y$ p

7 l) u/ a7 G8 q5 H$ }

. Z$ U# z4 z$ Q         $upload->adduserid = false; # w# a/ x1 {$ u" ?, g) g; S: d

; e) J7 y% Q0 _( w4 J8 x, R

! V" ]( x1 y2 F2 ?8 T- _  ; t8 {9 U& E$ s4 c8 l, F

6 W7 s4 `4 }1 h ^6 d) T# Q

~$ [( N$ c$ Y0 \ Y1 f0 g         if($upload->save()) {, z0 I, Y( p f/ |/ A4 A

1 t/ ^3 m' D }9 ]$ b

; k5 s- r$ e& a             ... 3 F/ v( p0 L4 B& U. z

3 H* d0 ] g3 V$ E

2 r% m8 ]' }# W) x* e         } else {4 {$ Y; D, u7 e

?" z2 L) I6 D B; Z; n

5 T$ V |8 H, E8 B: B, _1 |             ...3 q" g6 i. n4 _. i' e

( n: l5 @4 w* ~5 b: m, Y

, ^ e& @" B" R0 e- R6 T; M4 G         } . i9 e) f. k9 y/ a! b& F& k1 w) L! J

" | d0 {& n. N" |8 _ }+ z! F

, I; h, j* c N. a     break; # ]8 v$ C r1 L' \

: q/ l. R y0 Q. q3 o6 }

8 N: h' d, L! Z; o 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。 z2 k9 B) k! F* i" d% L, D

3 ^4 w: b% u4 c% p

( z8 e9 { J/ P, v) U upload对象构造函数如下,include/upload.class.php:25: 7 ]1 Z7 N L* K' V- E0 q* Y8 K u7 C

$ ~+ J! @ N& Y# b# m

6 G' |' {/ L0 [# I+ b <?phpclass upload {" W2 V& F# H2 o+ s

, c! \: F6 M$ p% X3 f: W `

- J2 x" t, S( _, K" W7 N7 F     function __construct($_file, $savepath, $savename = '', $fileformat = '') { & N$ a( E$ I2 f: }

" @! k/ n8 }( Z1 j) K

% T1 R: m. B# a$ l4 J         global $DT, $_userid; ' a- A. u9 _! L S

% z- |9 I5 y; Y$ X- s$ }+ d

: e9 R* w7 P! _! l" }- j8 p6 N         foreach($_file as $file) { : C+ k8 Y# Q3 n c/ l

5 h, R$ g4 l' j4 j

) J9 C1 d. z5 Y- @( h) `. k             $this->file = $file['tmp_name'];; ^3 U0 F: m( i% [" }9 |, }5 o

, C3 o [0 ]' V+ c' g! B

1 R: ? r& ]) [0 [/ n, p) W+ K             $this->file_name = $file['name'];$ d& ` x. X9 Z# a

$ _, g# W+ K, M2 J5 X5 c

8 y" H. D) E2 u2 H: D( s             $this->file_size = $file['size']; $ M( H: i; x% }, F! e

1 x4 F1 E/ f7 H4 M

2 y, l1 r) R/ j* B% n! c             $this->file_type = $file['type']; 1 h* u! M7 N. |8 u

6 w, w9 t& N/ U- e# P5 { ]+ O

3 j& c/ @! E l! U2 D( ?% @             $this->file_error = $file['error']; ; p3 i% Y/ k k e8 X: d% C8 H

% M8 S4 K3 K- a3 a2 Z

. {0 Y( J" [+ k, q* \   8 ^! J5 s8 v5 ]3 S6 y

/ `3 ~5 N# n: y

* e/ b }4 R1 Z& v E6 G         }2 k8 W z/ F3 L

7 h+ V$ J! `+ Z6 c7 x

% q( z$ {- G$ ?0 r& n. U0 z         $this->userid = $_userid;" u" \ ]* i! e/ \

" E. U# h4 K9 \

* r' C+ u# P- \3 ]         $this->ext = file_ext($this->file_name); ! b% y% f1 P7 C( R& b3 D' r9 r8 p6 n

: Y; _$ R. O) s4 l

$ _" J* b# y5 J, D; n+ h- O; x4 A         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];1 K8 K: Y( Q7 W/ C& e! \

- j0 ?8 s7 h( P) I, p5 @0 {

, M' F/ B* G: D2 z& k* I$ F4 M# T         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; * q X( m% O* _5 M I& `" b

2 q8 |& T n# X! X' K' a* s8 D, C

9 i1 {9 X i; F9 x         $this->savepath = $savepath;6 E/ X4 r b% r' {6 K

6 V t$ }) D/ r7 m' i1 F7 d& e

$ s' [+ X; ~6 i" t8 m. a, O( @         $this->savename = $savename; 7 S# I8 N0 j+ G% f* S: X

% H" U# }) o+ u* a5 E* }6 M

1 [! F" f. S; Q; l* M: Z' [     }} : @6 L: e1 I! \ @& w

5 I5 L, g% Z& n. p4 S8 k, x9 n

. z, B. M( q6 m. w9 o1 j- Y; X 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 f5 z* m- Z4 w6 v& e

k2 i) h/ i1 \

; c3 b! @6 Q* |" S, d 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 - n/ j! H5 B3 O

/ _1 v8 H. V9 F& N3 ]

5 P: U7 `- }3 m9 d $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: j s( x( h5 @1 S$ J1 O, ?3 h4 E

" r9 N& l4 k" e* E9 k

2 }: N1 e, S0 J% f: F- ^6 h 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: 6 N: V" G9 N1 l

' Y) v& a/ [2 V/ J& ?

' s- u- ?) I1 g! A0 ~   : F" R' t+ M; [, Y( c) h0 d5 a9 l

0 g5 M- B: M) R" g( g0 n" z; R

1 k; f2 [* F. m; z/ o& x3 I& K 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: b0 x; @9 B" n' Z0 L

9 }7 X5 q8 a" Y$ a% s+ E$ r* Y0 ^

( A8 a* U* n3 H. A <?phpclass upload { [7 w ^8 [' i$ D

4 f1 U6 A& [0 o* R9 c

7 n; s5 d! X2 L- w) T3 P, G. O     function save() { : w5 V7 I+ D1 S2 s

2 X" n" D- o: O/ }4 i

. J( o7 f7 n0 M* N# g         include load('include.lang'); $ m& g8 t: u* Q R

7 z0 r9 U4 D" T8 r2 ~' O

8 a1 ]8 `8 u0 M9 @) [0 r         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');0 B- u& j! J+ }# x! S

9 d! |8 X8 o& J

/ C0 R3 V) K/ c2 w   2 \8 r g" `. W* D8 Q5 Y8 x5 |

0 w, E# \' X4 U. L4 _ G2 _- E6 e

5 N8 n- V: `, `# \( M3 l8 p8 B         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); 5 A& s* j. r( w$ O8 Y; y

4 E0 C% T: {& f" n

4 n/ \8 w: c' D/ R: a& u: K$ o  % P7 E6 J5 p O9 y% f: Y; a1 S: S, \( P

" l* y1 o& F/ A5 S. z# d* S

$ z& u, e0 h- B* d+ s         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); 1 ]6 p9 f' b4 {% h: O: ^, E; j

7 `) m% M5 _6 v/ w0 V

1 i# U3 C; C- }" {  ' N: A f. ^# V

~4 J, c" G7 l; g, R6 N

6 q# g5 F! G: j+ X' `5 Y         $this->set_savepath($this->savepath);0 h* f6 l9 [- f4 A5 |9 ?' Q* i

+ v! A0 m. y |& h* b

S, q1 `; h9 r, H4 G         $this->set_savename($this->savename);/ _7 r7 x. a& D2 w Z

8 o' x# C, H |+ s0 I( }) F: `

& k! s: X% v; j6 y: n  7 U; ]7 t8 F+ S+ q6 {0 d- M

+ f3 F8 A; |9 l

2 A2 O- J) j) c/ ^! o9 ^         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);* M4 J7 e2 C/ J/ s& j; i2 ?9 A

! _1 b4 \! d* f# S \* R; C' a

- n; ?0 ?+ a; f3 p         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);* z N5 Y" x1 v) U% J

. h8 H% m7 _5 m: ~) v9 U

0 R, q G9 z: e$ L# D         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);& H" t R( U! d; T, o

7 v* l% k$ N* T7 F

" |, D6 c4 U9 g" ^- B) a  : L# h, v/ U+ j( N) D; F

& H1 a( ~. U' I- K# c/ {

- y! T4 W* F' z( c1 `* D         $this->image = $this->is_image();, w8 o1 C% H* q& X+ ?, d6 c; Q

1 l8 d- |1 G* A( i

4 [" o& P& g* I4 C         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); : h' ^: I! K- {/ @! i8 y* }

$ y- l7 N# \9 X0 Q

. ?, U. ~$ @( ]) m/ P         return true; : C3 ~2 S+ u5 Q n% p6 p$ R/ w

; ~& j+ y) [; \9 p5 T( k# K

+ O# j8 L2 `: |# T     }} 2 R" Y6 I4 l- \3 ?

& ?) G1 w: ]/ _" j" _9 \# N/ A

6 R5 v& E# s `8 S1 J& d 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72: ) ]* ~. R9 ?8 m

7 }/ J1 x6 R# @/ k

& p( A( i$ K( J, I' ] <?php 4 G) `0 u8 o: H/ t

1 l2 m) t8 u9 G) a3 l# ]

8 p6 P2 s7 e5 t     function is_allow() { 1 q& l- x; j q. U

3 Z. F9 c4 T. A) N) y2 j7 r

@/ z! I' k7 L5 r         if(!$this->fileformat) return false; . k! `) n8 E8 G5 e( @' X

: E& p7 M4 H- C) i

- G0 a1 S* |$ ?% {         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 6 Q z2 q |6 N; S

) R, w7 v. w% G( j6 t3 ]

2 H7 L% N- M1 O) w! S0 m+ Q         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;4 a% O' l4 D5 u- u) ]& X; H

8 n& B- ] _$ `. |7 E

0 k+ h5 h2 G4 d5 m R$ k         return true;0 q7 F' v% c5 o" p

1 g. h6 @5 ^2 n) i+ _6 B0 K

+ M2 U2 N$ T$ u9 N! [. B0 L     }: ^ L7 t0 G6 n

7 Q/ L/ X. \3 I) r( a

8 d! o6 U4 B* }, I) U2 m 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。8 o8 o% J j% V5 h" G

# t1 u' @6 p* [' x/ T7 h- u, h1 s

% Z; T+ T) c8 [5 Q7 v3 Z3 r L3 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文件。8 t9 c9 W7 X: t4 E' L* n

) O+ T9 C \- m0 v

6 s: I# d3 i& T& Q# Y: ^/ S 漏洞利用 3 K6 m, n# q I4 ?+ ^1 u

3 }( q' y5 ^) E" F: L

, j2 V1 t$ N7 }! B+ F2 D 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。 " \. ~& j/ e9 ~4 n1 g# G

9 T1 M2 c6 `1 s/ e

* f$ N' A! n6 f L( Y6 Y) ^   - a% m1 Y2 [" H: K

) A0 k! i0 M/ N* G4 ]6 J

" z* X. \* ~ O! x. [4 u; n 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid6 w0 i$ Y' b, B9 e. _

& F1 [1 ?: }3 R% f6 d

! J5 V; l* I$ K! ?1 s2 Y8 K* M 不过实际利用上会有一定的限制。 # F; p9 v+ X8 Z# D

2 f3 Z$ f, C$ U

, q) [2 M( ], N) z" J/ q 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。 : O; q. z8 Z1 S% p+ j

: v+ I5 b) f% u6 P: y

6 i7 m; w' M+ o   * e' y- g4 t- u3 M9 j- T

( I m" B' P9 @

* n. m4 q7 Y% A$ _" z5 F 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg: 2 ?, \1 J1 Z) Z( A

. z3 m5 Q9 v+ T& R1 W$ m

& P" W, R& ~; g 省略...$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]);省略...% V& T6 p/ h$ T0 g

2 M- v5 N% w$ S

4 V, S) S) K; }- h! U2 X 因此要利用成功就需要条件竞争了。. C8 \* p* N/ \

6 D3 N# i2 @- M7 {

; Y, l5 J, F/ R 补丁分析 . Q. T/ z3 \4 d% ~& `! e; k8 J

; S- V. C5 K) @8 B! t) Y1 E" ^: U! N

$ m9 J0 F; q( d  $ }- M0 v' j$ k

1 G/ o" j \# O" `

. m/ l3 Y$ Q/ A2 i& I* w 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: 6 @. q9 @ E8 V

+ a% N f$ ]& c! Q8 X2 K2 t1 `9 X

( R: Y! |: t0 e function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} 4 y& E; m/ g" E$ R

: h. r, D1 _# G2 l1 g( [; x1 M1 u4 I; G

# ?, @) S) S9 e* u  / b* M9 m* f. Y% i$ a

B* e; y2 `3 u4 X2 z# A

2 f& e4 i' b2 q8 L0 `3 D 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 - n3 r9 _! a+ _0 m

1 ?. S7 X4 f' M% G9 \* H( w

$ L' G& a9 q! g0 a2 O6 \5 f 在is_allow()中增加对$this->savename的二次检查。 % c/ X r3 r: t$ F {9 `# K

% P1 L1 K) B$ g1 v- `. Q

* \, t+ y5 \' J3 |( S 最后 . ^9 z7 |6 R5 [0 {- a/ T

. }" X, |! @' v5 g+ V. R

2 V. @% l- j/ D4 W5 t, ~ 嘛,祝各位大师傅中秋快乐!$ B: r, J; p) L. r

' z5 x. o# V( ~& q4 S+ K* j

: O5 a6 e( G3 ?7 @  # Q4 e3 `% }( g' |* o8 M

2 Q% Q2 O" \- h6 D( C( U% ~
回复

使用道具 举报

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

本版积分规则

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