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

Destoon cms前台getwebshell

[复制链接]
跳转到指定楼层
楼主
发表于 2018-10-20 20:13:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
( [0 L- k/ p- G. F8 a

4 R3 F6 O8 x8 B9 s, i

2 a) I" y3 \2 ]$ v- h# V

8 |; h/ h7 f) V' {2 Q& t 前言) `# T" e, B9 e5 z* Y1 v5 C

2 h S R9 q4 O) P

7 I7 I" S! h3 ?, `9 F4 Y2 S 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。 , ?% K+ ]9 Q/ o+ K5 Y7 Y2 \

# {" Y4 ^7 ?8 j

7 _; `& Q7 \9 d6 I1 u8 n. B9 @   + |) {% }# N4 T1 F$ y# b

+ B8 e& T" `3 W& C' U: P

% ~ P8 z$ y" x5 e3 |2 C 漏洞分析 : @+ D9 h8 m, j8 `

7 l# |" _! p# d. ?* Y, T6 V( f7 s

- L& D) B; o2 N6 a4 J2 A( \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:. l; x3 j5 {6 h. ]

& H4 z9 K6 d- X! \1 L8 r5 Z$ N

( T6 H* z. O3 o2 Z; h( V& q$ N5 A& u   ) g& `) l/ a1 r' N- g3 F

9 F+ O/ b. q6 s3 x& q. i

. a* V2 T8 x) k! o7 L% O 对应着avatar.inc.php代码如下: % F" }1 E- W* b2 F! `( |) [

' K5 b; ]+ K" q

3 p9 h8 _9 d0 c: n8 p <?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) {. [+ V4 D5 ~1 |; q+ B4 A

2 a: w: Y3 \5 J A- A# O5 V

6 {' v# A; `! R     case 'upload':0 T( y- Z5 w# a* q/ m" w# H, {

" W8 z( H2 y- l8 I1 N

. @, \+ A! r N! H         if(!$_FILES['file']['size']) {% j) ]3 j7 A7 v9 b) ?" w

, ]; g, b+ U( M8 f/ [$ Z/ }

( Q/ S' z; h; {0 |1 q             if($DT_PC) dheader('?action=html&reload='.$DT_TIME); : y% Q% Z; g h& X9 j/ c

" c9 T5 G8 _; `" H: x3 u2 V; n

6 \, J& q" A+ b8 \# E; q2 F# w             exit('{"error":1,"message":"Error FILE"}'); 4 Z5 [7 [! }+ c+ A5 }/ w

$ y) A, j! x& b3 O! m0 J* J

" {/ u% d$ m! E6 ^         }7 ]& o* I$ y: R

+ e j2 U9 O! E9 B9 z: R2 P/ C$ u

4 P/ l/ k+ k7 F) K         require DT_ROOT.'/include/upload.class.php';7 |8 V+ w% W% m+ W% w9 J

# ^4 ~! t( O c5 P' ]5 F# D+ C+ ]

/ N- G" ]% ?, G: d8 V  ! r$ {: z3 i0 y* a8 [3 @) Y

* Q2 X8 k& B3 T1 J$ N2 `4 t+ u4 t9 |

, Z4 \; |8 b: H4 O& k         $ext = file_ext($_FILES['file']['name']); * F8 e. c/ S* p6 K

& {" }" [# K) b- }

2 a& O7 w0 }7 M* k; l# X         $name = 'avatar'.$_userid.'.'.$ext; 0 u5 B) t$ W) K. B2 T0 ?

6 ~3 U3 V% C1 d

: ^! n8 e) ?( p' I, B% o         $file = DT_ROOT.'/file/temp/'.$name;) _/ q7 g% w# v: z0 `- T- v

: x) _+ e5 Z9 m* F6 g' X

% p% |# r4 U2 U4 V8 E   7 |" M/ p7 h v

5 d6 n4 |, l6 J

9 h# G; l5 k2 J$ |" b+ w; K" e1 \         if(is_file($file)) file_del($file);) M8 Z1 z2 U$ x- X9 `; w

4 d* J0 C4 Z( \; u

9 @; }) w4 N0 s3 _4 B; k         $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');0 S, _' e1 v5 ]

