4 w# i) H2 K. n+ b# V: t6 x! h z+ H( e$ T
, Z" [; M" N6 B. N" B3 Z9 [
0 f5 m4 @% L& E0 T+ a 前言
7 d8 t6 i( M; Y! V j6 [" H3 C; A; o9 Y1 q( a
/ F4 h6 L1 A: n' w3 T, ~; T! F
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。' S# r/ l9 V! ?; N/ J
( ]1 R4 [) i1 }* k6 g! q
6 C! ~+ w0 s, R; K' V
+ |+ K/ E0 h: V7 r, V7 J: `3 v9 s : S \ R' o4 r# q0 R+ _- |/ H
- W, w/ L' o: c% q P+ l! @: m 漏洞分析" v: F# e3 h- g! p" N. n: W# k( z
# ]) T9 J/ N0 n) \4 f" K+ C/ ?0 N2 {
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
% q4 ?0 C6 E b8 }* l
! B; U2 J2 c8 u; z( C8 H) _. W
2 c" x4 i9 r3 r' A. D0 K3 y ! L. t- T0 b+ F0 _$ _
$ D9 \4 P" y: h5 x" Z& X8 }/ f
3 |" U' A* |0 @3 }9 a 对应着avatar.inc.php代码如下:
m, K( e+ Z: i8 O1 o A, c, G2 z
; Q+ x$ |/ n, d! q5 o; w2 O. D9 F- f- g! M: s1 w2 k& ~
<?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) {/ ^; R& h) C0 a) o( ?6 U( Z, @
! Q* Y* @# u3 ]+ W9 v" J/ y3 x* }* ?# B9 M- y+ b
case 'upload':% r5 o6 o$ i: q; v& A: d7 f
2 o7 Q8 E$ y! F: K
9 i/ B; J. V E# P- F Z& M
if(!$_FILES['file']['size']) {% A4 T1 y- L# z/ \' C$ K1 O* V
v9 d+ A2 g, Z7 q4 {
3 ~; x2 O8 M# @1 h7 ^* L if($DT_PC) dheader('?action=html&reload='.$DT_TIME);& D# \. ~# h! B: l. ]6 u2 P+ U8 N
1 H, a/ I! I% c' t
, z0 N `( F1 o- Z; X2 o1 H( C2 I exit('{"error":1,"message":"Error FILE"}');
2 ? D" ~# D0 G% `/ q) S
. x: A( L" D& {2 \+ J
- N: R6 U! g4 R+ E& r } j/ G( Q1 G3 C) M/ s! H6 H
% G# V# I4 P5 [. o& @' \8 U# O' x5 s4 e" h! F
require DT_ROOT.'/include/upload.class.php';
/ v+ o7 b/ L% I0 S% p: E
/ m! w/ s+ b( ^7 j3 V
( x" m( T6 e: Q: Z5 J3 X* [ 1 Y9 `. o- j8 H4 t, U' @7 G
; e- H# W% \. U/ {$ \8 O
3 J( a4 ~" y. A6 G/ W( {% D8 K $ext = file_ext($_FILES['file']['name']);
) C2 Y# M) j3 b
; ?! c8 @' y. d4 V; n5 z1 @, b7 p9 s4 x$ L9 `
$name = 'avatar'.$_userid.'.'.$ext;8 J) G$ L" p6 O' Y8 m( p8 q+ n
9 S9 F( q; J# ]5 q! S, U
0 R+ n: t9 o3 M) ]3 @' L! Y7 u& a
$file = DT_ROOT.'/file/temp/'.$name;2 ?3 e. E, w0 [2 c$ I- I
- j/ m2 a) s- y, G! ^) d
3 ]8 O( S: M g! [/ x, n3 e
9 o# L# v$ c& v6 H
- ~$ Y# {$ m& f- j5 [1 i8 E9 ~4 O, C7 p& I0 a
if(is_file($file)) file_del($file);/ t5 l% A* q4 a8 o/ M }3 C5 M
" K/ G! s: Q1 \( ]* E
- W" m. v5 M" B. d; V. O $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');* e( |2 Z6 h6 S7 G' }
) L C f# O4 N
3 f1 I3 J8 f: e+ K, O6 n' G7 n - ~' n: U0 S' V$ w
& {" m6 Q; u+ o1 K2 m; I
/ Q# q. j& ~3 }5 @9 q $upload->adduserid = false;2 @& ?0 t' ~9 s* o: F
4 s+ ]; X# L. L( j8 F, ]6 V g- V( n# w; A+ \' f
2 I# V2 v K% X a h' c% f * I5 u$ I( S4 N9 u' B
7 h+ m+ ^4 l8 j8 N) C* m if($upload->save()) {
5 F- J4 _1 [5 L- M ; {% f, B6 x1 Y) U: d* Q" N" c
% m2 I0 y9 J' D% F ...
0 I' a+ W# O B d
' q4 k! ~( l) `2 _ S1 A* b: |3 K: Y9 r7 Z% b
} else {
& \7 E2 t# }- ?; x" q5 U. j
3 j; V) c a4 Y1 y$ r0 ]1 z0 L9 L& ^; n$ Z, Y2 ^
... _6 t# j) | e1 q
1 a1 N: g, S/ X5 T i; ?' u- z/ P6 |& I( h& i: h
}
. V6 {7 _( \: T5 S 2 E1 W& U0 b0 @) n; m! X9 y
% U/ ?3 c0 {2 g6 l1 u. _/ P
break;
6 w7 `. w/ I% D. [& e r: W' z. o7 x
0 f# g. Z( v. K" f# w8 a* x
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
# b; l2 _, k) x& Z/ y2 B( U
' _/ ? L T# |, F. l
3 @% h$ D" j2 c3 B; }, f upload对象构造函数如下,include/upload.class.php:25:
, z2 Z! S. }+ G! A( T( i8 Y" U# t1 y
w1 M# V/ S2 u" f! z6 e; m2 M. L' ]( M) }2 g2 V4 k
<?phpclass upload {' Z* u m' g5 h { _
9 A! l- D* e) Q8 T
7 y* j; x, i$ o& q- j8 d
function __construct($_file, $savepath, $savename = '', $fileformat = '') {+ V+ L5 z3 l/ m( P% q4 s
/ D$ Z9 M9 k5 |3 [0 [* ^ E
1 z2 I; H& B* U6 R9 R3 N
global $DT, $_userid;
" K* b3 W# y2 O7 W ) g" b4 s! I' H; v% a
+ L m h8 c% m
foreach($_file as $file) {
% ?& v5 ?! H+ h( l# \% e; U9 i ; U _. w8 U* S4 T2 m8 P
( E; c% d3 u- h, r/ L
$this->file = $file['tmp_name'];
- s: ~( C2 _& F | `
: j% S1 c r/ B( c. C6 C4 z4 E) ?# S+ H. @; C
$this->file_name = $file['name'];; X0 {% X5 f' F, p! ]* c
% j. R; f. C2 ]0 J9 O# n
( }: n8 @5 X' ]" n2 P8 F $this->file_size = $file['size'];, n: c9 {$ {6 L G
# U: E2 ~ H0 ]8 ^
- Y! `- \% ]2 e/ u) x
$this->file_type = $file['type'];7 @) s6 z: Q" O" K A, `
. p# A. _& E# T9 v O
, W0 e7 M+ q9 n% L2 ~* y $this->file_error = $file['error'];
& y7 x8 R( e) M. T% {7 ~. A& W: C 0 t, |. J3 A+ i8 f: w1 v+ F, K
% x$ S: s8 w- V. P
9 I" i9 T6 N0 J3 L6 _8 O / M& C8 |% B4 R
6 L7 o% Q m Y, H1 a/ ^$ P% D1 f }& L% n4 T9 F0 V; k- f* M' t0 r
' [& `/ K# J+ z. |, [
$ V: w, d, L( b( e$ b7 X# A
$this->userid = $_userid;
# Y$ _; a/ g# z# U
) o8 O2 U" m2 x* M2 A+ y
& k1 P9 v' Z9 f. `" F. u5 b $this->ext = file_ext($this->file_name);
5 m9 K& t6 H% |5 c4 F; R 6 ]' T S9 ]& x8 \; c, Q1 R! h ]
( Z5 e' m) h6 o) z $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];' t+ d1 H9 T& y
/ D7 Z/ V6 i# W. u7 f5 Q7 j2 T8 l: R2 n9 m
! K( {$ P- L3 O8 ?' L b
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
% X- K) q: N2 w- n & i1 f, Q3 p1 h/ f( {& Z
$ D' h2 h/ h) @ R/ k c9 }, D $this->savepath = $savepath;
3 W% R2 |& s+ v. ~- `9 V
6 F& Y$ Z! r3 z
7 N$ Q2 E' y% }$ m: e! _ $this->savename = $savename;3 I) x% ]) g+ k5 M7 B# ]& _" G; e
6 L) J( m! D: K+ E
, ^ A. Z9 x5 z }}& N k$ l, m ]" z4 y' }! V2 E
W5 u4 t& l5 y
3 l; I7 o8 K/ [% F0 E3 S 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。. W+ l, e* P( I2 j
# `9 P1 @$ a6 P, q5 A8 K- c t5 |/ J7 d% W
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * Q4 F/ }5 b4 G0 ~7 N1 {$ y( ^
) K& I9 Z% G" N+ z: g8 q
+ j% y+ l9 Z& `0 A6 T $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! H) V+ V& `/ ]3 _6 l' [/ {' s
6 Z. R0 H, v; n/ L9 ]/ T; ^1 b) ~
9 |4 P$ d9 G$ z
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
+ V# l( W# T& B3 k
2 m6 L1 @/ u5 S7 ~* L2 [ J" L: B" l7 y8 u8 l: G% L
2 n" f1 c* E: P. S! T; j% T6 c ! N! l$ W- S! y# U n
9 W, s6 A8 D8 c; e7 l 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
5 ?1 N" E+ a4 G- i* A0 w! \ 3 ^7 m; \& f- d* }4 O4 l i
& q# g, R# B. I6 ^% T <?phpclass upload {
1 A/ G: _% b+ l , i/ |6 O! G6 q; p2 d+ h# p
& I- `# ~$ A- Q7 Z7 B
function save() {
+ _2 f$ s: {% G. R. f9 o% G : |& A4 v" t5 @" R4 w) P
- W9 f* g. E1 j/ y; h2 w include load('include.lang');
% {3 Z$ s) K6 u3 k+ U0 S* \5 f1 a 2 |& Z6 X6 c! E( `8 S" v3 u& ~
5 W& p4 Q7 a4 D6 `; X
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
- }' K+ u/ ]- D% a
/ i/ Z' Z+ T8 n4 O/ W3 f3 I7 r6 j- c9 Q0 y
( m- o4 U8 P. w7 o6 h) T
2 u% h+ ~* o, I0 ^* ^& e, n; l% R4 z1 v5 Y& L. M/ ?1 ~/ |+ A
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
H, ?" q$ R9 L , t0 C1 \0 X$ C# ?4 B
: |7 r! m6 P3 _; @3 V2 b
& B# G! ?$ q1 N) M8 L6 W
, M2 F2 _; c+ l: h
' k% s# y+ O0 K& p9 ~6 _6 w- y if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ Q* {: O! n& H* U5 F
* j1 A1 p2 U" X6 }* ^9 C9 q/ s* p% `6 p8 f% K6 Q1 [' G+ Y
0 g# O( |. k/ ~; n8 Y& u; Z
9 N6 j/ z, z7 [5 g: Q3 Q4 l+ j+ g$ Q" R( v
$this->set_savepath($this->savepath);
( @- \+ n/ E- F. ^# G
8 l" m: @- _# B8 h4 W( B* P1 J
. I! n! ]! ^2 N! B) j $this->set_savename($this->savename);
- m6 w, @3 e5 _$ y! B( l 7 N! J$ g4 M( u$ G4 V5 o, B
4 p6 x' p. ^5 j1 \9 M, E/ r ! u8 E& a8 h/ a9 E" z. L! Q
) L* Y2 x1 A" s
9 F% Z$ }: M, H8 @ if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" k+ o4 f7 a" E, z! i+ b
1 d8 o# o8 [; K6 _# x- c j4 |
! L* b4 ~9 T. u5 Q# @) o! b! m. @1 N
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
4 E; x( y5 v+ H# o% p
- M2 m5 i" N- M2 A1 `* H, y X" q
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);( J* X: r3 l5 }
& [; D2 N- U# L$ E9 E) Q& L: R2 x
8 s* Q; Q/ C: i5 v2 H# s
# M: m& Y# n& p 3 f' W( G a( |6 w
4 t7 L, { I$ X) L' x y4 o+ A' M $this->image = $this->is_image();
( Y7 D# V' M( f; r- a/ ?# t * m7 a2 O8 X b' P1 z% ?1 |$ ] e
$ ~/ z, ]9 b9 z* ` if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);6 K7 E1 V0 b& s+ Z' e
- A* }4 j. y3 E: d! O' Q
1 w& r1 s- l0 g- p, w: M- S- t return true;
" _( U" f! N- D* E( b
" `, ~3 p1 D0 L. K! x0 Z1 v5 w! W+ r8 {" D: F" O0 {7 g& u
}}: V w# A) s, J# b# w3 D: `+ h
- {" L; {' z2 o# \' A0 Z9 a. q
( F3 V' c0 ^; i3 @; z 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
8 H. |. `( `! ~3 v/ `
! z4 a& |$ @6 [- ?* H9 d Y
3 |) W9 a* `, `0 p3 B <?php9 G# x. {1 k9 i, c9 @
; d Y& F" Y: T( Y" X' J, E( I$ ?6 f- g D& P8 r2 W, c2 v
function is_allow() {5 E& v8 x- |8 ]
' g! C4 c& i3 I3 `
0 }1 P0 c6 _* y- R$ y
if(!$this->fileformat) return false;, M6 |5 X( r2 V' b, J' x/ v4 n
8 S& ~ U! M0 P f5 f: d2 [1 ]7 r* Q% l. D7 R2 Z: {, ?& s. G
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;7 @3 j) T' S4 t
4 ^7 p( F- ]- D) B! D5 X- P+ d0 H3 C0 W$ r; p3 @
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;2 M6 I+ I; k3 |4 t* N* b
' R( u/ d1 H" R6 o! _1 `- ^2 ~
- [* N. e$ Z1 r* R! Z a. } return true;
G1 N6 D0 c* i* r4 V
9 Z$ X s% n5 S, b3 R
/ Q* i1 H9 R5 C3 p2 @0 n }$ N# r% H0 _+ Z, J; x8 l& h" s
0 s3 Q% r, U9 i. _* V/ c" f/ y
" C X6 a3 _$ A+ x2 P' n 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
, U& d& i* u& U / n( ? T8 N+ X+ t& `& C
6 a$ }: s* Z$ e$ g0 q) E1 |
接着会进行真正的保存。通过$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文件。
3 n+ K9 j% f5 H4 p1 R6 k3 h6 i
0 S/ Z4 X( l4 B/ V4 e" P+ _
# r+ e% s0 ~- G' i5 j% `" F 漏洞利用
9 z O0 \5 P4 l# s/ h, V0 M2 ^
: C$ g5 Q/ h# Y2 E; `, J1 m2 C0 Z+ E9 |& c4 ^+ W: `4 `
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
( i& ^# y* M. j* O+ c6 t0 j
; F, }( [' s4 u! P2 k5 V1 S: N2 f M) T, \* f
- E6 G, n+ x1 F1 i% E0 i
6 U1 D9 r7 w/ p- }2 X* G+ W3 N# V+ U' I4 T
( G8 O# w6 {4 y
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
( c1 \( \; ? h. {: U
2 g) u/ N6 d- ?* m$ [2 [+ S5 w' ^/ D
不过实际利用上会有一定的限制。
9 b7 c4 l5 E4 o- z& z/ A6 u, A1 t 4 `' ]6 T# [. Q. r( b5 l0 ~ ^
2 W9 f; U9 z" w. \
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。& C3 x2 s/ Y4 C! Y& r6 S- P2 b& V$ I
, W% e9 z ~5 J; D b
7 m9 P9 |* q8 C
: a0 q+ T( C3 f: s ' l4 b H6 y$ A% C' O
0 o4 I. a( q v1 t N% y
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! s T. S+ I9 D# ]4 F
# J3 z. z9 C& G: h
$ `; {" F9 W, D; n, R 省略...$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]);省略...
! C( @$ R* R& y, p% K. U 0 y7 z* V+ k+ N, [. G+ H$ V: ?
, T/ x6 u, l' `" K0 u6 Y
因此要利用成功就需要条件竞争了。
5 S& O; h/ ^ @' [* M8 l3 [ F
% m0 k6 x6 w; P/ H, [) [
6 P2 a8 M* N) ~1 t* ?% J6 N 补丁分析
* B4 a! l( Q/ K( ]8 V! T# ? }# G5 X" r" J& `
- f; L; I7 d& X% U% |& V: A+ U
" W3 m4 g0 J8 }
1 r: k9 @3 ], I7 I
: @5 N) ^ w8 ` _, P) T3 F 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:! x# C: C" t+ _. B
. h7 q( g8 ~: O" K, Q' y/ {3 M. b/ m% k) j) @0 S% Z+ Z0 E- D$ G* {
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}" G( H, L. `& m
3 n4 ]1 j! T; a$ r& G
8 f! O, G2 C- A6 X
) a$ R' a( d/ G5 g" T& J* k
0 b. _( `. c2 o$ M' r( t0 q- y- O2 L7 E+ k) {2 o# P W$ [
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
8 b9 k* e. r# a+ ^4 _( F
) \( ^, B: B# A; Z4 Q- S. g: N, R$ M; {+ W b2 {, @, f
在is_allow()中增加对$this->savename的二次检查。
4 t7 u1 L' c6 |1 d! V6 f/ ]. l " w q4 z7 _& |6 Q4 G9 _% `
# y9 O' X0 |5 ~& k0 f
最后
+ K7 H( F- J5 ^5 u$ [9 o+ B2 q: q) ^
! p! ?3 |3 C( K. E, s
嘛,祝各位大师傅中秋快乐!8 v+ n+ x, X3 {: W: t$ L" }& |+ x
6 q5 w9 _3 F% ~+ o4 Y* \
, _4 y0 S, h+ J( v" S" _
; z0 h8 ^3 x: v8 D* M9 o* G ( Z4 C* X; Q( [( v
|