& y" B9 n A* I* ~$ D4 h) |0 _( h# t
& X- F0 t4 z/ u; I( V$ n; ]4 A2 Y2 V# x' ~! N
9 c1 t% ?$ g* }+ y. p" K+ K
前言% D* m* ^4 E: W2 C6 ?' x) b9 l
5 j6 N- w( i, T2 i4 i; h# ]$ W( h% Z
" R7 o! d" v7 c, f( {" S 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
/ y' s, m8 K, ?0 `# D1 E6 p / }7 J% b" C7 {4 \0 ^
# M# z O3 [. g$ g . O9 h3 }2 Q9 J) W, K
6 X* D( a- Q2 a* t; i+ e
. E l, B3 k/ }- F
漏洞分析
* b/ m8 ^( D7 R8 v: ?
! L3 r. u) J5 Z0 p3 ~+ a7 j% O, n' C( y1 p5 E1 |' g: D1 C
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
' l; A. \ n- X; k6 `, K" f& B
( \9 U, g8 h7 {0 r, ^ `: q! l4 f
, l5 C" n b) r+ s7 f) l 1 G5 u: I+ G% z
' W( O7 t$ |7 Q& i" E6 E* w& A 对应着avatar.inc.php代码如下:# |) n" c4 |& X, c m7 o
8 B4 Y+ Y/ w$ p$ J1 m/ q
( K/ _5 `% g& D, ^, h$ h" ^3 ^2 C <?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) {
: V: P2 r O& B x" j. q
( T- r! T% q8 n' y) Q2 h- F9 W4 F( [
case 'upload':
. e6 b/ O o5 A) t) V+ b ) z5 W; u8 e: d; [7 o
9 g4 Z! w: R) _& K, K if(!$_FILES['file']['size']) {
, p# i* L3 l. a9 ]9 U
" L# t! \" J: [9 J0 v- O) Z
1 F' G" U* g( V/ |( T$ I% n4 d if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
0 q/ E* r7 d& A# K4 q& G1 Q 1 K& _/ c" B _; X1 N) V, V' |7 S
5 G1 D. b* `9 x/ n; R ]: h ~/ p7 _
exit('{"error":1,"message":"Error FILE"}');
, _/ k6 k# R/ p 0 f6 S. A D2 ^/ o( e
0 N* n# r/ p* E0 x. B% T
}
% c; `) k% O1 C2 m: d 8 \7 @8 {5 F* }# k2 s
2 m. ^, J( ` E! B require DT_ROOT.'/include/upload.class.php';
3 ]9 D7 |' e( n 4 ^4 m' Z" w9 C
/ E$ @- l) g1 p5 O
& Y/ l1 Z$ F& Z. l6 R3 O
; i& c+ ^! S J1 j0 Z% [
' t. c# ^* H: o1 Q' r; l $ext = file_ext($_FILES['file']['name']);7 t% J/ s% N4 i* Q; a, `
/ p$ |6 W; o3 x: z: v" [
2 U& D5 l4 O0 a# {9 w
$name = 'avatar'.$_userid.'.'.$ext;3 G1 V6 W% P3 a
0 V2 v+ b& a, W4 D( {: u9 D [7 _3 O: i1 ^+ a5 z$ {( y+ O
$file = DT_ROOT.'/file/temp/'.$name;
- Q* R: M+ ^! L( ^7 x# \$ ~/ k4 E
. s: A& T: g6 ?5 Y$ x
. p% n7 C3 x' h E' d 7 [% _% X: \% l1 W- M0 q
6 T$ ^! V3 R7 i2 x. m% L3 b+ x% T* c& F4 b; ]1 C; }2 I/ A8 V
if(is_file($file)) file_del($file);2 D1 A) j) ]& _, }! V
2 N) u( G, o2 Z' h7 H
0 {: y. I3 U% S$ x" f% \0 ?" M% d; s $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
/ i4 J" D- s& _7 M" x! u X
* m" s3 J9 Q/ i4 S
% W9 s- G1 \( _ M0 i2 q
, F) Q, T# ^; @' Y: G
) v% X( B4 B2 C1 o5 X
N! ^. q5 z& s6 {$ H6 d. @ $upload->adduserid = false;& \2 L0 b5 t% l R* ]: n$ i% y
5 W& ~% J. X% |" d
* R$ X. ^0 S5 g8 E$ G& @
0 j* K3 a" r0 \$ U
* u( S& Y! v2 O3 }6 J, B; i. \. e9 |, i' M" _
if($upload->save()) {
& ?- `4 U- \+ A: G/ R: A3 y2 ~* S2 O
9 e5 G. y7 h" i8 K% K0 G* {$ a
" A1 k7 e5 ^( n( R- @6 ~ L ...; f8 G% W3 f# ^: N# T
" l# U5 m1 u$ e. Q' s
) e, o. J+ R$ k; h } else {* }+ b: [2 u7 E4 P" h5 f& I
+ b: w$ r# K# X) n1 ~0 l) M5 @, N8 a" y. Z
...% `3 g3 ^5 k! ?1 g
) [* E6 C% e1 I9 J
c5 x5 H+ A1 S9 \; ?
}
$ I' C/ ^% ^, l- A" l9 J& ~. r " D, I% `6 l4 X" C ?8 |
- {# Q0 X1 X) I0 ~4 Y break;
3 R7 q2 D* g; B; ^9 U
' l' C& n- d* u/ L2 a6 `* K2 }1 _& r$ S
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
) m v* H( ?; p- f ! L/ o- P, S2 t
# U% d: D4 y6 A% U: i upload对象构造函数如下,include/upload.class.php:25:7 O2 |( N& N; J( ^ [' F1 H$ A( a
0 |' O% w9 a9 Q5 q+ _1 K" B
: z& s# v# z1 i <?phpclass upload {& {3 T* m4 S( K; W2 ]7 T W8 p
3 f- t9 L) Q& t ?9 o# C6 W6 `* K$ M. d7 m |# n! U! k3 E1 |, X: M
function __construct($_file, $savepath, $savename = '', $fileformat = '') {0 E9 `2 ]. P0 W- f- w. T
~2 m ]) f, _/ U
( `3 ^; }# C% u global $DT, $_userid;* Q# T4 H& s o
- ?, u5 j* }, m R7 E
' K, m6 ]; L+ n foreach($_file as $file) {
2 j4 M# V9 S. r% D# p- G g
, d/ J- E( d$ u# B) s! D8 M) C! e8 v1 {3 g9 ^3 g8 V2 z
$this->file = $file['tmp_name'];& ]8 ~: W/ j: Z. H
w! r; B. P+ n7 }* H, W1 M% ]
* e. `6 d, x) o1 K4 d, h |
$this->file_name = $file['name'];$ }# f( Z3 f( R3 t
0 N' r, }7 \$ Z4 `1 \9 S
/ X* P! x* G' m $this->file_size = $file['size'];
: o; ]* H2 @& ? ] # a5 V0 e ]% J* ~4 _
& y- u; b% M. s; J" x" g3 G- ] $this->file_type = $file['type'];
8 f t) o! h2 M) q/ j6 H: n
" O( J* b5 h8 F$ `
# q' i! I0 t* u $this->file_error = $file['error'];
% t# i6 @5 x) E" |" V5 V
% l( @6 T0 q5 S8 y- l: [
) G4 L3 f, P3 d0 E/ W
$ }' b$ c& H* q1 y; g
( m# C8 d8 A* t# O: l. @9 P5 U6 A
3 E/ E' G/ a2 b$ \ x+ x }6 F, J& f; q. c+ d: H
+ t5 F& x O! z: z+ J
7 Z: y* t3 n. j$ J/ P
$this->userid = $_userid;8 o- F6 t5 }: _- Y/ n, p$ i
) o' C! T- ~ r5 O/ L" O6 D' ?1 _2 i3 E7 h, h3 x
$this->ext = file_ext($this->file_name);7 S1 j! E& o! h% v, F# Q* C4 o
% L5 ~9 Q5 F$ u; Z& t @# G& Q: i& Z
) | T' f! m$ I: ]% L $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
( T4 Q- @, D8 b6 B , b0 k7 H& g; X/ ~( W
# v" z! m* S! {1 W j$ [. q5 O $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;+ l! M$ n1 h/ M. |
7 L8 n- o* L) ^$ k5 E& q
8 o" a1 Y& O! D, K2 i5 P1 j
$this->savepath = $savepath;
( I. x3 p1 O% A1 ~3 j4 _! d4 U/ n, k
" a+ `3 `" b/ h6 z* F4 `, ~. L
7 h8 ]! ^8 _8 ]- H8 q& r& w! ~- A $this->savename = $savename;" Y3 L8 U: C. W+ U# a/ z- E) l( b
: X6 P; E# N) Q2 p9 j6 w% [$ F2 u0 K) u
}}7 d3 g3 o* |2 k8 H! D
* [3 ^2 y) x% _. w ~; J. j5 ^
$ {( V5 r$ u' V8 V) H8 q; I) p2 u" f 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
* c+ T7 q8 a6 |- | 3 x" D( `. _6 ?. a
# z- q @/ w9 Q# f/ x3 H
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * G6 ?3 P& r4 |/ W( f) k. p! X
# W$ @( }+ j. f. e- t. {# G. w% p
$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
( h9 ^, E. S3 T$ R$ J/ O # b6 e/ J/ c' L% J/ Y' `
2 f" Y* o2 k0 [6 h( A
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:* {9 f# {# D2 {: _/ N/ ^+ |
" P, B3 `6 S: G8 U$ G3 e6 _, G8 v. W% `( T
$ I7 |! w# K& m
* \, Y0 F9 y& J! U2 X
& \9 G1 E; v8 G A* f1 @/ i- Y 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
; J3 u/ |: q6 h6 b& K5 N7 O3 A3 D _ 0 M9 b' n9 v4 h5 p8 V$ I3 x
( t0 _, |0 j2 q) f6 ~ <?phpclass upload {5 ^3 f8 R3 V9 s T: S2 n
- y% I- {8 s- z& M2 Z/ z' ?6 j
7 h; B V7 j# n5 W3 o; C
function save() {& ?% q3 A0 \( Y
' o& m# l: b! |
& N2 F+ C8 u4 `+ ~5 H include load('include.lang');
. S4 V6 f: S! Q2 m2 H" j ; b; j- L* J/ G# {
8 h% u# f9 F( w& {, A' J
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');$ r$ e: ?- S8 J5 ?( J# g2 w
( r$ `& q9 x% H0 f
7 q R8 Y9 h; S9 t- W2 Z6 O# @
9 ^! X4 N9 d4 D6 X# c o' c- H / x. `+ S* {, g( P) j- F
8 d! J( u; ?! e( X if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
8 I y1 Z d3 t+ i
3 h J1 D% K6 l$ T
1 }% e9 @5 n0 {9 K' l
3 i8 g8 t( T/ N w1 V
# E% ~) y$ V+ d6 y$ S( b, E* W: M2 G6 y; Y' k/ \
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
2 ^; ?# b3 g8 s7 a$ Z2 I
3 m7 K+ X) K$ Z! P3 H, T9 c; z+ F+ K6 ?0 \) L6 j2 o
9 ^8 \9 h+ c7 [, s, ]+ Z 8 g! i# F) Q: P7 p
* G( D( _8 w& K- Q. j% o9 f% W5 } $this->set_savepath($this->savepath);$ A8 U5 P0 J: u. e- c
6 |, R* x3 w* U. Z2 L: m
, p& u+ `$ J1 Z) I: a2 o- N$ j $this->set_savename($this->savename);, x- D/ [* q, D# e5 @2 S6 B3 U
! D; R( W* [) W j
\2 [- W u& X6 r# A3 r ; {7 Z& ]: _7 K% D6 k# y$ S
: @8 X5 @1 I" }( l! K
% j7 B$ Y7 Y+ r ` if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ _$ L$ S5 B- s# Z9 z
% Z. V$ D0 _9 R6 w3 N& c; h# S; O J5 s2 v! e; U2 i
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);2 L: n$ U4 o6 m0 _) A5 x, Q
; w. R6 S" J9 x: C$ r
5 h: G9 T i$ `# ~ H if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);: A7 ]! \3 Y$ O8 A5 `; E# B3 B2 P
. n0 f0 S0 V) o. d' J- w% ?. j; x
% G& A, ~) Q. a9 M8 `* t# w
( e( b0 ?0 D( y 6 a3 Z3 _6 t3 e7 D7 j
* [+ _( {/ w# [ $this->image = $this->is_image();8 V8 B3 d( T) Z7 e" z7 a
! q# ` D: `6 y A; P, h
& N9 S3 d. A0 n if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);1 w: G+ ?) j/ ^5 G& f
& j8 q7 F2 H7 Z" h5 g
. U9 }0 _2 A1 b, @1 W' M4 t1 ^ return true;* F0 k* b+ I3 u9 \- C
: w% W# Q" \( _* p0 Q+ E
; G( J2 O! Z- |1 b }}9 K1 D2 `8 k% @% R4 f8 G1 M' V2 Q
& b. K \0 M( \+ J1 y; y# O
! K. j( d: Y9 z4 ^
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
& _: V8 |2 F _! g6 ]6 u9 [3 F
) y: o3 }8 Z& I# o) E8 P
" N1 g' I0 D9 } <?php
+ q" h" ^' Z& m / B: [/ I0 M' Z5 C" u
- s% Z* B: d/ f' { ~( w function is_allow() {
- o J+ H' z5 Q& C0 a( r, ]
3 C+ N {; d2 u0 E+ e* l
- q! R T. u3 x" W, d if(!$this->fileformat) return false;' B ]# z3 x% `% u, ]
9 Y0 _. ~* f. D- K9 z. h# t( @1 `9 n T
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 D3 c+ W3 }7 H+ G% c
+ d1 t6 W! Y- X# R
: Y1 i0 M! @' F0 z; G
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;+ t' X" z; z8 g- F2 b
. Y# N- s H6 T2 j0 Z
! M( {3 i" _; Y P# n ^+ o
return true;
( T | n0 [6 l( X' \
" V* w% a+ a1 V' h. ?2 L$ I& t
3 ]" |" _7 Q6 Y4 M* ?* S1 i" ^; u }
2 T# Y6 O' }4 I; e2 m $ D; I: n i* M$ {) O& y
# Z% \1 x* g2 ~9 q2 h1 P
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。3 K G c$ q' G. H: ]
8 b: E* K. p* w }! r$ j
- W& a6 L9 ]! W! D1 g 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文件。 q/ w7 e- j' ]4 e/ Y% N2 H
: T' g, c. Y. H _( q" l: x" [$ H8 F4 f* D
漏洞利用& ], P' c3 a. r9 b; r$ S& |
& p5 ]; `+ K1 l
9 N4 I/ }1 Z1 Q3 f7 x( L: b 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
& t: J% J, `- K$ J5 V& d0 G1 `
1 k& _! E+ Z2 X
' ~# s; Q l& }# X+ `
; k4 u P+ g# G7 y* n 3 f7 Z! l1 n8 E; v& {5 Q
6 K4 q: h2 A9 m( `) r6 g5 z
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
3 N, ^4 w/ Q* q$ \% B0 ~% U# j
1 M i: Z% l$ Z6 g+ t7 Q) J* Z* Q* T7 I" C" h% M& }5 [
不过实际利用上会有一定的限制。
2 u7 ?3 u- p* f& C& n+ g / ?) p# ]: y- R) @9 \9 T* L
9 R8 o% i& ^7 C/ M! G3 x 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
5 O7 R `9 `0 F- v4 U; N r& H- }, a1 x( R5 ~9 U9 |* v
, `2 {5 t, m k % W( {' k: O2 m* `4 L. l
8 S& h, |8 J) m8 R, u' w. W/ \; D( C3 W
- K) N/ K+ O! C \2 G2 N: H i
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:) W' R3 P8 r* u! j
2 [, Z; {( O2 \3 L( F, t
- N1 ~" C. e3 K2 Z& T: y( e
省略...$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]);省略...
{" W# O9 v/ y1 O' R" o
3 {+ r9 f! B3 k& k* l1 t& F9 N- p' J4 q
因此要利用成功就需要条件竞争了。" k% i7 E/ o. f @" b
9 P' B+ Z1 T7 p8 R& \; o; R# o4 a- ^4 |% s. \5 @( M% }
补丁分析, N& W' h2 b5 |( C; a
! ^3 L9 {9 J' X+ X1 I3 {) N0 I
" ]: Q: j: T+ } n7 L7 v; h
* D5 n/ j p3 U0 z5 k , Q. @' O Z. ~1 l/ p
, d3 Z, w9 t' e5 J( {
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:+ ~, _+ G9 W8 [' i8 P' }: S
+ N% |( A# e, a: ~ u
0 E# R' }- w1 l: D, p% Y function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}+ H, O8 l& _+ j) Z% ?2 f3 G8 k
2 ^( Y3 f L7 P7 m# A: ]" j
2 n3 U; E7 {9 I
# j9 t1 H6 s/ ^1 _& y" n; o ) x1 v4 Q% Z- \" x: {/ W$ J. Z; n6 o$ g# Z
7 v- o' U \1 y/ v# S
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
9 j5 X4 ~2 K, d+ g: O: u* {: H& f 6 f- Y) S+ L, |- U; p$ J
/ b4 b5 x) t1 n& }' d# Y1 h5 ]1 | 在is_allow()中增加对$this->savename的二次检查。/ d. X4 t- ]$ d
* O+ k- X* V9 c. H! {
" z& `' q8 l: ~7 E3 t
最后
5 v# M6 h p X/ q4 x8 k0 g3 R
" H4 m5 w3 H' w2 w, t7 r- d
3 n. p; ~" I' ]! ~) i% ?% \% t 嘛,祝各位大师傅中秋快乐!
$ c+ f1 m* f5 I P* e
" Y8 u- m0 B( v- K% a2 s- a2 S$ |, w
# A+ Z7 i I7 c' P
( g) {. @# `' v- c* v& w+ g1 ]
|