: x# D; M& h5 R) q3 `4 \1 S
4 K& a) P3 }7 D ]0 a
3 T7 R( Y6 \! r% a2 W1 n! F) z/ `
% g/ G; S- H; N6 i* ~; o V
前言
: ?, v% ]8 h. z4 r, x; @* Q7 N0 |% A$ T3 X& N
# F0 B* x2 g. c9 F# j
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。+ g2 a' E1 [2 [+ L
9 k! s% n7 @# J; S9 ?& P
3 [7 e0 V9 e7 t/ S; {) k
" Z0 E2 y# v5 G7 f
' K! b2 W% J" F
9 }4 X- R% v! o# R' k# } 漏洞分析7 \" p% t `' E( v3 j
/ ? t* @$ c6 q0 m) Q; [8 M+ f% j7 h& }! A8 {' r
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:$ t: ~" \4 b: C
3 @# Z+ O, l2 t6 ~8 W# n& E8 F, @6 Q/ w% w3 T$ `
$ M4 L. T- p% K( E5 _1 @6 u% `( n4 c% `
% r; q* K1 ~! l- b4 ^6 [, w
5 l/ e) R i4 K( _! O 对应着avatar.inc.php代码如下:5 v' ~: q% ]6 B% m3 P
" C9 A9 @+ q( n& e. e, C
/ A* R% A K+ ]2 ? <?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) {' ~# J, Y; f. ~) F; \8 b8 s' H
( @& t" C* C6 W4 g
5 E. L1 o/ K6 _, w/ {2 c case 'upload':
3 U$ g C+ P& f9 v6 ]$ m/ c2 H2 K
( j- W* l+ G3 d f& O% n- E: M4 `
} C4 M8 {9 |4 x8 b3 `4 @) p if(!$_FILES['file']['size']) {
) l& T" h+ W' y& P! {* |4 m
3 F3 |& R+ v+ u! @6 x) [' ?9 O% q# g1 J5 S' W) b% `
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);3 a; ~# X( w! q" H
$ S& k! p7 h( }& u' S6 x x
8 _0 n- U; s' E) j. b exit('{"error":1,"message":"Error FILE"}');5 Q7 U4 v& _8 K+ `: B4 i
2 @6 n+ M1 v0 N
3 C( w; f2 P& E/ g/ K" l }
& x l0 G# k3 M2 k0 }- j 0 F" }; R7 D+ i: f3 m9 t( \
$ L( E5 C& i9 o# b. L
require DT_ROOT.'/include/upload.class.php';
$ U4 H) I+ p# f- L Y n1 {0 B: @1 r7 Q
* z# s0 D2 \4 }
3 |( T' W) y8 T3 e7 @% N6 P W& f! X
0 x- l$ T) Q$ \% Y7 v& |! C0 f
! c* Q7 F9 Y( P, n2 ]9 V( V0 w $ext = file_ext($_FILES['file']['name']);
5 j* E$ z" J: _1 b+ |" T 9 y0 i; H" I$ h. z/ W2 W
) H2 s6 _6 W0 q8 ? $name = 'avatar'.$_userid.'.'.$ext;
8 `7 l4 i) s' p7 C5 _: f' k& j : G9 g! a+ d9 H2 [/ [/ _
, t+ q( n, F# a* r& \$ D9 @ $file = DT_ROOT.'/file/temp/'.$name;
5 B; R, ^( F" o% s
7 D' C6 O( j8 b1 ~1 u
4 _, I3 |3 S7 d, U9 ~" L" E- `
8 J" Q# K6 X' \$ ~; W
1 ~9 v4 a, K% ]& p: ^! V Y( x3 V6 f$ J" v) d" x- U9 V& C
if(is_file($file)) file_del($file);
* x5 B: Y7 a6 k) |0 B2 B $ b( O: }$ t$ }: S o) n$ D
; q0 v8 n9 L2 N+ m* n1 K/ S $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
$ ~& [3 J* G* E e7 V } 2 U2 A" b4 A' p) `
3 p" e i7 O6 J' S" o0 X* V
- g: G1 f* G. P, B - s1 H* w9 `; Q- I( S
4 I" I! [4 O# V3 p! [& c $upload->adduserid = false;! _$ j2 G1 @( J. ?! |; X V
1 X/ R2 `0 ]+ ~+ B2 E" F% _& o
" K t, k9 N% Q+ g' t9 f" {! P7 v1 H% |
. N/ T4 ?9 V) j) B" _& B) `% d# r, s
Z' a: T* Y. ^; e
7 g+ `7 D5 `" l+ j* V4 O1 [; a& u( S if($upload->save()) {
: r8 q4 u/ b/ s8 h7 `0 C
% w4 ~: E+ v" H! D5 _4 e0 ^* @
: v1 w* M0 y1 D; g ...4 D; `: T7 z5 P6 b5 l* a, j# B
% |4 F3 V$ T: N# g. Z0 n. G1 G3 U$ f
4 P: s% G; h, G9 y8 }8 d( D! \ } else {) @* W& g5 N3 s! V( P, n$ H
: @$ o* U7 `3 S4 B s2 ?6 u
( |% G$ R: y, _1 X
...
. x, ~" T9 G, A( F, \# h
0 |+ d L0 G; U# q# q- [
0 h! y2 F+ @( \! I }5 |1 P1 w* }9 ~3 j1 G2 z
" ]& c7 e$ Z6 m
4 `) b/ v) T7 C0 ] break;' d$ u. Q6 N' V Q( b+ n. n
8 e q4 N; F* J4 h) S8 ?
1 P8 _/ E6 `" t. R
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。' I7 z, [# u9 q
8 R4 n4 X& O/ b! T: l7 Y4 _
9 A+ F4 \5 V8 H# E upload对象构造函数如下,include/upload.class.php:25:0 }$ Z1 I; w) x1 T7 f' d
; t" f5 y0 G% K2 [; v' x* \
! h( N/ Y) g T6 ^0 | <?phpclass upload {
# E/ d Q, d! ~" N Q: j
- A* |2 Q# B3 N6 I8 u
) _; C3 u; Y* A9 Q& ^: t function __construct($_file, $savepath, $savename = '', $fileformat = '') {; Y! D; K$ y) [) H) a4 l: u
# x" B0 q3 K0 k* {; [6 H4 M/ m6 O( g
/ g; u( E' H9 |! Z: Z2 F& _1 m
global $DT, $_userid;
+ |$ l7 |; w' f6 C) F 1 J2 S& w; p& v/ f: ?9 j
! k* }% S' `7 s' s' H foreach($_file as $file) {
* E" n. Y; F K5 c# V ( E' Q3 H) f: T6 G5 o
: Y* {- ?/ c5 w2 S* ?( k2 [ $this->file = $file['tmp_name'];0 W! J2 U6 G l# M7 u
) f: }" l' g6 q' q4 }
, V2 n1 x6 M6 M0 [. _ $this->file_name = $file['name'];/ y3 G. M+ F# _
: r, X3 }% E! p4 d+ R
3 T8 V0 g( j3 v8 f1 A( w. Q6 w. R
$this->file_size = $file['size'];
; S( W* E8 c6 m1 `7 k/ }2 l ' M& ~( P7 E3 y" B/ ^/ A
( ?4 l2 F- D5 @& a& M. } $this->file_type = $file['type'];
' ]. ]. O! ?" p) }9 G) ?# H . i/ [1 q$ I) P8 l; K5 z8 J0 j6 A
- \, }6 M. T1 u
$this->file_error = $file['error'];# ?% u: [' W$ d- ]2 S6 d
) N Z9 y- u0 g! v
& i A, V2 R% Z- U1 z 7 z4 W9 G- Y& ]$ j
" w" h2 t. A! N- K& f& d! E( `, n+ D- W8 U& j) h5 ~- E; O8 Z
}' u( m) F; v) {* A M' S* w0 {; C
8 [& J# F. |) e
2 i, f& O; r7 E $this->userid = $_userid;, [5 [1 Q, A l
3 k! Q! C( T; x5 }
" S& k* ~4 d6 j5 N $this->ext = file_ext($this->file_name);, W# }# H3 W) V) |+ ]7 T
, c6 j/ D2 [- q7 R
% ~. k/ p$ {' O $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];: i" l4 Q0 s I T$ f. B/ Y
& x+ f1 K1 G9 d8 Q# F
2 I/ H# w: j# n; P! G
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;' b Q& R! V- m6 Z2 \
& ]$ Z. S4 {7 O2 @6 Y' y- ]
$ P8 L' g- d& I9 `1 G. y $this->savepath = $savepath;8 ?/ A r( j$ q- z' o* F4 q! q
* j4 W. {0 c2 _; Q, @9 K; P' [7 f
7 |2 {" p/ D: B! s4 I $this->savename = $savename;. G/ m8 p6 [* m7 u1 \
* R+ V: j/ g! N8 z( w) E9 m8 N) X& q- m. ~
}}$ s% }: o- c) x: U
- e& U$ G0 t) J' N" C3 W* Z* [- {
5 `9 p* n+ G, H* z' Q$ h
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。; p7 q5 G- {8 B, v* E
' V2 ^, T( \0 q! U y8 p; R0 c+ L
6 Q; j3 _8 u5 D5 J. H 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ; |9 Q% j( m6 Q1 a
8 } |5 X0 j' Y7 I" @
( g1 V7 s% o1 p% W& r+ ]4 @+ 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, F3 i( a& } c" M. N+ A2 y
( s+ a, Y5 H+ i5 u1 f9 k. `2 D+ o: a p ^) W1 _
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:& X. H$ ?0 t7 ]$ g: T0 @; `
2 D4 f6 q# D- ]) o$ X0 L7 N9 c9 p0 R# @7 X
! i5 m4 M' H/ ^. T- E" a0 ^2 E2 E% `7 g : H! y/ h3 o* R) S0 k
0 e( H& y. n9 a* C2 w
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50: x! \! I' c1 A8 |/ K
) ?. g* h4 t8 d0 h3 J; x; ?1 W. w- O2 J" U3 \6 W) V
<?phpclass upload {) f$ L3 p5 j7 W
" X+ _/ n) C% ^5 ~0 S
, u3 C) _" D7 ~- L/ i$ I
function save() {
! I7 d: Z0 s4 g1 {( ?
" a$ @1 \ ^% Q3 O7 ~% H: ~8 j8 U. Q! F1 i; v% Q
include load('include.lang');
! r* J7 n- R; y- I/ K% ] 8 z; Z# m; {" i- _$ E# e
1 L2 u8 P1 `2 e. C4 f if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');( `9 ^! F( p! C# ^' v" J" q
# j5 D# g7 l/ h. |
' @0 p) `9 R4 f8 r* V1 x& n. r) g
6 C3 I8 S$ V) l- i1 q9 t8 F* H3 X1 u
8 _' |. l. ]. ]+ `; T: s, J* g1 z, W, a6 d
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
9 D _ B6 V1 l1 x* a" G& U2 q + R1 z( B; K* d/ j
7 }$ y1 g5 E/ B2 i# w$ N5 K
& u0 N5 S; p) h2 N/ g, h
. i4 i2 J1 V6 r- ^ O
/ s. Z D {: B* K- A
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);7 Y6 y9 T6 p; Z$ N, R' _& f
2 P5 \" ~8 \: I7 _% N
' ]* k* z& Q2 n5 v9 K: Y ! u" j( q) _7 M5 c+ ^. C
3 G1 A2 W, ^) R; F( b$ C, N/ h2 l X4 X7 T7 }3 r
$this->set_savepath($this->savepath);
- d4 n+ h5 h. A4 n
+ Q. n9 ]3 Q5 }7 Y: I5 |
* j# d. {6 t5 R/ j $this->set_savename($this->savename);
$ H. y; d: x3 m! C3 q v
7 g; }% ?2 ?3 C7 N% K. ~7 M% b4 K
1 x" u7 ]' ?# {. g/ X4 I: Q& J & X6 h* }8 f( D# j" b6 `
' k Y# P6 X6 ^8 a. Q
4 ^; e# B S* v8 f if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" h6 }& n+ i& Y" M3 y
3 j0 T) [/ w8 I% P, q7 G( B- ^+ Y) l- p! y" n. G# _$ s, b; U$ g
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);: T" r3 w% C' `% f) \
+ h* Y6 n- G6 \* B1 F! h% N, k( d- j m+ H, m L9 z
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
: C: K/ I4 W$ a# q5 ~8 [7 i ' N- ` l& K% ~; V% p5 X9 w
3 d! o3 x4 e5 @+ S9 t! b* ]9 w; T 7 V: G+ @! P( s6 S, I: N
+ z1 r/ Z- H' g! F( B
# e- \* G3 c- g4 K1 S9 k $this->image = $this->is_image();
5 m5 d0 f" j. U5 }2 H
: w( V( v$ m+ \4 T8 V* j6 P" p0 J: r4 I+ W% _" d: v0 s
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);$ S: a" T3 w8 |: m" M$ {: |/ N$ _
1 g V5 Q+ e7 k# K* @; L4 z) V
Z4 o' y U& E, P) D1 R
return true;- A& J' z& Z! C! D% N
9 Z. o5 y( \5 i8 L
* e* y6 j1 t: P5 }' `& s+ B }}
) ~9 F% W8 k% G: P3 V h
2 L6 h0 x* `4 k5 G$ m& d" Q. k; O. ?; @% L) h5 _' W% g
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:! l+ S4 R7 U/ j
2 P) o; ~9 ]# F6 S
1 J2 C6 H9 i l: R* ^- ` K4 p <?php: |0 J+ u# P; c& Q- ]* w" n
- ~! m- |& i8 |" X( h( E
8 N8 a9 r$ O, A3 d function is_allow() {1 f( K6 o3 ^2 W* { ]6 W! A: {
# c% Z! }, K4 b6 l# W9 L, `& z
" i6 `9 s i' i1 B0 Z% | if(!$this->fileformat) return false;
( \9 v5 y+ Q: W1 w, t
/ n! q6 n, A. w/ |: E( i4 X* m d9 J/ N4 s
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;1 W' {; E" R& N9 c$ d
8 |% c( U( e& O9 ~& v6 E. }
+ T8 t- B$ v+ c7 J1 u* Q; U 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 p3 U e, _: B' ~
% W( ?: }) u% n8 `$ W( w; w9 |) C$ m0 @- ^7 l0 a z, a
return true;- t2 c: G# Z' G' B
$ T _: ^; K) M6 n7 t9 Y h
- @! ~. @& O& P A }! l6 t6 m0 k2 u2 d
8 u) P, S! N, I1 n% H* z9 d! e6 l% W; q1 P& b/ V1 I
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
4 e$ A( t8 ?3 G! ]# X 1 Q( q" z1 R$ l
9 `% p+ L- [9 {% i7 \7 R
接着会进行真正的保存。通过$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文件。
6 D- z4 a I# P
, F# J9 w+ r5 E* s2 x$ R
4 @( |. }& ~0 C2 w' S G7 U 漏洞利用3 v& {5 }6 j+ W; `- d' B8 ^
- x1 o m3 V* d4 v# Q& J
& C% O9 s6 }* b3 ?5 a% Q+ h$ j 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
+ H3 a0 c& B4 d* f% ]5 v
6 t* O9 _ |. q* u4 j$ H
5 ]7 c* z. q6 B( U) y ) E* H+ b6 R. o2 t/ z
2 u3 P: A8 P0 u/ g3 R/ D8 K
! R) e6 I! `5 h% @2 ~3 Q 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid$ H5 l% _# T1 D8 D. t
' ]: B/ P4 O6 j; \8 H) D; Q4 G
! @ }6 I" T' t+ B/ d+ { 不过实际利用上会有一定的限制。1 @9 k2 F; T$ h9 Z7 A9 R z5 M2 T R
' I1 D, I. G! ?' d- b2 _. X- W
9 s0 x1 E; L; t% b* I
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。! E9 S/ ]4 C0 c) w4 {" b
) c$ e7 W0 P t1 o! }9 L
, }) q3 Q3 Z/ D0 N& w p
) Y5 `6 w& u; u $ X7 H+ W+ I2 a, w
a" \& j& K) [+ [% q. h/ }$ D8 R" r: y
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:" ]- [, m" Z' L
. ~) g3 ]3 J; G+ \% q3 B1 R
. i7 z. J1 t" {& ^! l 省略...$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]);省略...
0 x& W0 X; y( @( g. K 7 m$ h E4 l6 V6 [8 q; r9 I ^6 X
- b1 K, f! d- _+ B$ K
因此要利用成功就需要条件竞争了。
5 f# n; V, x6 g& \5 J & K! H6 D8 F# I" e: \
3 B7 g. U, n! C3 X( c3 c
补丁分析
; e2 a+ H9 {) p1 l/ V( `
6 T# h( M# _; P1 Y' a4 S8 Y# t0 t# A! q" R: ]8 P, b4 c
7 U5 `) X( B7 E3 W5 H- b/ p
/ q. l! D1 P; L
3 x0 w8 u" y. B* C! r8 Q" h 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:( f8 q9 X+ H+ E' K+ D
2 I. b1 e: t6 g9 l* G7 N' R) Q" M" @2 g- b- h2 U9 |/ a4 \
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}( e0 g2 ]" o( x) A# y
5 U7 [1 O" \2 T0 p5 ]4 Q
% T! i4 m, F' P# s, }1 e 5 |9 w" M! R; Y0 R$ }
2 ^6 i7 S% ]( g$ t9 W9 Y
" e$ ?, V ]. Z) W+ v3 C- @ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
+ O9 i3 m( R) r8 S& M 1 T, t- o7 [0 x7 N8 Q% A
& e: ^6 @+ \7 k; F
在is_allow()中增加对$this->savename的二次检查。2 f1 o# G( W3 N! P6 K k- e
0 j3 ]1 |4 w* b7 u# Y
J, ]# J! k6 T; l$ q 最后
- T: v0 ?$ \# O( V* X2 M5 d0 l$ d) i, ~5 \1 J6 i
4 E; e$ o0 i1 D. w/ C
嘛,祝各位大师傅中秋快乐!
4 m' e- e3 ?( i
* M3 i* M# o, f1 H$ r! v
! I' i0 K! Q! X , N7 A1 [7 \' Z$ W/ i
# Y7 M6 z$ T! B0 I& i3 z7 H
|