. q% C, G6 q2 u! r k! U) V
6 D; r& O5 h- Q' |- Y
' w$ _( P+ K$ w5 ~0 I0 }' u
. X2 H8 t8 J2 I! F; n/ x 前言
t, Z( R2 y6 e7 A0 J" v: b
8 b$ e U: b" U6 c' Q5 t% i, a2 j) U+ o; t& D- M- ~3 S6 b; H
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。8 f" F& w& p- D
5 [5 [2 m2 a9 Z" C
6 A6 ?6 o( w- q% @) d
4 _; n ]; a( e* A2 E; U" [
6 d5 R( I; _: O) r( n4 j/ M2 B" m7 V8 ~& d' F6 \; K& t
漏洞分析
2 n( V$ T" n0 @7 H, v
8 `" Z6 N4 R9 I! O; m. D/ P. f0 d; G+ X. e4 M! U! A: L
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
+ w1 y# {; w8 h8 M# V7 Y
- }6 L1 ^& m9 q$ K% H: R8 _$ I/ i: J3 v @$ f" |
. G: w' c+ X l2 N% M
4 q; l* V; H. b# X! t2 a3 v3 [
* |+ o0 Q* w/ l$ V/ P% c' z
对应着avatar.inc.php代码如下:
! z4 ]: E' J- E. Q% M% n9 }& H 8 n8 F, P: f8 L& q2 |, n1 h* T. ]
1 ~) P9 T6 x. i# 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) {% `) B4 \3 V& }& S {9 f" R. R
( L+ g% p U7 L$ _- G D
1 G) ^, Q! R" X9 i c8 D8 z+ `
case 'upload':' L6 J# d6 {! H+ m, ?+ R
I" L! |. Y: g8 [7 F* Y! b7 \
- Y- N4 _" ?% T1 x: r. W: W* O. E) c
if(!$_FILES['file']['size']) {( |+ y4 W0 M( n$ l, s) s- [
4 r! }4 Y2 a, k5 A
$ H) N# R: W3 N; c7 ? J' }
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
9 \) I* f# F7 s) }0 M; A2 \
/ X% {. n# |, z( t8 j
; {$ M: Q; }9 {6 w/ p5 j exit('{"error":1,"message":"Error FILE"}');' w' [3 z; O0 V
& U) _$ ~" h* Y
o; Q8 e/ I; }& n+ ]
}
1 e; V0 p [, R3 w! \& c
4 h" c# g& L/ L2 L" [) @$ G- M) N
require DT_ROOT.'/include/upload.class.php';
" x& m( }! V! S+ L6 S/ C 7 C/ x, l, Q/ i) h2 f3 a) W( C) P
8 O6 g$ e" L4 `
9 q4 I1 k: S9 D9 Z: L 5 P) M. a% H# p e* l0 C
4 b& \/ O* g+ g9 ~1 L' \; r5 h $ext = file_ext($_FILES['file']['name']);
5 t$ C4 [# J! l4 N% x- E 0 @9 w6 T+ t0 ]3 ~
@/ W1 q; W* [ j3 Y $name = 'avatar'.$_userid.'.'.$ext;/ i1 u( B* Y9 `3 a: H4 H- d6 E: T
/ w$ W5 x2 ^0 q8 N# n7 O
" }1 F/ j5 v4 ]1 l7 {8 n
$file = DT_ROOT.'/file/temp/'.$name;
' ?5 G) M' s. G8 f. ` g( P! L2 N6 ?2 m! G* {: g
0 i! [7 P4 a: ?5 P. f
/ I( a& C; W4 z2 J
! x3 Q: |- J* n: ?1 Y- g# d0 n. D& k6 ]8 f; t# c; q6 R# S2 c
if(is_file($file)) file_del($file);) h9 `& Q8 R8 p1 y; J
1 ~% h& y* f0 H' ^5 V3 g
* b4 d( Q' _, J, r! S; y V8 T
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
s3 D& _4 [7 W0 K' E
0 F" Y3 b4 o Q
$ P+ L9 x7 b' I! [" H$ {0 O
+ W0 V6 r2 T% h1 l7 T' O . }6 v9 R6 t% P8 f1 o7 i
2 _) n- ^8 N# s
$upload->adduserid = false;' J x6 Y% d& W+ m, m! k& @+ Y' y
8 r8 p2 a$ X6 n1 ~1 C+ f1 ?# _9 L6 r
1 r3 m$ v1 d+ n4 e& C7 J+ W+ j 9 Z3 v) W I' e H1 J
; D4 n; e& ^) ^9 r if($upload->save()) { h% o% R1 l$ ^7 w' s4 C# H
1 z. m5 \& X$ o5 t
, N4 ?) |4 h$ _3 n; |8 Y ...
$ w% W" O$ B$ q9 c8 D
: i# y. j5 L e* Y# b$ z0 B3 ~9 f. F% w9 `
} else {# ^8 h$ P+ _5 P4 N2 U2 Y
# Q6 U) h, K4 q1 _+ \ F
, k' O! j" m1 W2 Z0 K; y+ J8 d ...+ Q# x6 S" b/ Q) W6 \4 I% ?6 t
, w# `- F1 j8 B+ d( ]
# d6 j2 m- m8 c N/ y: ]9 a# S }* I- q% ~' g7 r) Y( I/ J
0 T4 M0 |, a# x H% } R1 @
- }# E9 O4 T7 g. d break;4 y6 R- Z( m1 s5 V4 g
3 f+ z. H) q$ C7 q! u {, U. v4 H! V+ [. p3 B" _9 ]
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
2 o" M# H) ]% `, ~1 p ; F9 y$ H4 X2 O- _
9 z4 S- p4 o" Y5 j* {1 H
upload对象构造函数如下,include/upload.class.php:25:
# ^( w/ l7 K6 |
$ a: w( H% V6 }' l; V! L. W! \" ]4 _8 P7 Y
<?phpclass upload {
2 k4 H9 E1 S) _& x# L f2 z, V. h ?2 \
: Q# T# d v+ l" s* m, U( C1 z( y8 V" b$ C/ A) E6 C$ b
function __construct($_file, $savepath, $savename = '', $fileformat = '') {& _ ?$ e* J0 Q
' }% E) X u5 v K: |
' D# G0 g3 p5 d global $DT, $_userid;
: W9 n2 ]3 c4 N4 h. j
2 j% _ ~) k& ^( h6 ^ V7 E% o$ a$ s- t5 N. \3 w* f$ }9 Q
foreach($_file as $file) {
4 v& A" m0 J0 G4 h( k+ l
1 I/ ]' P" w4 H; o7 {7 a4 Q' }2 u5 a! h! p+ i1 t
$this->file = $file['tmp_name'];; v& `6 s, J: `0 {3 j# X
5 J" y( W# n g( _7 r
0 E- K Z4 a! j0 k7 a% s& M $this->file_name = $file['name'];) E2 Z" b' i' p+ q4 H) X7 b
" n: Z8 R! Y Y: A! q5 n) R8 M/ I& L8 s+ l7 I0 g' j
$this->file_size = $file['size'];: }- M9 Y& o# M- h
7 \6 a7 \% t$ r# z( [* \! s' z' K' |% ]+ ], x/ l
$this->file_type = $file['type'];
) h: @7 y1 [, L' _4 v
/ F4 x# b D& ~# Z0 p" b' M* A9 t- r
$this->file_error = $file['error'];/ N# h* ^. T+ v7 f
. G d% r, o+ U+ k
M/ S- d6 T$ B V. n5 x Y
" J: ~( s) ~" X Q3 ^, ^$ }+ l 3 u' ^" \9 x( l$ J+ J9 [2 F/ W
3 D- o# ^5 H! N) v5 F: _* [8 |
}
$ H' V# ?6 w- d8 f% [* Y
" m2 |9 N1 s$ c$ R, ?/ U: n2 G5 v }4 w' g! _" _
$this->userid = $_userid;
( o$ A" R) X; K, o- I$ F# `4 E, R 5 t( n5 u0 Q" t2 L
/ E" q4 m x% `6 N, P
$this->ext = file_ext($this->file_name);
( K+ r) a: V+ a+ j, j
- _9 x" W# Q% i( G
# `# f7 u6 `. Z1 [( e* f w# a $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
% l# f' ?1 ^. p3 j ~8 X' q* E / K& }4 D: N3 Z* G5 f$ }& f
0 j N" ~4 S' r, y+ X+ ^1 g" @. I( Z $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
' S' [- ?9 c6 v( W' ]6 g( T
) P8 M$ V7 i; H' D' ]+ j5 {! o
$this->savepath = $savepath;
$ f* z4 X1 i9 p" V 8 [) k" O2 K4 O% l8 v
- w' ], _ }* z7 \/ z) o
$this->savename = $savename;. B3 H$ x) E& Y! ~7 H6 u! {- M
; E# J$ |8 ^7 m
. q _1 x; w) }) J9 h& R n }}
# @3 `8 k. P! C # Z4 a) u0 r0 i. x N
1 W- I6 v3 |3 v V8 o) J
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。( x* y+ w: K1 l2 R9 H
' K, R# r- N% K* j% Q1 t6 }
1 M( @- x e& B6 ` `( b 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
& \! V4 j9 ]) m) @, x& s
7 \" u& b2 f1 M$ ]% c
( Z* @$ E4 j- 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
, V9 C" k @* _4 d2 A) I" V. f
1 ^1 A! Z6 @8 {5 c2 ]$ D( @+ n1 S) W" ]3 z
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:( ?2 y$ {+ ]" l- S
* I1 B' [0 V% d% u
! k) c; [5 j; |, r4 I2 \* `. U) B# A " z* x2 N/ R9 ?; E
. V5 ^: j& n4 \" O+ X2 s$ O
1 t1 Z5 D% g" t1 E( f 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:4 N5 N5 f* K/ Q2 o, K7 L/ D, F
6 s8 s" K0 {' w* x9 j1 H0 C4 t! b& a
5 D M, f! A% K( l
<?phpclass upload {% ]* u5 r" E5 y4 @7 ]( R5 F: P6 v; \: ]
7 a* j% W" `* ?* T
: D' `, e" j1 o0 e
function save() {
! R/ G3 @' S) X3 ^
; X+ E: U* ~& n- v' N# R# l$ b
2 W U9 K" u/ V z3 [: c include load('include.lang');! p& N( Z6 b3 [1 |3 D0 z' x5 p
4 m3 o3 z A) S
3 x$ S: V: D& G" Y2 ^ if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
2 R2 ^1 t2 \$ l, ` 4 ?9 R6 D7 T( A+ X& c3 e6 }
$ k; [9 v" i, S; Y9 ^7 X9 }( X) A
2 N h6 u9 u' b1 O5 T
& J3 W' w6 B+ v/ z6 A( r
8 P: F, A$ c; ~; H7 _5 O if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');9 J" B7 w# u3 r( k) a
5 L5 O; o! F; K2 g
/ h" K% b( ?3 J4 `- j
9 p1 J3 d- C9 y# B! M
6 F1 O0 m) O- i6 u
& ?$ n, E3 ]0 ~2 c% F4 g& v4 Q
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);; j2 K# ~$ P; |0 L
/ z: V* J4 M3 h
! W u3 ^% E- m0 b) o3 R
0 `. D" j7 v( j/ q- R! n6 t4 a5 D
( w8 Q: ?7 n# n( ~" n/ I
2 T, r+ b& D8 N D; I $this->set_savepath($this->savepath);3 f5 ~3 ~/ R* i$ P1 q3 c" k, n: D
; z! |3 z8 O9 |9 v
% s$ U% i* ]: v $this->set_savename($this->savename);9 m9 A- O" j, `) p5 }
7 o8 B) f# p+ K/ y8 K
5 [% V# T# F0 a+ r2 q
: i% G0 \1 e3 X$ M n- g- J # I6 C) p- V- @ _1 h7 K
' V3 N- V6 U% t) S if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);5 j3 R8 Q. J1 [$ i* M0 V1 k; w) R
$ D$ H& o! S6 P# J: m5 H% i
2 ]1 u' n6 J& ]0 m4 ^" i& J
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
3 h: d \3 }$ n+ q2 w% C' Y 9 t% l/ m3 O- Z. @6 d5 V
8 K/ N3 i9 q( ^/ U* ?! [# ^/ f if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
+ S0 l7 B& q% Z, u8 L & i1 H2 T# e/ g" ?2 ^
$ q. |) U$ S& w
+ l- ]# c7 f4 B; i . Z2 _" u |9 r
/ T1 g. U$ c6 g. b $this->image = $this->is_image();- t% w" u4 x ?
' f$ U" T8 i0 F: g+ r
* S+ r) _6 `& M# D1 t+ W) P if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);; B3 \( u9 V/ E
" w Y+ z3 J9 I: R
% Q `. n& ]7 k! s: x return true;
, X, o$ ^' u: \8 E# ]2 B " f, Z; L, O7 P% T2 ]9 y3 h
" @% i4 x) Q. }" o
}}
" p, e* H2 h: f+ C' U9 s7 I6 ? 7 G; `+ K& ]. z! x' D' H5 {! y
: z7 q# x+ Y9 ]4 `6 B 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:' X. }# U. c2 p. X e$ P
9 l) @& q! Y5 i. A' O' X% x& N' ~! n2 M
<?php
7 E( ?) w+ ^/ E; v , p8 B/ f- T' s: O# z5 {
5 p3 {. N' ~. f function is_allow() {7 w3 T+ r& _$ R( L
- A$ I& x. T+ Z4 V' T8 J3 a+ v W9 n) O( s
if(!$this->fileformat) return false; ^2 R; |! C* a* L: @0 K
7 q" ~# Z8 G5 Y( g, c" G( t8 m9 a. O. H/ y+ C4 H$ K {
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;0 ?8 B& b; W# W/ t" V" n6 W
N$ B; ~: Y2 d6 j0 p
, T8 t d4 o! T2 W$ K# 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;
" K/ z8 a7 c0 u7 \
9 u' N0 u5 ]8 c( |
1 t+ ~, E( z' o7 T {5 e& w# N5 ~ return true;
% y9 l, h* Q B! v& x
1 _9 I4 Y7 O$ o' f2 t! A2 {. r6 X/ |' l7 C' o3 ^4 f! A
}
( U# _' T0 E1 l9 D5 W- {: M& Y , T6 f. `% Q8 c- K' l- {
: b+ T* X# v, u' W 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。* i% I4 Z1 n, g0 T* j7 B' x- ~" X
( f4 K! ~) K1 S, H6 f% L
/ L1 F8 P4 p9 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文件。8 i$ z/ O7 U3 J, N8 Y) f
0 \/ P. C7 M* a- j/ u
: ^0 I3 ], L9 X. f 漏洞利用
; f: f" x5 F- N' k1 \" v' Z3 g4 u+ _ u; J" v- D) P" E
9 ^9 S. n7 Z- c0 k
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。2 d" z# z/ c. _2 e4 }7 P) r
) r, J7 }+ o" J5 K; \+ }+ b. J% F" ]; j+ }
! W) A2 l* ^* S3 M' R @* \
1 \+ ]0 p/ U! R4 g/ X0 k' u9 z2 }( t3 H" e
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
( {3 p% J7 {/ a0 \
, ?9 m6 Q6 \6 c5 s) \# h9 g
! g4 d/ [2 t8 y* d 不过实际利用上会有一定的限制。
0 m! i9 L+ q8 B( f
. r$ t: S2 T0 P9 m; Y
6 e( r0 }4 D) y. | 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。$ N# P2 _3 b7 ~0 |% C& u6 B
* D. @6 c1 [5 ?7 a
% m4 N8 }! E7 u' P7 Q! g& p
2 C0 K8 U& _# G9 t3 _. D
( T) q' T7 ?# r( B8 Z2 |! j
0 k8 M7 C* @2 ]2 b3 `8 M1 }, h3 y 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
7 ^# j/ Q( G/ m- h! ]$ m; N
. W) v" w2 F8 w+ a) p1 k
r {( ]0 H1 `% J- u, @! y: Y8 q 省略...$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]);省略...
; e- C# r% ^' B1 [4 I5 j# X9 z
2 x3 l1 C0 u5 ^+ f) V P: g
/ y- y' {# u3 a* U6 A 因此要利用成功就需要条件竞争了。
* R; X2 R- Z- u B% m: ]5 a4 } * b% C- E$ p7 @: w9 v
/ D* s5 J7 m0 y* ^0 ]6 ~ 补丁分析
) W9 g: ^# d7 A5 W, @3 }* x8 e2 T) `: U" Q5 S0 L4 K! e
! ]9 Y) |: Z* y, E$ R9 F! D
# n( m8 m1 z% g. Q9 b
, @$ O# ^+ Q. Q& k6 V0 e, s4 e+ A
0 @2 g' V) E& [) L; [0 e- ?8 z1 v
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
, W# H! ~1 T* ^" | * @- b: H y: u6 U ~% I
% m5 |9 h, W5 N9 V8 ]" w. R
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
0 n$ J0 G$ r- g
3 h. q. C. g: f( {% W: W
[" |! b$ l: \0 `+ j' `/ U
* ]. W2 s0 M; r: p
3 Y- [3 @' ]* g+ _$ c
) D [0 _. V9 J: q/ X7 ]! D4 v 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。) M/ N3 u6 v: J, {, y/ r3 V3 v
2 e. C9 c {$ r& U3 n% x+ d+ W9 |' I
3 D' h: r: E. Z" ^; t
在is_allow()中增加对$this->savename的二次检查。
" t. k' }0 U% m9 g+ }" N9 m; `. y" y 9 s6 n$ s# U: {/ ~7 V8 T5 z
3 U1 Y5 P" q& `, C, E 最后5 i1 W3 l$ }7 u0 \& s# b9 n
8 m- i* L; U6 S* ~% ?6 g0 A9 w7 v2 m% t" X3 o
嘛,祝各位大师傅中秋快乐!
& e9 w8 y! V- R$ U- U, d2 B% Q
% ?! v7 T$ o- W2 [+ n! i) l; h8 S3 n$ _, b
. q+ u$ j* y4 g) _
: G! F4 {' M+ q; Z9 w
|