0 N0 k% y7 N. @1 }: z# S

) h7 K4 ?( F: G. h6 F) G  1 w" Z" J/ [, L& N# S+ ^) w/ |6 `; Q

: I1 G5 G2 t! C' Z8 @" O

* D6 p9 F# V4 [         $upload->adduserid = false; 3 s9 U" l7 H: G) k$ g# x9 [) |: ~

. X% G5 [' z3 h: ^7 G

1 Q% P/ u; G8 G" i: U* Q! z: ]! {   " v0 h, g: ~( b+ A6 J3 E- l

. Q+ @ ~) n" v; n

1 F& }# a, P3 N4 @: | H         if($upload->save()) {, n0 |7 M3 t( N$ G7 b

0 ^3 R( H( y- A7 y

6 S) r& [( }$ U, m1 x$ n2 W& I             ... 6 B* S6 i# D/ E+ K6 U3 G% {$ b. [

. m5 ]" P7 j1 P( z) D l' X* T9 @, X+ m

3 a3 }5 L. I: s5 ?0 ?5 j* n         } else { 6 M% K: g" i- z- ^7 H. Q

2 k" O0 a8 d+ {* Q$ c

# ^! @9 F0 o0 @3 k$ c             ...3 a# A+ s+ x# F7 l y& g0 z

! I; A0 h- j% s# t' A: O

$ [5 {0 ^' |. i) I         }/ }" Q n M9 @2 L0 p, B* m$ d4 [

! A I/ A- a. s( o) S

" g6 R6 S& ^) r3 \) G8 K2 `( X     break; 4 H, P! b' b. B7 {! J; E

+ P% C) k$ d) ?) ~2 z6 o# D& j

, [- ] R1 i: _8 K9 L5 d5 Q 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。) X; N2 s" v: _

# [; n7 l; ^+ F6 z$ {0 F! Y! K. w

6 Y3 y2 S0 |$ f3 H/ ]1 U2 j upload对象构造函数如下,include/upload.class.php:25:8 r- e2 ?. J- T* S

