# z5 f9 |- w" A$ z* x- g+ Q. y
& F8 u, M# X6 Q/ f6 E# o" X' ~
& g( g0 @8 G. V- n# M) C' g) Y) E1 V3 J+ W2 ^! {% A) T8 P) ?
前言6 @& i3 x- {* a1 ^$ _
0 m% t. }$ @6 t
5 Z4 V4 Q: f( v7 [- ^4 \ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
$ O+ p: T( _+ \, \# C. ~0 T+ M. E - T2 g/ @* ~7 V. w+ D# ]' y
: X" c3 H% y" f3 A* x
$ f8 z5 |7 h$ z; R5 ]0 ^
+ _6 i& [/ A: j$ p$ J* B
w; G3 I/ h5 I z ~, } 漏洞分析
7 k3 `! G" r: F! x) V& P! `
. g8 h) z/ [+ N6 K' y2 ^ M* _9 y. H- {6 u
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
6 w N2 m4 H! k$ W H* m
# ~, o+ X- e' j0 X4 F6 ] I
+ v; X+ ^1 a. V
8 v- }7 x) R) z# A; K. o# R
3 R, w6 H6 w+ |2 k' B- X: w: W, E5 x; u- I4 g: v
对应着avatar.inc.php代码如下:! ]6 {" g# ~0 ^; E6 t$ V
/ Z1 J' u$ M) C4 k3 `3 b7 }+ e- L
1 h" N( |+ s8 t* E5 A! t <?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) {" h' x; v( `$ v5 [$ }- e" f
/ y: ?; A9 w1 D$ f" y" F
g+ U y' j6 w3 J case 'upload':5 ~* U4 a' e _4 \* j2 @
! _3 u" s; b4 Q/ i5 c5 J* d# d A' _6 J2 C3 [* p
if(!$_FILES['file']['size']) {
( W% T v0 D* D1 s& \3 \
+ E3 v @# a* ] x+ C$ V# N' ?# M) M
! B4 j4 P4 d# v, M0 B if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
0 A+ W u* c/ G( f/ v / g- K0 D( y d$ c b8 b: ?8 l, v
( ^5 `& @ a/ z4 }, |* F exit('{"error":1,"message":"Error FILE"}');0 o' t; i: f& o5 r# [+ T" n- O: {2 `
' G# g5 j+ R( o$ q# G, W
( x+ u, Y6 c3 Y& k+ g }$ Z% P8 Q. r+ g! M4 X5 h
, U ]% k) J5 K9 B
# w$ l: u: T; P require DT_ROOT.'/include/upload.class.php';/ Y8 n0 r1 R* v/ P C9 {% ~
1 ^) b) [1 X1 v6 ~
! D) j4 M) E7 }2 F& v* |* z
5 I' X3 a4 A5 y6 x( f, q
9 h' X: P! q# B' d$ F9 \+ y( l6 w) \0 i; F1 k% `( D; k
$ext = file_ext($_FILES['file']['name']);
" q p8 q/ @; J; d M7 U4 g; P- l3 f V
9 b; u* _7 |& s; x5 I; Q
$name = 'avatar'.$_userid.'.'.$ext;
# j* M) } V) n% N7 T; M. x9 U5 N
' x4 \9 J6 T0 u7 j3 W4 h# n- q* g) ]2 a7 t
$file = DT_ROOT.'/file/temp/'.$name;
+ c/ `6 e& N; l- p' t( t' t4 U
+ [4 m# @$ o1 V% z" K( B8 |+ T* P c& h. \/ k' d9 e2 G
; r2 J7 y5 J$ v
; u2 C( c7 G, s' @5 |
9 ]4 D: r4 [. q if(is_file($file)) file_del($file);
( D& M3 \- e9 L- L7 Z9 g% n3 L
' j- w7 l% Y7 \3 w. \- T4 j5 x: `3 x: ]
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
. Z/ }) |/ K) B" g0 i + C( k# h9 _2 |! W) r# Z8 p& ~
6 y) Y2 M" H; e9 b, R0 B
, x" s. `' U* e# j- L l4 e" }* r
0 H9 n2 \1 V* t- Y. W
, }! G$ D0 K* b n+ |8 c
$upload->adduserid = false;
" }$ g0 o5 E' ]: k0 D* \
) Z- v! t: Y) F: ?* l0 L1 a; t
" q/ c: R, I" u$ J4 @5 S
; p" ~2 u* H$ w0 Q 6 z* \, K& r5 I( X
8 K9 E* [% C n% \/ L
if($upload->save()) {* L, e+ ]: i: f
: P# ^) s4 U, r$ D: X: |* V
- O9 Q2 x2 K" u5 P
...
7 ^ t7 q5 @/ K6 s" \5 N" O
- R% Q4 }9 {# K* |8 o
: G3 b& V; ~! w- x7 | } else {
- Y7 g( _% O/ B) I
* Y! L* K( q% G- h
# E. L3 z. o) g/ } N ...- ]# |6 b2 M% s7 S
) U" p: X' U" S# F3 C5 o+ q2 H ]! |! ^5 @5 w" y3 S2 f! }( j
}
1 l: k7 J5 {( f
# E" u. T+ t8 m0 j9 H# A+ `
9 D( M" B( f1 s, p2 o5 Q0 \/ L8 p' x break;
3 z& {* u; p j6 k / I# K& L: Q1 |. P1 `
) i9 |; |# {5 B( t7 e 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。5 D( p5 ~# W3 w4 E5 ?$ ~
0 j4 u- w# H m# t% X" A: r2 G& u m% l9 c
upload对象构造函数如下,include/upload.class.php:25:
7 F* u* H0 S' L- S* P. { ; F [: v) u( Q- S% E
2 @0 X; P1 W9 x# F/ C2 Z0 Z
<?phpclass upload {
/ I- i& ]' G, M" l
9 a0 n8 n7 I/ [
. u) v" w8 H( f7 C1 p W3 L( l function __construct($_file, $savepath, $savename = '', $fileformat = '') {
/ g5 J, v( l9 l) e6 D
" G$ h5 J& N# f9 @ t, c& U, K- w: P6 {* T
global $DT, $_userid;! O! e( `8 y6 D
# C8 [: V) v$ a5 }+ L
, R( O- z7 i$ B
foreach($_file as $file) {4 i- P" {, n* H! O1 [
- c* q0 E* p% d, a; Y3 Y( u/ j9 m- a0 v/ \" Q! u
$this->file = $file['tmp_name'];# }( Z" F: i& W! ~. \
+ d. p) d0 K) }/ j: N; Y1 G: V
" n M4 F+ G2 y6 P3 F' L
$this->file_name = $file['name'];
) r" ~+ Y, s9 n) D' o% ^( E0 x. V 2 {& S& d5 r0 T/ J
- {0 A$ Y& b# C5 @
$this->file_size = $file['size'];
5 Q& _+ C& p8 n2 _5 k
4 X: m4 y H5 ~3 V) M% [* i/ @. h. T2 s8 L# b- n+ x
$this->file_type = $file['type'];
/ L, D9 Q1 Z/ Y( F. o
3 O9 z$ X9 R3 H0 _% D4 C* N% {3 T2 O& X
$this->file_error = $file['error'];5 l3 b" L, v) K
2 r7 w, U$ _$ G6 y: ]2 @/ n, w' N3 I0 b% p$ @
+ O9 C1 h8 V' m7 T' s9 C: `
4 ?# R+ |) A9 M
7 b. l: t% ?& ]2 } }7 }6 i T A% X e" A
* ^$ F, T4 U7 _
. ? R" V5 { {3 X7 q $this->userid = $_userid;
' ?/ U) \& p7 T 2 v7 G+ l, _3 P; T+ `5 h
6 l1 E: P/ M" [. s6 Q4 D# K
$this->ext = file_ext($this->file_name);
4 N0 K' w6 v8 C
' A' w$ r" w, g4 J$ N" J4 e5 s e; s) F- G
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];& W7 a+ M v, t$ y
& _' p& r6 n6 ]
4 ], S( r+ P' j' S $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;2 c: [, u! S2 T7 m7 o( x8 G6 R
$ u/ O- Y% H( V+ D X' Y# Z/ Z" l4 X# {: i. ?
$this->savepath = $savepath;
5 J" T0 r3 H+ D, q2 d- A) t8 d$ W : S: } L s( ~
. ~: v6 `( }6 F
$this->savename = $savename;
* e$ C/ y0 S$ `
' {" x% Z3 O1 d) b& l1 ~1 Y8 z3 P. I' m7 q5 j, o- G
}}
8 g8 g S& i# z- T+ U$ L 5 p' i3 W8 t# c' }: ~* f) M4 C
4 {. x( h h9 Z* V8 w" |
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。1 d8 y7 X( f% j) Q* u" G
; C4 k7 R% w/ e- G) i1 @1 j
8 `8 @6 R" O) N& p1 Z, j I 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 * E1 K b D- J( p! f1 L
! t0 ?' }% K# z8 y, n4 S8 w" w5 X
2 {! Z8 F9 }# {0 u $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( x' j+ T$ f; T# S% g
O' M& ?) [$ @# d" P
0 V9 w' e+ o# X# {- o7 Q5 a 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:+ e6 f' {3 _+ T% ?' E
$ w8 w& f7 U3 |$ n
: m P, C" p$ Y# Q ^6 p: `
% K# [9 _- O& D* | * p' T8 A$ I# w, u( G
5 v% }+ e5 D/ X9 i. o; I* j
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:/ M# |: _7 R* v$ A
( f$ e( ~3 h: G; J8 l# O4 c: Z/ \, L! f/ b! T; D* n3 U. E
<?phpclass upload {
, Z! b* k7 T5 ~# X/ @, Q( W, y1 m
2 ~; k: y7 G$ x9 I& R3 R) A4 T5 y& G' U9 V, _% ~- P. L/ z
function save() {
8 j3 I2 q7 s4 U' i! r% G4 s2 n( S ; M- ^# M7 `! @! B9 G) K8 j2 p
/ u" _3 l. s# |7 W1 \ include load('include.lang');# A4 o( I+ A3 R' p
* F' Z! i7 u; h+ J* Y- h. F, y1 P6 T5 d6 @8 w- ~
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');* C5 M( W+ A+ J7 A: }
- {& R5 M) o& m0 u) V% e( p, d! B7 y8 L. L6 W4 V
0 Y6 Z. J; e2 u# i3 j' ]8 y9 q
4 Q* j, a) i: R- v. N) W! `
' J5 o5 s, T9 r' ^ if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
6 E6 ]) n- J% n- [8 U5 U $ P4 U& ~' R* Y4 T, h* E/ x* Y
% ^% m, T& K- U/ ~- }( ]+ s
( w4 y" m8 |" x5 j 9 |" Z4 i" T, p p% D# A1 L
* v7 B3 b2 L& u2 y if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ r/ K0 {* Z% w" X' V
3 r i$ @% k$ g7 r: R
+ X& p; y- z7 A. t3 T& t+ S' D4 R' }8 @
' H L* p! X0 R$ H9 a O
* Q' a4 A- }9 r8 O) ]! |; c4 X
) Y! R+ |: z" [ $this->set_savepath($this->savepath);
3 j6 {3 d9 w9 i+ t4 I
% v0 y! v2 c1 [- y' z; v: w
! s2 g4 [* k) d4 s# ^6 M) j $this->set_savename($this->savename);
Z! {9 S5 ?2 ^- I7 J
2 X$ p8 F) b, o/ t6 w9 V
3 w1 T/ D7 H( @1 D
8 v& b) f7 B5 l. r% C* O1 B 3 E2 b7 I/ U; i ]7 J1 g' Q
, N0 h/ D9 M/ k9 v' B0 O
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ x3 q& @5 L4 M( p' T+ L
! X/ V, w |* a6 u% ^
. v) f' N, ~! X# Z2 ?
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);$ E" y, e& m0 D- M# T
( R9 \$ J9 g8 E/ P! a
4 @' r% g6 y" y+ ~- f if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
9 p8 ?. R" J% W* m6 r
: ?% e. ~% |0 S ?: H9 C2 Q8 _- e @1 i Z% x1 [3 u
: e! W3 E( C% k# J
1 a4 K: i, {6 P
+ c8 \( v( B6 J8 m $this->image = $this->is_image();
4 H/ P7 B/ O* V# H; o! q7 W
: N+ u% f) F$ I9 _# ]
* K2 E% u5 q1 V if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
3 s& @' d& k# }+ L : j! {% u5 P5 T" n1 i
& k D# W9 F3 B% | w/ C a
return true;
0 E5 `" i* R2 G) s+ m0 u6 \ ! p. N& m- W8 c7 I
, D) R; e# n8 z
}}
2 k: {9 [ d& j
7 K7 z. h9 D' k: L" N, S& _; ^/ f7 H; R/ [
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
1 }/ S0 l; a: E5 {9 X" u- `& C
9 r# p. i- i1 c! G# v3 r/ S" b5 w& [2 T/ j' h( {- `
<?php
/ }1 i7 h0 o' L% O$ l4 J3 X+ L, p 0 ^3 L. G. a: `9 k
1 a# c+ \2 R8 Y+ C function is_allow() {
2 A" c5 R9 x/ M$ S* @& o4 I/ w9 Z
7 t) ^) n# f- [7 z3 b+ K5 Q
4 U0 [7 P2 V ~4 q9 M7 u if(!$this->fileformat) return false;
4 V7 S. m5 o* J3 u4 S: @
2 Z% Q+ J' l' I, b: e( [5 Q; B$ ^; Y' Y) _
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;' E9 T' W0 I T6 |9 h5 o; a
) E* H. {( W( B/ {2 M# m! Z$ w# I
, }& O* ^0 m+ K 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;0 ]& g: e% ^8 E% V
6 H1 D* U3 M; e! s2 }
, j2 T6 M' w, b, L return true;
, T! c3 w ?# |' `7 M/ K
( ^" x2 U* Y! g4 G0 H4 d; V+ G
6 y! s7 |. j* B4 S0 b }
9 u5 P3 V6 `3 Q, n. ?5 L1 w- O2 H # K6 D. U5 G" I: A" l
0 Q' {; o; g' ~3 y( U9 _. j 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。/ Q( s' Y% k( a( W5 a# @/ a2 H
A, X4 p: ^; k2 o8 u6 d7 B2 L; _, Y. I+ y
接着会进行真正的保存。通过$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文件。" G/ L: F/ V' S. n7 t
4 H2 J1 f# ]8 ]% j& v3 Q1 g1 o) N) `: u8 v* Z
漏洞利用2 U- m6 e) e6 Q7 k4 _
; [& R. P1 J$ D6 U) p2 t' P
! _) n% }+ Y- d1 {0 L9 [! i 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。$ r; G: g0 R8 d _8 U
6 i( {9 w3 x! `% w, `. r
# W! \" X5 S0 f4 [6 @/ l$ T ' ]2 L+ q& |; E' R. H
5 s8 t: z( ~/ Z6 B
" p& [. d2 j& C1 @6 q
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
3 z" |, |0 M) I% R' b" b $ _/ V: V+ ]5 T- T6 ~ e4 r/ }
; @& T# N3 C. E' z- x1 e/ g0 ` 不过实际利用上会有一定的限制。
) g( M6 X! I6 j7 k0 u " d: `7 S, D6 n" U% |6 {1 @5 a
+ W+ K+ m+ E& [. G/ u d7 f7 ^* {' O% x 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 F4 s9 j/ z. N) r- B
2 G+ H& f& u' d
. f V1 B; D% R9 s: ^% e( j( i- A
- w9 i9 Q" b' B+ T Q8 o ( y6 P, J+ h9 Y4 g) R% z$ w! T3 R
- [( \' H* z5 D' h2 k
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:6 x) f0 E5 i( F! l( {' l
1 z2 G6 W1 j: x6 {3 Y8 U' N6 P! H: m+ ?" {8 e9 t$ K# h: X
省略...$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]);省略...3 K" z3 g& A% g
1 ]8 q6 S/ W% k! X" @1 A7 {/ o
! C3 v) x! Y5 @6 O3 o' H0 ^# t, D, @ 因此要利用成功就需要条件竞争了。
) A5 a: ~5 C2 k$ {* s& \
' {1 L! ~3 H$ R3 P( e! _3 u, h" ]+ f( g: i
补丁分析' ^% B! O+ E$ s! z6 E" V0 h6 W5 ^
5 h! d4 u; i7 S3 K2 h8 ?
# I" q' W/ g: K: W- T0 F
# R% ?5 d( G% H% l
; W1 O( U I: ~& R
9 j4 N3 s' p! f 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:& O8 W1 d, i) `; D+ m1 p' {
9 p6 M6 S( P' k3 W7 s" @" H( @4 x# h5 \- {( w
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
$ o; Z% l; {. W7 O3 [5 I) k, Z7 G
; {7 k! H4 X7 B% B1 ^& G! V
* J5 r5 M# l2 k : J! S3 _" e) P. P
! k7 U* q0 Y4 k2 ~! Q1 e/ g) J! z: U( O2 W* \/ e! L4 m
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
2 h+ Y0 O2 U: y8 \
1 x. n8 K5 Z2 ^/ s$ n( I$ Y5 n
% `7 d8 L7 _4 N% t7 t 在is_allow()中增加对$this->savename的二次检查。
9 F' J- D/ k% |: `: `) u F$ H' \% o7 I3 o, x5 Z
6 U9 F9 |/ B( F3 H 最后, x6 T* O g/ V# F% R
* {: W* ~' D$ H5 ]* x1 g; _( t( ]5 k& h, A7 S+ d
嘛,祝各位大师傅中秋快乐!
. | C2 L4 `/ ?, |: O0 v/ S1 O : A o4 o. @: g$ z
9 N Z1 a% V5 \9 V& J2 D+ b: B 1 _% i8 J' J! m6 F, K
3 ], L6 C; ?1 ]/ ^) O; J' C: D2 p
|