+ [: O# {3 ?9 l- x
+ F/ S# Y: w1 r1 L& @: @* u
- a0 E: W, |0 V i" u( F( T" ^
8 g; U8 o* U% L. [( B# v/ G' p
前言7 x: e4 a: u+ J7 g( j
2 L% A' M4 e2 x& h3 R) w1 e0 u/ @" }' `3 W9 O
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。1 X) q* L4 b* L o9 q% r
! k' L4 @, E4 t$ R
; a7 D/ A( a- \, Z) i% {
& L# i7 _/ W0 c; F3 _8 `
0 B O+ Q0 T% ]( l1 o T- [: Z/ z& R3 c) r$ n' A ~& Y8 h
漏洞分析4 \( d$ }/ W2 A
q$ E7 _1 i1 S% `+ y7 |
% c6 T* _& s& ~/ f0 m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:9 k# b, u, I/ B! B7 q/ a
1 g; \' K9 D: F9 Q# G6 j
0 B1 ^% g5 \5 X, l8 z' W( d
' ^3 j3 l6 U* g+ Y$ J! O 6 l' B* A8 U: W
) Z0 \' W0 g, A7 K3 p7 Y5 l7 M
对应着avatar.inc.php代码如下:
, k; r0 H9 Z& h" O8 t
8 N8 T, v) S3 Z- v2 _% m( {; u/ D O' A; j
<?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) {
( T- k/ m% u2 V 5 [# c, h5 f7 \8 w& `
! F& U8 d. x, A
case 'upload':
7 y, ? |. j- R+ \" L& [ + j" R9 V# Q+ D n! B" R
& r9 e5 D9 ?# p) {
if(!$_FILES['file']['size']) {
& @& j( M3 k& H& c * ?+ f) [2 A9 w2 U# |& r" H( o
* q1 t+ @) @3 W! i) r
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
( u- }# [ @7 Z6 K1 F
j3 c8 M$ x( d% [: I) P% f7 ]6 k7 k! w9 v) k$ S; @( g" T
exit('{"error":1,"message":"Error FILE"}');
0 ]1 ]/ p+ C% [) w, U7 `. j ! }7 P E8 ?& I* C. T
3 M5 `! H- s, h* h% q7 h }
0 v5 X* j8 k2 t: w* S ' @0 M0 _! F# C7 Q" ~
# }, b% T0 l. h# ?5 a
require DT_ROOT.'/include/upload.class.php';2 o, |) C; I% Z& h( Q: M
" r- o- o( |# N/ ^+ Q- |
( h+ R6 [4 ?8 ~- S8 B8 C/ m
9 `1 i4 ]/ F" e9 ?/ k, f - q1 h, _+ y/ @3 B+ C) W
+ j. x0 K8 h8 |- J( \ $ext = file_ext($_FILES['file']['name']);, o6 a2 F( j2 ]! e% r( f: l5 a" K
x: d$ j; z* F: V
o& b/ t) j6 H9 p c
$name = 'avatar'.$_userid.'.'.$ext;$ @6 W, I& u1 X7 f$ k
; s0 j8 @5 p/ ]# p& H8 A) A
% E6 M! {: v) L6 R9 y; r $file = DT_ROOT.'/file/temp/'.$name;
; m e* m' [4 v3 C4 w 7 R$ U; m, i; Q" @
( M/ L, B, d8 j( }4 E ) F4 ]# [$ |2 Z1 I$ z; b( q) x; I
0 ~: m9 o1 `+ K- I0 j
. |% ^# l( ?1 m6 B+ l" I' J if(is_file($file)) file_del($file);4 Q* b* H5 H: {
! R8 z$ T0 ~5 v8 C, h* @* Q, t8 Q9 ]
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
2 M4 h4 A- K3 h# o/ @( l
* J; c! b$ K: [
1 F m8 I( G7 d! _$ t & t: i0 h, F- L9 J" R
" S% \# t! t5 `# f% [' N$ R+ X5 w( |" G3 n
$upload->adduserid = false;
# k( f1 N! Q7 H8 A , I$ j+ Z5 y" X C
7 p( i3 k( O$ `+ b
4 ?) |: t# A' c5 F4 K$ _: \ , Z" Q/ {7 S( z: \
. ^8 \) b/ ?3 Z6 B if($upload->save()) {, \9 Q4 h+ k4 F
9 @3 O6 L Y, @& E3 f: i2 Z, |( k* P
# U# l* B9 C6 c! s+ h! w7 h: z$ V; Z ...
- O& M! z0 A$ r& W" _
+ Y$ Q6 n$ m% A8 @) I* l: m1 O5 D; y; C. s" o* }
} else {* \; X; O8 ^( O6 J
Q6 P8 F# j3 G' p$ I8 t+ }
& A) _+ P* y1 ^) ^ U0 x$ q ...* ` u+ g4 L: k/ U$ {+ l! D/ _
% h) S8 x) b7 y+ E3 D
u7 T) ^# _" i }/ b( r/ s' d+ Q B b) k8 F
6 o# Q% H( E9 \) P* q8 a
, v5 [6 C. N* w! `
break;
6 @( B; a; v. _5 C: ?' Y ( F7 n. K7 E9 }) Z0 Y
* [# S% O( ]2 G# E 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
2 P, X5 i- A5 k
) _" o8 X& `% g$ C1 i" S0 ]6 O3 X0 c- Y
upload对象构造函数如下,include/upload.class.php:25:
) p6 l7 Q7 X+ |/ ~& ^& a
: s5 ?3 g+ l: I5 l+ ^$ O! X* n( d6 m, c: U6 g, d7 ~% W
<?phpclass upload {
4 e* m% ]* g" |! d! w5 v, E6 H
2 e7 q7 D$ E6 ~# Y
+ P+ [- l5 m+ H+ G function __construct($_file, $savepath, $savename = '', $fileformat = '') {" Z( x$ t# x$ G4 T( w! }. a) }
2 X; C9 j# ?# B
' X! r1 q* s0 q3 g2 c' h- i global $DT, $_userid;# u8 l7 b9 Q4 ]
! w& \5 M O: e* h: n ^
, o9 j4 H& L* k
foreach($_file as $file) {) r! [7 n( D) ]* A. k
, p1 ^8 ?- t. A
) k% h, @0 v; b; n9 ` $this->file = $file['tmp_name'];
k5 o2 B3 l0 S% C! S3 O/ `6 g4 ` ; u0 U: ^- Q8 w, g
* `& i# w" x# U8 e. M6 Z
$this->file_name = $file['name'];* r2 F' p0 y3 ^* X1 W" A3 M8 k7 |
! T! u. ^1 y) y
1 t. e1 U$ O6 H' F% G# O4 J $this->file_size = $file['size'];: t1 T' s8 I9 b( L6 y
0 s; Q) n+ \6 K, ~1 Q
. g8 q: L1 T& ]: O $this->file_type = $file['type'];8 J5 j8 \7 D: q/ _
8 k; f& N W6 f, N3 u: G+ z
) ~3 L v5 |) l. A- |) f
$this->file_error = $file['error'];2 V% b5 n( y4 a* M5 J& w7 |# [6 |
( Y. o# H! T2 e+ j2 \; v" f; v% G6 u' |
1 e z) u% ]1 r! W8 ^
1 {; n# b- C% \ K
; N* d, g2 P: @$ o' S, O8 K
}
5 }3 ^: m, [3 u7 k7 ^
2 C# L6 _( j+ b+ c/ [) d2 c
1 K) t( ~3 ^9 [3 ~ $this->userid = $_userid;
: T% L7 P, t) e3 k! V' D3 d5 L& F
3 a6 s1 T# K, F+ _% [
3 T. H& H* Y$ G $this->ext = file_ext($this->file_name);
1 y% b! [7 S* y1 o: L
% R- a6 h4 S& m+ V+ }+ K9 X5 ]# c I+ W3 }$ l
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];0 N7 Q( j0 F2 _. r( {# l
6 C0 x' ]1 {% q9 S0 R' D9 p5 Z7 }
* E+ }4 _* e( K8 K $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;% f' {8 ~8 G8 U
- K2 s4 {8 L' n7 `2 G) ~
R3 ]& e( t+ E' [) H/ Q) N& n. z $this->savepath = $savepath;$ W3 @4 v( u/ _8 Q
8 p: G* i5 S! z1 Q9 g
7 z6 p* F0 h; F $this->savename = $savename;
' `* U7 j7 t7 T- E6 G) L . A: T: S: @3 `; c7 m
7 ?9 L: i( L; B+ ], B }}
$ D2 m, ` ^$ o1 | / L$ E0 G' i3 w ^
5 @( O. {" }2 W3 t. z- I9 p+ { 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
/ d9 h' d! J, b* ~. ?! m) u& F , i) C% @8 x5 s8 F' ^
7 C% B8 ]1 M+ r0 x1 k) b% G( B" u
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 . N, W5 T' D5 e A# m5 T5 T
. v, n- {1 S0 o4 o3 A
8 ]5 ^3 Z: U9 O8 } I
$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
; F d& u3 p6 T z- U9 I 9 p# ]: H6 ^ w
5 `/ ?, _ c1 K4 U$ N$ l
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:, U: c1 P( \9 z K. t
# U) N6 L9 R1 j
4 g( T: i6 }( J, h6 g 1 x8 o$ F; ?. J: K8 e
/ Q3 c; R( g* S. _( v. v$ s6 _1 x/ h2 [
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
J- c4 Z. T6 z$ ~
2 ` x' M D: a6 \8 l' a
5 U* u6 X: Z7 d7 M; ]! G: @ <?phpclass upload {3 n3 q2 R, z. g- L
* ]4 a. F0 U G8 K. z; E4 e/ _& H4 n$ j5 p( p8 C1 G; h
function save() {
/ j" s$ u3 f2 E3 S! R) C+ ^ 9 o0 M3 t" E7 T6 ]" ^' P! b9 V
% o: p) n0 Z2 s6 T8 _& g: t include load('include.lang');
/ }- O) W `1 `4 R4 K/ x) x7 \ 7 B4 e8 P5 d1 V( m: ^
4 a- }4 D* q) k' Q if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');( F6 E' Z& e& q6 S! l
# r X1 r0 j; U$ @9 m% p7 ^
% D2 p& x4 p* z1 l f6 r / t* Q1 G2 ?9 D
! I* \. M9 J. O
& ~ N" f6 \8 {( K% a* I
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
: k0 R* W* t1 G2 m; _ 0 ~! A6 J8 ?+ @) }; Q6 {0 i; z
* q* S0 y5 v7 g7 T
( Q9 A3 U$ X+ t7 A2 s 6 P- Q+ J3 [4 I' a" [: b
. @. K+ _5 Y/ ?( @- y if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
$ F3 Q) t6 U+ A" | 1 |- p- \. Y$ {
/ d5 W/ Q: t9 U5 S, D5 U( ^ 5 ^8 ?7 s0 E1 |5 l, x$ O
; y( y& G: h/ B3 S
: \8 n) Z0 c; U $this->set_savepath($this->savepath);
2 g3 h1 o) z: x' L7 F& k : {% N! o! ?/ D9 L
5 _8 ]$ p' O) J. }8 L- y $this->set_savename($this->savename);
$ u+ r" f" l' s& G+ i4 r* A# Q
; x; i# D M0 I& ~8 @% S2 \
( y9 m6 ~9 g( s6 r' q7 M/ M* M
0 E$ M3 i8 Y0 Y2 H0 v' P " H6 e$ o7 s, _' ?
; r2 o: w. t9 \, G0 v+ |* w
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
. B/ ]( A) ]: R! x% Z+ K% J6 d : k, o7 \: U$ y5 n( ^2 O C
1 g$ ]7 }' c% Q }' j if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
1 @; o1 |+ `! y A; O+ Y$ U. X/ h 0 r3 t$ i E! U9 ]
$ J% S6 n6 X, g$ ^& H5 Z& s0 f
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);) j; E; o" Z( R5 z( [0 w. g
% `9 [8 O/ @* }) v* X
: j& h# s) r3 [. x! v+ M 9 l/ d- _4 |7 u5 i
5 |% x2 E4 |$ [2 e, u
. E; Q3 r. Y( R+ P' [/ V. T# P) R- w6 U0 k $this->image = $this->is_image();
5 Z$ I/ N+ A4 j/ x! g' v
/ ]9 P8 w \6 @% Y4 F! K1 [
+ l5 D6 A1 x7 Z# c0 e. ] if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);+ s \5 x0 v% T$ Z- r" ~+ Y2 b
/ p. [' n5 }/ \! B: {1 {2 H- p
7 g# n) S* R4 [) m8 o return true;
8 H3 e* F9 a* s2 h- e: P: C8 D
# N2 X8 T% i. E4 A2 a" ?7 l8 y9 k5 `0 V0 P/ u; A6 r$ ^
}}: A. {1 z+ z# o C
9 I( A. F1 {1 p7 Q( ~! Z, h3 r# j
6 |- k! a, J; C$ R1 H 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:( Q/ m' I8 J$ u/ @7 \$ z- j: H( Q
5 R. x7 b& s! F" [$ }8 v2 I' u& [
- O! z' s4 y0 h; }: O8 X; k: u g: J <?php
k; o# p' }6 ?
& N1 b) t( y. Y0 i* w4 V5 v5 P V! h4 X6 r4 B
function is_allow() {! ]: @8 ~2 U& h+ `6 [3 g0 p
+ ^7 L5 {1 O7 O9 S
; | s1 q$ N2 l. R2 v# M _ if(!$this->fileformat) return false;
( j$ w6 X5 d5 v5 P1 M
6 c% e; n+ L" M, P
, r" k( i6 S% s6 n; E# f% h if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
& N& R. u- q) l+ ]
- R* c+ L/ s- r: o
" ?" l; a7 ^: ]% ]; Z( L' `4 x3 b 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;* a+ i9 M2 c9 C4 M
0 v, j) F% u% a0 e; X
; x9 h$ H5 M2 H return true; G @3 i. @, P. h6 x* S" s8 J
+ S* G) ^ m! x: [. G
2 B2 C# s" L9 k8 ~. i! ~: _ }4 k! H9 g; _, a: I! r
$ x- A+ g# s& k; j8 n
! F5 [2 N% }0 K/ Q) Z s 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。- X: _# g p* `9 C+ V( Z
( W4 `/ V" U. j% K, q* r. o
N% |" U% H- i
接着会进行真正的保存。通过$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 t5 j# Q
) N: b3 D& w: A, a# B
D1 i" Q& ]0 u6 m* E9 G 漏洞利用* S6 _# R* ^ o: ], A/ a
- y, @, K! z1 l
, ?; G$ }# W( A8 D: ?0 |2 ^+ B 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
! J* V/ U" V* s6 R6 O+ l + f: {+ e5 s2 G" t& M( u% {
9 j4 a" ^* d' b. q' W' R! O4 ~
3 ]2 Y; m- g4 ~6 P X5 P
( Q& C' l6 A5 a& H2 ?
6 L+ k0 r! E2 c: P1 { 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid& U3 v3 Y, V4 r9 b! I& L* X7 d( m( |
# D j) ~; j8 ~( A: l; b4 D5 f8 A
1 \! U6 ?2 ]; A# q+ d3 q9 c5 D) c5 a5 p
不过实际利用上会有一定的限制。
6 X+ s. B7 |7 ^2 N
: N, y; Q/ C: ]! e( k
- w! k `5 [6 B& _ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。+ ~( V& `" ~* i3 s
) m6 w; `8 j8 A, [4 K
2 k. Z/ q8 N$ ^: C) L6 G) s
4 k$ U3 @% Q Q- w3 T7 O c
9 w0 _& f6 l# O. G& Q3 Y* t
% I6 V" H3 [, T" ]% |* K 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
; u% b, e, ?7 p9 T , ]5 r7 I- Z+ _; R0 L# K0 b, h
2 S9 f( [! L+ N" H, M# i: { 省略...$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 ~. e; v8 e3 t+ m2 q2 C1 Z
/ Y: y; K3 {! p: j! d7 `
# f) j$ {9 ?; ^" G5 ~, J; Z1 d" U
因此要利用成功就需要条件竞争了。" i% ^3 D" M% y6 [6 R" I4 E
, n f+ ?$ ~5 @3 h+ N, I1 E9 }( ?# B+ }/ _! c. Y, i
补丁分析" V f) L3 L; p( n) n6 P, r
3 h# H9 w( M Y7 O2 D7 ]; j# N k8 N/ \- C! P% B
6 U4 Z8 T' G/ h$ e! n$ f 9 v+ r7 u8 K, x% w0 H+ F5 X0 y: ]
) U) b, u% l5 B% C, g6 o 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:7 t& c- t( P3 i- T! C( a% o
4 l: B$ p) {# z: M2 ~6 L5 @4 d
8 y0 H n% Y5 v u8 u0 F' Y
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}6 P: j4 D& w& f5 ^( o
6 @1 y5 R4 M7 Q
( o$ `# e: Q: c- ]# L+ O
! j, y; j( J0 Z0 W. N y( p6 [$ j
8 }8 a. W) ~9 n6 [$ C5 `
# d" }' ?3 K: k% M" H" |/ ^9 @4 J 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。& i$ A) m* q9 d8 l, ?# C
3 c0 i2 m, {, Y
* @0 R, n. H7 g# N d9 o9 t 在is_allow()中增加对$this->savename的二次检查。
# f/ p9 {8 t9 ]. n3 q / k+ Y% ?/ J4 l$ J# \$ b- [; E
8 \6 J7 B2 N+ N& W- ]& ^ 最后
, b, V; q% o% q' E2 ~" V. {; S9 r
# |2 x/ {2 d# M' w
嘛,祝各位大师傅中秋快乐!
0 v i0 ]+ x0 W) q
$ K8 C. A* P7 H$ ^% }2 Q+ _2 o6 i g1 D4 t; g' ~7 B0 ~0 T
. G: N: v3 }2 o( P( q/ Q4 F8 t
: `$ Q4 I* p' W3 L/ m3 S8 i
|