4 ?/ a( x# r2 L- K8 p/ M

7 f; A0 c. j: m: D: H! N3 I, z <?phpclass upload {) R4 [* P# ` ?3 W) \2 B

/ D1 m1 E# o2 s% A; R# _

. W3 a* z0 e/ x c% q! ?! [     function __construct($_file, $savepath, $savename = '', $fileformat = '') {& f# j2 C8 B- h4 t. z' O

+ z6 {3 A; g# K

/ N. s8 d: G* f, s/ r         global $DT, $_userid; ( X7 ~) m/ o8 u$ |. u! Z; W

# f7 S6 w4 l9 s" }

4 f) t( [- Z$ @- ~3 P8 ~9 }% m         foreach($_file as $file) { $ o+ \0 f+ M0 @' ?( e! v. k

- D& Y( _% {4 h

6 T: K" \$ E9 ]1 ]" e; l             $this->file = $file['tmp_name'];' |/ O+ P8 n3 y2 `

% f( G A3 |+ V9 z( h1 c

5 X; `- Z7 _( F             $this->file_name = $file['name'];& R+ q' n1 y3 O, p7 F: V; D9 r. u8 D

7 j, [& g1 m0 J" b- M

! u. g8 n. A+ l             $this->file_size = $file['size']; . N* ]6 ^! \4 ^2 Y% E2 \' B

8 W0 g8 R6 H' I4 S% U" e5 U

% A0 C0 g. @$ ?1 e6 \             $this->file_type = $file['type'];4 P0 C% [( U( B! a

T5 A1 R6 P7 b0 V3 T, i

2 g% H u; u: V, d; g9 Q% P4 Y             $this->file_error = $file['error'];3 B% u: ]2 T' e$ B* N) g

9 O/ }7 v( J6 N

' M4 ]2 S- J( M0 P4 `6 M. Y  : u8 x' d: W0 s0 l9 a4 m7 t

$ x- n$ I+ w$ w

6 W9 U( M# X( o& l O& @         }; P" t. i5 L' m5 h

. z5 B! x: u4 u) D) O

( F& k$ Z0 q. \; J5 r3 M         $this->userid = $_userid; _- l7 F$ f$ r+ L, V% i/ T3 I

3 \" ~9 Y1 O1 P, T& ^

- R" J; F- T2 @ S0 ~- Y8 g4 f         $this->ext = file_ext($this->file_name); # }+ l, b1 ~! |) q3 B

* t* Z7 W* u- L4 Y( C0 X9 `2 ~8 b

$ h$ R. |+ b( L5 Z( h         $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; $ d' b8 t+ d7 N" T7 x( A

1 [1 I% r& `- p+ h" r; }# H

+ d. n3 K% M' C4 \& f" g: H) I         $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; 0 p& O: |/ R/ \, U) B/ o. [

; z3 V# O5 L. H0 e* H6 q( o l

% O$ X9 F1 r- m S, T         $this->savepath = $savepath;- ~$ m- c+ h9 m" R8 o

$ f9 [; V( o4 b+ S: B

* K2 m0 M) p) a M+ }         $this->savename = $savename; 9 p4 b4 v2 P, J F" L

" {3 d7 f4 A7 H2 R6 c

' |, Q/ J' E& p8 j ^7 F     }}5 I: G _' M! W; }# r: `

7 `3 @, ]/ q5 k1 R

" q/ {, R$ X; I2 m% ]; j. r O 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 - ^1 q% d8 y7 h9 l$ n7 |& Z

, w; y2 q1 y7 O0 u$ w; i

- k, P7 e0 S( I5 O. O6 u 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 / a. {( d+ c3 O% K; q

$ G' u7 \; @! H- I; Y$ @

4 k8 J. }. d1 R% h$ ~+ q: F% Z5 F, a $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 ( s Q2 I: z: g# ~; ?

1 J# t& I3 e0 z

$ D9 ]' S" y" O 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下: - {" o) I3 ]5 L1 ]' _

% z# u/ @6 d; x

9 Y* j, g N" Z$ G/ r; h2 C. l  4 L3 x* ~7 e0 A

& C7 x# e# s' }( G4 ~

/ m, s7 ~" ]9 s' U* a- k& m 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:5 |& w0 }( H" o2 `7 f' M" y! c

4 l& h7 j0 r! k" y: f$ Y

G1 g0 U- V8 p1 B5 [ <?phpclass upload { ( ` V# K/ A' X

1 D8 W3 Q% i, x5 h& q. b' I( q

. Z# N/ q- `- r8 m9 v) v' \     function save() { 1 j6 V. X0 F$ S4 C- z

+ \8 t. N+ J7 e! I

9 Y* t1 I, E+ C/ l         include load('include.lang');# Y" u! ~7 j4 |3 X" p+ t6 M

. W9 |; P2 b1 V- b1 A! J

3 C3 e+ @3 t! O: [% Y         if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); & K4 [2 Y3 {; y" u

' R# |) {/ a* w2 T+ q

0 A1 E* C- C/ s2 d: i/ l% G   1 W- T+ B3 s" f$ k6 l

1 g" ~( p% |7 k# Z* U& q) q

& P6 a7 P; @* y% l: o' L& m         if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); , n8 J& u8 V4 j7 H

% f" q& R5 V f& G9 D& A$ P P

4 F# y: T% _! k, y   ( o( [6 v- k& g. u

) J# G, N1 W( v" q9 B

+ g8 |4 \* j' o5 `         if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);0 f9 B6 F) }4 d X4 }5 e8 I

% ?( u! n( Y G/ Z, ~

q/ F+ \ P. S0 S/ s- E% y' F  6 c6 H6 f) a5 g' w/ O3 A

: m. d) e/ K% X" ]

# F- i3 P( O, p         $this->set_savepath($this->savepath);2 c1 U+ f; l p7 p; p( J

7 o5 k4 Q% m, T1 T" T

5 n8 D' F( I c3 I' x         $this->set_savename($this->savename);0 Q4 n. C- f9 ?4 q: F

$ c- L, b5 U0 S

5 C& Y, { K V6 J; q6 }( S7 Z& M   : l' G |/ a' _

$ f9 d# A" ]/ o0 F0 K1 A3 g

' k2 s5 k2 q7 N2 b+ x/ {         if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ L/ K( X1 k7 h# ]: o, {2 f7 w3 U

& p: l& ~# f B" n+ d

6 [- x: W# g2 `% A- M( J D) K         if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); - \9 y- Z, [7 m3 q

! }' n8 l* }. ?, @

: w! p. E4 }& V9 N         if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); ! k1 m: b. S5 y/ W2 j

5 J* a0 ~+ X/ B7 T) j- `+ L) s

2 B" ~5 o* K5 w2 u  2 ~; K1 w; O8 L2 C: ~7 x) i

: b8 k# K9 S! \( y

. o# j- }( e! Q# d) i         $this->image = $this->is_image(); : l6 \. ^5 N/ D: M! p$ q

9 W! R8 I0 T# }% s% v; }0 e, D

6 h2 @9 q" N# q1 O5 d         if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);6 F; a) S% K r5 G4 d7 b, F; ?' f

" x/ n" `% V% e0 c4 L

& G6 t, b8 O% _" }, J         return true; & b" X4 `9 ^! B! l

% n7 v5 X: x- [; `9 r

# d+ p) F: p& r8 Y$ O7 e6 Q) N4 O7 U     }} ! T3 |$ B& F _% P6 V) |2 y

5 H/ ]9 Z& x% y- e, F# n" P* }' |

' E7 B: V6 H( S4 `* a1 Y! D8 P4 e 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:1 A Z3 n# T' O( i% v6 t

) ?# t$ w, {2 |2 |. o( w+ \

' _2 h% b9 E8 j! H2 k' T: B <?php# {5 |* n6 @4 a+ n, {

, m7 T/ V5 z$ ? W# ]% r+ d& I

- M p+ K+ S {$ `; s9 Z6 L8 Y     function is_allow() { 3 G5 Z: s* _; N, P6 g

* r9 d# `1 Z; H" r5 h, @6 n# U- N

. }! K) a9 v6 K7 O         if(!$this->fileformat) return false; * P% M$ q, i/ T) _. J

! {9 @( k8 N: W/ n6 i& b6 d

7 m# l# D& N4 L& t         if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; 3 ` F# a3 g2 a* y2 K8 ^ B

( l: d' j; ?9 j2 \- C3 v

7 w9 o: g- I" B/ C. T4 m         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 L7 u: k: f( I0 O' s

0 [6 W6 r# ?9 ~. O1 s/ O

* ^0 d7 b6 ~' G- D         return true;; o& @9 X' J) [9 z1 T' v

" J; a6 ?. Y7 V* b" @$ f7 g

' J- H8 J7 C1 s, w     }/ {& D- U, M( x( q* q s

4 T1 H b$ S/ T' \9 F: Y

- @- j I9 N# r0 N6 l6 ]3 v 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。3 Z2 @6 T+ x) V

K. F+ g5 Y+ J( |, R2 j3 l

6 f/ F- n& d/ E 接着会进行真正的保存。通过$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文件。 % I1 t5 J& c. D8 x+ T5 p9 [' \

% u5 r0 w( ]$ ]( k& b J; |8 q- ~

0 Z0 X- |+ K3 D' K& x2 _& i 漏洞利用# E- P2 q" o9 {# k. `& j

3 i5 Y, b" S+ s

( y$ b& l6 y$ W& ? 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。5 O5 ]. r0 r( S: v) p7 v B) L

9 m$ `6 u n2 J$ I7 I

7 O! d& o7 c/ y, [2 a o   6 ^4 v) Y4 |) G: Y2 {& T' `) Q

& a/ B- Q# p) Y# O6 Y* p

7 W, y' l' G! E u8 w n4 q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid 2 @8 ~7 d" y1 Z! B' {" ^

6 j/ ~& ]3 `3 N/ U! L$ |2 R

" ] t2 @/ Y2 u/ t8 r% [, e 不过实际利用上会有一定的限制。" [6 |/ r. L' c8 J# o$ a7 w

( k' }/ P7 J( U/ H' e: E6 q: N

. d0 [3 b) z) Z0 J1 v4 X 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。4 M7 Z* B+ i6 N1 K

0 N6 v6 i+ R+ F5 ?3 E1 Y9 ?

& J. k q1 k. I+ B   6 \! q) \2 S2 O

* V3 k4 c- T# u' }7 @/ G! {

% J; Z' R W- i 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! A% q7 b; b4 N" P- e, N

+ R' I G7 U! _7 P1 p9 \5 e

1 H8 V7 q$ _: T5 c# e 省略...$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]);省略...6 v8 ?3 l+ s$ I L+ d$ J7 T" p

6 f7 A& b7 F k; ]# j

+ l0 h! \5 Y- e# N2 {, y 因此要利用成功就需要条件竞争了。$ K1 s2 x; ]; M, D9 m5 P4 U! g

4 @2 W9 y m7 i0 M- G

& N" ~1 q" m- h 补丁分析 " a- w* ^& H, I+ X9 g

& s4 R( @( {& `1 `

: i% A$ y( e/ n$ A& V  # K$ V! Y1 Y, t5 T1 J

3 ^% ~3 I+ i& x: H# v4 [

& J7 z8 e& u" b1 U2 c 在upload的一开始,就进行一次后缀名的检查。其中is_image如下: % H( e; Y9 d/ ?$ R4 c& `

: w, V* ^: P0 K5 P

( R/ {9 ^# o i# H function is_image($file) {    return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));} $ U1 t+ o0 Q3 P

0 [& e8 N& } J% N" y2 t

4 H- b; V3 Z7 C5 f; q; r  $ w/ a) J; \6 p. v& U: g( Z% q

6 n" q$ Q6 \5 S6 }; g% f9 o

: u1 @7 F, H% ]( M7 t% ^ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。& A0 d# r5 ^( ?' o3 b

# K9 G, U# a E! G, {0 u) x( L

6 Z G$ M) Y$ d t3 R7 w 在is_allow()中增加对$this->savename的二次检查。- f! g2 `- C( Q& x/ G' b- ~

0 b; R. G7 l$ N* W9 G$ [

/ \: ]. G1 t1 H 最后 # c/ Z1 T Z; n% k; w

- @" n1 ^! w4 |1 `3 d

* B1 _7 u4 b0 o3 g 嘛,祝各位大师傅中秋快乐! G. K# h% u* s3 v9 a! h

% b$ S2 u5 k2 P9 Z2 u- |8 N- j

7 o. \9 v$ m9 R2 x  . D. L- l* l0 R6 i. B6 @) w" [4 x

- T1 ?9 ~1 i4 d9 c% V. L
回复

使用道具 举报

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

本版积分规则

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