2 y0 \ v* L6 {% L* g
$ d6 j( v" d- r7 R; \
7 Z+ U) L) y% b2 e; x6 O
" |% {* M! ~6 Y0 v4 `" h 前言5 D5 i9 F: |* h! B0 z+ e
# a7 |, F6 p0 n: O
' `( Z' Q8 i# A! k- A1 l% t( N 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。3 Z) t3 C- X A
Z# b6 `1 H4 B/ r. o
! E" P) t" T7 u2 h( x( L 7 l* ?- p* p/ K5 `5 X7 [
1 v) f7 Y7 t5 m
( @9 a/ `$ I) }8 Y5 s% ]5 f 漏洞分析9 q; H1 W; d; e- I, m+ }0 ]4 j5 w
; \8 s \* x/ m3 t/ a* y; S; n
( k- o w# A. Z
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:) a! H. p5 h% Y) ^1 b8 a
# n8 H( R2 V5 ?. l
0 h& _. n% ^' t% h8 A
' S+ Y1 t4 j: b9 w" N
8 n5 H6 Q7 v/ i4 q6 D) m3 o6 a6 ^8 h
对应着avatar.inc.php代码如下:
1 N0 H+ {! c/ [1 P/ s s
s9 W( n3 }: D- [4 f$ f: S7 M7 w$ a# u
<?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) {
7 a/ w: y9 F1 G, t$ b4 ]
0 D4 f( m1 |5 P, D# q6 b
2 O- M6 Z$ a3 }% [% ` case 'upload':" c, @ p+ H* Y1 i) K* Q. ~
5 }/ J1 l- s. j2 y; J% h3 }! H5 [# d- B% Y
if(!$_FILES['file']['size']) {
# Q0 H( F ]+ @8 G/ e 2 N+ u: f. {* I3 R+ v) Q& f6 b
/ Q, h8 }6 B( ^9 Z if($DT_PC) dheader('?action=html&reload='.$DT_TIME);; m, Y( v2 E; w- z9 R, D
/ v0 T" c4 G6 w: j/ L( b7 Y
! N% L2 }* Y+ p2 t# ?
exit('{"error":1,"message":"Error FILE"}');7 U+ y/ ~8 m/ V% N! z K) d' x
7 c4 k5 J; U( }5 L
. E8 Q6 v3 l. ~7 j/ ~+ h0 D
}
4 [& e. ^, y: A
) r0 m6 a) ?+ \+ x- k' b' j# |) J" r% a& E$ ^! |0 N
require DT_ROOT.'/include/upload.class.php';
- H6 L, @3 O* k* O' c: J 6 F0 b: Q* |5 h2 l, {+ q
5 f' @$ I: c; V3 Q$ M% l
- Q9 b; V: S3 j* ^, T( K. L+ \1 P% X
3 r8 k6 n# C2 C" { d ^6 M( Q
1 S. n" T3 u2 K $ext = file_ext($_FILES['file']['name']);
7 V0 [4 T2 t) Z+ D6 _( s7 E2 r6 D & ~& C: `) `4 [: j8 n- U
7 [7 o9 G1 ^. d/ j
$name = 'avatar'.$_userid.'.'.$ext;
6 ^% O/ n$ f: ]7 F/ n, e
& q4 P/ a- D# ~
% T9 f3 ]0 a7 D, t% P6 M) f $file = DT_ROOT.'/file/temp/'.$name;0 _: T) }/ T/ e( I& f
* q9 n: r0 p; U6 L1 ]& [* u) F7 K0 U* g
( S9 z( T$ n" C4 U& O f 8 r$ U+ T2 ?) Y9 [% l3 m
8 d: f& E( Q2 b& ^0 ?/ A
if(is_file($file)) file_del($file);
2 L# C( |' `9 Q% r8 U2 Y$ S; v
* S% {% X0 q* W; B
4 y; y3 X* ]/ r) U- i% G $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
8 H9 E' \7 n8 h) W2 A& s3 j $ O( z, L6 s, ?
8 m# P6 v0 c! t' I
* h) w2 n" @" w1 o
" B" r$ Y, t" f# `1 J& y3 q3 ~( A+ C8 \: f! }4 h1 B. E9 _/ Y8 @: J
$upload->adduserid = false;# h3 K Y$ ^& `
( q0 O2 f9 i7 `9 R8 a# h+ q0 q
/ U3 Q8 F) O5 J+ `8 R+ P
) W' x9 [% l4 n3 ~/ l+ J
3 q* E. @" _! p6 E' l8 I
5 w: Q( l: H& o
if($upload->save()) { z8 m7 [+ Q, p4 k6 z! B7 u4 w8 c
" ]- [( F# I; M9 ?
* o, c; L3 Q) y: f ...
( [! f4 a& v4 c( _4 f) ` / i8 B& a; \2 j6 H/ R, e( N1 I
4 |3 s! l" d# D i9 K/ X( F! O9 U } else {
9 }% ~1 v4 Z( ]3 I0 T& Q6 |! n 1 c9 V+ m. _5 x7 b7 S& z
$ A$ v3 e+ @0 A& p2 Z% e" @/ s" O: ?
...* J, c6 w* j$ C, b; H+ k5 F
# A$ A# F& p! x" h& W8 K' n7 e
; f! ?* d, d3 [ }
8 ^4 R8 B6 V: f
/ u! [; E% Z) X' K- @5 F( m/ \' _" x0 h5 [* `: Z
break;
% q5 k" P9 f2 c* L: m
2 v! k/ x5 L8 l$ s2 n; G, O F: [- t q6 Y) W3 M
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。+ r$ O7 ` q. V9 W5 q
, ?! i, @4 b [: v9 N& ]+ Y
5 @9 Z% ^# x/ w2 } upload对象构造函数如下,include/upload.class.php:25:1 T0 h4 K# _% i
4 C2 ?, I! A8 D) a# x4 z, \- a/ y5 E1 I7 k) U" J8 e x8 ?. B9 Z) U
<?phpclass upload {6 @1 g3 E; R* v% f9 j
1 e$ w1 |4 N; D+ H9 |! p8 `* l, b1 Z3 b, k; W5 q0 o: Q
function __construct($_file, $savepath, $savename = '', $fileformat = '') {: U2 |- a, P9 O/ o
8 n( f; E% q# x3 |9 f) x* @) E0 ^3 O2 n& a7 m" j9 ~
global $DT, $_userid;
9 R, f6 a2 N1 A1 s3 L1 h
" j# h) Q' L: Z4 K, ?
. V9 T6 t6 p$ y& V3 n% k foreach($_file as $file) {, o0 u2 ]2 N! \% E' ]; w
2 e& f ]6 H8 x3 g9 _8 k8 z" ^( y9 |2 ~6 D/ x
$this->file = $file['tmp_name'];
* h$ U4 O* n' o$ i0 K& j' X 2 J: j" U: E% g, p
- S5 ~3 O( Q" t2 _$ K2 Z* k $this->file_name = $file['name'];
4 _# L; w4 c k" j9 Q$ a1 ] 4 m& n* u( |4 M: |! E. k% `
' o2 e! R4 A6 i! r3 G $this->file_size = $file['size'];& \- @( a3 s" ]+ p z
$ d" q" X/ w$ H
- I) E* t9 u/ |; F" u2 j
$this->file_type = $file['type'];
& E1 J! C- z$ ~" u) Q/ ` ; p) B Q* O( l z# j, b0 T) U2 C
) r2 X$ g) Y. L! N5 U. m $this->file_error = $file['error'];: w+ _; I2 e$ b; ~
/ X* ^% E6 o* \5 M. D/ t9 F& P
4 G K* u2 O( n5 ^: [& H
# Z7 N; m) Q# Q* E
4 C K/ f, E; u% l- j6 }
* b( u1 _+ l4 R. v% n; d6 u- |7 p9 M3 Y }
9 d& z& Y6 K& T; { ; B2 H, n& K) u! C
9 ^9 _; r$ A! t# i& {9 h" f $this->userid = $_userid;7 A+ v8 K& `9 r5 o! e* d k
. D) n$ U- x( Z, G& i- [/ R8 m+ q* S/ y$ q) C
$this->ext = file_ext($this->file_name);: ~+ U9 c- J7 j8 ?2 X6 Y
% C/ ?) S% c: }4 H! a& S) v" v
) a3 P. r5 J P! { e0 r; Y5 K5 [ $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];! l9 s# V' a/ Y+ S( Q T& W$ t
M1 T- \* n; ~& a7 y1 w, x9 {3 J. [6 \1 y. l- X
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;; C0 l9 |; p. Z, O
- V6 {; w" Z, w, R3 k) O; q
! O5 @! r+ N8 x* G9 g. k% q $this->savepath = $savepath;4 x- W/ d# \/ R3 M* g# Y1 r, t- Z( H
' t/ v0 C; M( c1 R) v7 C9 u+ Z/ o, u0 U4 o) C
$this->savename = $savename;
2 h4 F% w! h, [$ n3 s" Y5 f; |
( y* k3 F3 N1 b
1 |! p4 k5 D ?2 P @; s1 ]0 B- h }}
. T k1 o' e) t* g
- e% C3 T" b+ v; }# Y' Q5 `
' e8 l$ z! g* k 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。* @1 R' r7 t9 Y" n0 m" w6 r
! i d" Y' G) Q7 q
* a# b3 A6 t @# f" r 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
$ `! ]3 y4 `# i2 i, i
/ P7 T; a) O: f# ^- \) k
2 |6 T7 h) P. D" D3 e( Y $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
8 T! b, p% ~0 p) y' ]
6 [- N! O) U" f% R2 N
6 f9 N# j0 K# l) a6 N! N) l8 ^ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
; G$ {: \1 d2 G9 l9 x6 v7 k t
1 @% M% i1 c3 A- c- u+ i) l1 v! y* n3 @ z( W
; O$ z7 {& p* U" r, h
N) a, i" L$ n: B
& I7 O3 Y$ N6 x6 f, j, Q* W4 C' x 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
" E3 C3 B" G5 o* Z
6 H( j& V5 g: N% V; D/ x. I
' S( a. J! P. O& g$ t5 k2 D. m! Q <?phpclass upload {, a% j) `2 f6 A" P s! Z0 a
* w% k( C" M# ?4 Y/ `. g
# t- h) z. {* A, l% f! k function save() {9 a m6 [! A1 b a( p( a3 ]% Z
2 a% x3 R0 l: t: o8 R' T& D* X. e5 B7 K+ h6 V, B. M! |4 r* c
include load('include.lang');
C" Q9 ^. z4 v* [! N! k4 _( f: c
- _, W! e9 G- i
1 ?2 Z0 y _" J4 | if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
3 J, ~5 e) F. Q& ` 7 j% L4 I2 o( R2 n1 T6 D' N
% l; r! H8 G! Z+ n4 T( _ $ i6 s4 F/ z( Z+ U2 ?
' I" m6 Y+ p# l6 w* m' Y$ Q9 i
) B, I: q& }* X8 E1 E/ w if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');6 n$ p/ j' Q3 }3 R8 C! u# T
, k" I# r: ^8 C% a) P5 G
# Q4 ~# u: q5 Y/ K# p: e' b
- J, r$ U$ ~* u* @ 2 k( V& G3 ?' Z7 R/ w H
; v V, D2 T5 G5 u
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
5 y% D# n& w9 `
0 X, F- R4 X/ p P& J; O b" R# K4 [7 T
~8 ?; K; ?! `
B, D% H3 T6 v c. g, p# c
4 c0 h5 H# I# |1 C F $this->set_savepath($this->savepath);
% T3 I7 ` r: J
" ?9 l( e3 S; T* l- [0 I) p/ h! h+ h+ Y( O
$this->set_savename($this->savename); Z7 R5 O! A7 ~2 V
% P- B) K* t' J/ J
2 o- {6 ]. I' d x0 D6 ~3 {
8 G0 A! r) f% k+ }, P
?1 ?7 ]9 n) _! m
$ C3 c# F' u. i if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ R! S# J( a. w( n3 B
5 V1 u; C! i* y4 w. V) g4 F; q) U$ {: w# z. u2 w2 u
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
! z: u* G) b1 D, \
5 B" a, M7 q7 V( }' f; c- V
# W+ Q! @' M* H9 z& [9 M9 @ if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
6 l& ~3 C9 r0 p; W) D
8 x9 F# R: g6 m P
7 P) ~* e& T2 M , I% Y6 ]4 q8 z" A. ]! s
& T/ ?, m! K6 F7 i- J
# O* z' ]. t. F$ n9 T9 ^
$this->image = $this->is_image();! p( j! V- M: f
# l) G' t; C1 E" z& H" \0 {
! a4 M$ h4 f8 N8 s2 b9 [6 [0 u3 Z if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); J: ~. E8 b+ N( Y
# ?# n" Q5 \, r \% G/ f" }
( \" n7 l/ Z* P" p8 p2 l3 e return true;
% k4 \9 w% }) m/ W - A1 J( E) D! P0 @3 j6 e
/ O! X, M, h* Y4 K }}! a# j/ r# J* _3 A
/ G# d& l# v) @; H: u# F, L( u
0 J7 J& K+ Q5 m$ c( S
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:/ F2 i; V, J7 n6 N7 _3 k
& _3 H0 _0 A. y2 W! |# d
2 L) F( p) ]: s4 ~( @ <?php; t, R% j& p/ m) d
# B1 k0 Y9 c0 m! z% ]. K
/ m; [; t8 D) r7 M# x# @1 s; Z
function is_allow() { f+ P) [: v5 y0 d" w4 `" u; e
9 e& H8 g! N% |* \/ d& Z
7 ?' V9 P4 ^1 s6 y4 _: c7 j
if(!$this->fileformat) return false;
% ] }/ F) W" d. V, s6 \6 `* f
# N4 C. }3 V% n* U* k1 A/ [1 |! ?3 W9 Q
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;2 g: W6 i w" K' A5 s) \
( K5 j! Y* J" A3 _, e2 Z; `- H2 q5 p# T4 ` R) {2 D( k4 I
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;
* W8 k2 ^8 i* Z4 |1 a3 Q : y/ W9 r* S3 _3 E( ?1 }6 ?: k
. ~, Z. y, q* F$ M
return true;8 `" H& z' |( F) V* a7 [- L. x
' \7 }3 L3 o6 k, h$ B0 z; Y1 J- ?7 Q' `7 u: L! R
}
1 N0 {) u6 P' l$ ?: Y- H! C 7 w5 |) n l9 o" Y4 Z% C. J
+ W7 E9 R' T) g: W l 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。0 B/ o6 w7 M9 k5 ~* t H g- j
, n9 H8 C% b" I# x( Q% S. e
7 a$ K N" c% @. x, C 接着会进行真正的保存。通过$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文件。
% r2 A, _' h3 J* e( i5 }5 u % X4 V* S$ s* I2 a
$ {& @& z7 u) s" U* X t
漏洞利用: c% ~, T* v6 m# F* _4 U
/ N5 d+ [ k- V
" i1 ^% W- W' X+ |7 c9 W
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
/ R6 ?( `6 }- F$ l & I" d' V' C, K# U S: U
0 u5 ~0 H4 l9 m" d
7 R5 e+ w+ q+ U1 g' ]& {1 j5 d" v, |5 w
. D+ ~; p" F. _4 M) n/ z' b% n$ o& N) Z" o
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
8 V9 [. n1 J1 t u. f+ y
6 F' y7 A/ C$ Q# x0 n* l, s1 E+ e6 W- q7 L
不过实际利用上会有一定的限制。" B: {3 n' p5 \/ w6 V+ `4 w/ K( ?
: ?+ o: w) u* d9 }5 ^, I3 Y$ z
$ z" m: v8 a% `& Y7 R' u* M7 m/ i 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
4 Q; ~- k* i! T- F; g
, x7 ~' H5 N1 z2 Q% k# E+ b) X# W1 M, q/ X" c, C8 S
! o) Y2 z* J' Z0 `+ ] * w: }) ?5 L. i: o# o4 B, E
2 _, V. q# a4 Z/ u 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
% d& n( k8 \$ X: E! n( f$ L 6 H8 [& n5 w7 H& X# Q# J7 `
* }' ^- `) H3 i" w9 \7 g+ 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]);省略...
* h) J% |) _9 G1 \
2 z( g& E4 \( d4 Y# f/ P
6 H2 H& ]% P% e; K 因此要利用成功就需要条件竞争了。/ W5 A! F6 a* F
( E1 S3 d' i% L3 ]8 a# W) Y
6 Q L8 l; {$ n7 K/ D2 f 补丁分析/ Q) A$ E! H& G! k
& x- R2 {0 R& Q1 h' E/ Z
) }: y0 m" t& O" V% B) e6 H3 i: k
1 a! ?$ B( m. [6 U 8 W; @: i4 r `, S
) ^2 y2 Z4 ~6 |. E+ ^' U% _$ F 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:, N: S! P. u. Z7 V) K& v4 x. c
" m, C/ ~$ D, O5 M' X r0 X" k8 K7 r3 N: n
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}5 \8 O% J3 k, f7 F
8 ?' F! n/ R. i4 L( t5 N8 g e% \$ M
. }) z% V$ w0 }1 h- f& U% l- Q 9 p/ n- L5 W; l6 N+ }4 S
, `# Y b- s: q4 e
9 C: Y( `; u7 m1 Y 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。: F* a! m- \( y; ^
) u+ X# z% ~* A. H8 ^
! x1 E3 Q4 W! _- O* N 在is_allow()中增加对$this->savename的二次检查。
: ~: S8 S0 r% j9 X5 P- h0 F
5 w: z3 |0 a' y! t& M1 h: @' W7 s7 B" w2 D6 R) X9 Q3 k
最后7 X) [. V! ]# K1 Z
' i2 ]: f4 j" n5 J4 J! P
5 G4 [6 g% c; U# l T9 l3 Q 嘛,祝各位大师傅中秋快乐!
# \* [4 F/ J6 D/ `2 s# N' Y0 P# H 7 L+ `" g) E* _8 a# i( y6 `
$ S* ]4 A; g! n. \
3 h! M3 n; e# ^
! t8 d6 n7 k8 F9 A0 ]* z! T
|