% O( u7 K; C& y, L& W
' V$ R* q5 `) P& S% G |( J' P
9 O3 V6 {7 ]9 `0 k
4 k$ A) n9 B7 ` 前言
8 R7 ?6 G% ]. m" n ^7 ]/ Z; `* b; Y& N' W8 f# T1 \ I$ Q
# U) {# _8 }) K: C4 h
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
% g- }" ]4 d( a, W9 M+ e% g+ G 5 K" P/ p2 U/ K, I8 ]; z6 W
3 M3 z1 [# c: x2 w- }
+ V% _: n/ a! p `+ \ - J9 k9 f; i' J7 U' X' C+ B, E
5 N$ B3 p) h9 g1 N5 r 漏洞分析
1 |1 o7 T9 l5 Z }8 k7 i( Z
3 W+ E ~: J. C9 _ x& L
3 s! \+ o Y7 ]0 s( r 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
, r! V# ?" ]2 Z" X. O8 A1 j; J8 d$ w+ \0 q . o# y' w$ ^; D+ L* Z- a% f. q
i1 U7 ^! L M3 P
" g$ K& `) V$ o; M' z3 o
( C0 H- h: V. b7 J, T7 B) a1 R' E" }, ^! _; ^- q2 b
对应着avatar.inc.php代码如下:
, p) |9 ]3 E- h; |0 L# x0 l
: m* V, |& H1 O3 E0 v' C& @) F6 C$ Z' n7 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) { v% z+ `( @( W& o2 W
2 `" B- j+ F( J- U7 i# W6 S4 G3 {3 s* Z6 B. v
case 'upload':* _- ]# t' G. C Y6 L, m% z
/ F Z2 o) \+ P L7 R: `; a
4 y2 K, O2 I; ?' _2 X( W
if(!$_FILES['file']['size']) {* V+ w: \$ u s4 u& d$ {' ~2 d
; @9 O, S+ @! X- D
3 l/ ?2 Z/ B+ z if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
" U7 y. p" M: P. u
b. b5 X8 `" k1 S" B( T% T; `; @
exit('{"error":1,"message":"Error FILE"}');
' {3 C- D! P6 V; B7 D 4 ^$ v0 R! z" y. S# q. I' o
4 \1 r0 p! I- w+ q }) n7 r0 ^* S3 h7 F# z2 l7 q
/ A4 g, ]3 ~+ o9 F8 p5 B& w
& p- D6 ?9 S& N6 y) @, J require DT_ROOT.'/include/upload.class.php';
0 Y% k2 Y% h! a- ?& W' N3 ^+ T 4 t; y! {) W7 c5 k
4 b! F' d) X5 k5 k0 h1 l6 d( q
) B* e9 y& q9 x! V+ q 9 \, A1 r% K: q+ L$ @8 K
6 A0 t; u$ a/ B# s" G9 E
$ext = file_ext($_FILES['file']['name']);( W4 L% D! \+ p+ d* g2 m/ K0 z
) {% d( w& K1 e
: C4 Q7 `8 r9 A& a7 S# ? $name = 'avatar'.$_userid.'.'.$ext;7 ?4 y2 X5 ~3 I4 f
/ D0 ]" A! ?9 I0 d
6 `6 Y; d R. b9 e $file = DT_ROOT.'/file/temp/'.$name;
8 h# k& N: V) q! |4 G
- }3 ~5 m* M$ U* b: P$ I s. R& z
* T) K# d" |: z- x
9 {' F/ |. }4 E& s
5 X+ D+ L" ^3 a/ `
if(is_file($file)) file_del($file);
?/ q, `$ C! c ( o2 o# |5 H) }5 x) e& P
% w& @# t4 W( k6 M $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');2 g$ b: r8 d+ }; n& X
6 P6 s& ~, E: j4 o/ e8 Z
* B- @8 `6 X& I/ J: m" W2 Y$ X, B
) `' a! A! D+ I+ g8 Y
1 }( x* L1 O7 f6 C2 ^
# C7 j0 S# m; ?3 Z, A( J( t+ M4 i9 r $upload->adduserid = false;
/ S) |- b7 x: R1 x, a4 S
7 h$ F- f/ c1 J2 n1 O4 C
/ u5 _0 K% _' k+ t ( G2 H& d- B1 [9 q" y" x
( Y; R* ~: K2 h
6 z" U0 N0 W& y1 u/ T if($upload->save()) {2 H; l( P9 R! m2 n8 ?
1 _- [( c1 b5 ?
0 \% W& o! x1 d- n/ A6 R# P
...
9 n8 b! y4 E7 F. z 8 V- M4 D) E# [
* w2 I3 T6 p9 J- _ } else {2 ~+ I& h4 r+ Y
& l6 |1 |: u. N9 b9 ?) i
) M& x/ g% x7 `: _$ z' m ...
$ H: c. s {* h2 p, s) a J- k1 U, u- m5 ]5 u8 p
, \7 Z* [/ c; V. F, r( r0 D9 s }: N7 o! s$ H) z3 V% A0 D
- I; f! E8 v, y0 G1 W Z/ k
+ W/ x5 s% Q( N( m
break;
2 q, w) }: D: X4 V, f$ D
2 [ a5 ?4 z$ A' x
8 x- R6 G- \' r- i' x4 C 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
3 v% e) G) k! ]) {' [9 Q! k1 {9 | 6 W6 o3 q5 K' h- `, s! r- z
1 z/ [; \" a" g; ^
upload对象构造函数如下,include/upload.class.php:25:( _+ X" n4 ]& n
9 ]( ]& G" y# ^* K
9 v- j& G' m- `0 D4 W: O
<?phpclass upload {" H" {! X' d) W# H: s7 O
3 U& \. @ T9 K, N6 V% Y$ m! ~1 C9 e5 @
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
% P- Q4 E7 r8 y4 ^3 U/ X5 ]
# A8 |$ J; S. \7 w$ W( D# ]" z
4 _- S/ ~) n) l" G6 O. ^, i, W global $DT, $_userid;! W* J- d- M) T0 }
5 }3 a* T7 s( f$ ]- A5 y& e
" q$ b5 Q. o7 w$ E
foreach($_file as $file) {
; ]/ G3 B, N z9 x. c ! s) }+ s% j( r4 g6 n
7 k6 j) T1 T+ u! W6 @. | $this->file = $file['tmp_name'];/ U% W& T6 E) w" l9 p: ~0 q1 b
$ Q, s. G4 l& ~ E8 ^" n
/ P5 b3 d* V" a" l $this->file_name = $file['name'];# w, {, _1 s8 m
1 \7 P- ~7 v c7 m/ p! {% ^1 ^
6 Q6 k8 v3 c- S! ]2 n& b6 F5 Q
$this->file_size = $file['size'];- O4 A: I; k; l- O" x- [
" M, v }4 D% N* v3 n+ N% @
/ I9 H N' U( n0 T
$this->file_type = $file['type'];- m& ~( o8 O- s. b; s8 ^5 a5 A
m" k' _ s7 W! _
" W! j2 y$ J& u/ m2 x5 y/ t
$this->file_error = $file['error'];
3 K3 k) D) H# r3 t, f 9 d" s. l; |4 r% I# B
- Q. p8 J1 X4 q) t% |
+ E3 B; }; C; E
0 ?% Y% T9 A. W* @/ u9 l! M) Z! x7 Q
}0 ^! G# A; x) B7 L& X
: c& D3 S7 T9 l& W0 w
`7 q( S2 x! }6 q* q1 i $this->userid = $_userid;4 C! @# ^' ~% q, m4 T
- x8 h8 M# @7 d! {" `! W" C! ^9 J6 {" \- v1 O H! @
$this->ext = file_ext($this->file_name);; `7 R6 h2 ]: V
d5 g; l! `* ~8 l7 W( m$ ^, [4 F% D' U# J
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];* v' F9 E4 S. `3 ]# _+ _
" t5 ~& u2 G- @; R
7 A* |( R3 \& q" T. s' q $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
5 H8 V- F+ W$ [. P3 ~ 5 _) _4 y: M0 }! a- y4 S9 l3 ~& n7 h
k$ M! \5 Z9 Z" T' ~# V9 t$ Y* Z
$this->savepath = $savepath;
* r. W% t! y" l# N9 ^1 o, ?0 i ' p) u7 G2 a; `+ ^
. L% I4 C( Z$ _% n $this->savename = $savename;# w7 a7 r/ V, F
1 f0 g* S; t" V# g5 v/ T" U# p1 a" L6 X- j u1 B% e' E. G4 m
}}5 J9 l( y8 f/ _7 L I/ U
( t3 U: }' x3 y5 t, o0 f
3 ~; q6 ~: q6 N* z2 @" V9 t' H# H 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。( B8 G" x# @- g, v
: {6 E4 Y7 D: h" m$ P) y+ ^! q) S2 Q! A( Q7 i2 j7 K
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
/ b$ ^$ u) ~$ G; t" i " P" T& ^1 Y. _0 y; Q& T. `
9 ]: q1 S. d+ r+ [0 G7 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
$ R0 Q, W: z. d- L$ ]+ x; a
i' a/ J/ u# N6 N3 z
/ }2 x* k- x9 b- K8 q+ b0 }1 N 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
: B- x) m3 Z# Z# j2 J, e7 P9 e ; P- M: E1 a3 E5 h
& p% Y p% V6 k# }6 s3 F1 ]8 i 6 _+ [$ S3 V% @6 n: `
5 g9 \# b; b$ B, S
0 P9 ?( [$ T' q5 |9 G+ E! A/ U
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:1 [" Z2 g8 J0 o3 W7 T# c
6 k$ K5 p" X6 h; K/ ] G" S# W; K9 P/ w- v/ M5 r2 G
<?phpclass upload {
( L9 Y& R3 o2 _) w' }* C3 G# w! v ( G9 L: q+ `) H1 A
) f, C9 _4 R! w! ?
function save() {
}" @: T2 P8 W- W8 p + K/ |! N& E0 d. B0 _1 ~
- O8 L5 v$ r, ~
include load('include.lang');
! [' D% B8 v, ~9 A( C$ w+ a
7 _+ W) M @' w# S4 i) A; Z$ U# l7 P( N) {. X" W! w0 _9 Q8 i
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
8 a1 Y5 j: _, m% D9 s x: H6 _7 Q n0 s% ?& J
# x, E9 Z2 E( n/ C/ ]7 j" ]% [
/ `* C! R4 }! ^8 b. u, o: e+ o
& T1 k& d4 b0 n N4 l( _2 B$ u1 L
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
5 [, F' W# J Q1 Y, M+ s: D
. E y& P" J2 o. |3 O
5 Y( h6 `! |' ] 3 Y+ T8 }) a6 w% }+ D
- H; a4 H i: |' L
L7 N, E# R h: d5 { if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);1 R: m4 e1 g. Y0 Y& c& ^' q) K
1 s1 I$ b3 [ p+ g4 P+ F& n$ }
. W9 D6 X2 i4 u6 u' k3 d # B* s2 m: G" Q$ [/ i+ L/ i
1 H' L3 \$ z" m6 F. O0 d2 t
9 y% L2 |1 J2 _7 s
$this->set_savepath($this->savepath);2 w" @7 X/ a' P! z
2 t) E6 R+ X% C5 _0 ^
# v3 {3 h5 g0 S $this->set_savename($this->savename);5 c# F8 E* ]) W. h& K
, X% M! V0 L. x D0 W4 g
s0 T# z. {" ?3 s
- `; [. n+ C- K$ p' ?+ ^9 q# b* C5 e
) \9 n% r5 y' ?. l0 u
, s6 C& k! z8 @, F; f' s1 o" |1 U+ O if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
% j6 v0 q) n7 L8 |: e9 w. p5 A8 G
& Y/ I8 |" |( K0 ]2 Y3 K: ?& S1 O4 u4 [: A; h/ i
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
F' n' N9 s- a8 O+ B: K7 |0 E
D% B; d7 o. X+ k+ ~2 k; K+ z
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);! z: J1 j+ x0 \1 m7 |
g& Z. v, {) y0 Z' S5 a5 b7 _3 t; m3 K; }. W# |5 w& e- h
" L8 J9 r0 ]4 \* {( ^# @1 f
$ m; w2 _" L" Q- C6 v# x1 R( s+ I. t: |% G' @) ~" Z% V. i
$this->image = $this->is_image();2 t& G3 A3 V4 U. F* D# E
1 s: U; b/ n# Q3 Q& f
! k: z$ C) d. p+ ?; n+ N; L0 b if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);! M/ Y$ ~4 j: X* o, Y
6 Z2 G2 p( H, E$ @. ^: F4 ^2 J
5 ]/ V& \& n& C return true;
7 W/ X" X: z2 }" m6 ?" {0 s
- p. R; F7 F$ A9 D' u0 d; [# x1 ~; P
}}
y6 h# }6 k. L. d $ g4 b( `! f8 `! S0 M# g9 ~7 W3 V F
2 q' z0 N4 e/ ~# n8 Q 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:/ Y7 P b1 F" v4 R( O( j
) e# A3 H% d% z/ T5 S! T
9 P4 k; U: ?( U
<?php
; }- ?$ p% ]) @0 C) v( A6 O ) D- d8 G3 i0 K' @' \% ~
2 h2 N( C& W0 L. k3 z2 @
function is_allow() {8 F4 k0 R$ T+ U/ w/ Q- H
* k, V( h% c% _1 }+ }4 ]7 v- Q* D$ `5 y! o2 ~1 @
if(!$this->fileformat) return false;
& G) ^7 z( I1 Y- ~1 q - E; P2 j; J, i9 y
" g2 B0 ?9 S4 |2 H& a) q1 J8 z( y
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;9 b E. v, t! \
8 m5 P B# Z, R' b9 x7 F
7 q, m7 n) C9 S! ] F 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;
4 ^1 i8 @0 ]7 [8 W/ d$ B
. f7 I4 J, N9 H' ~/ U4 d- ~$ y$ y) k, ^+ D' H1 I1 S2 t
return true;
# t/ W, m5 L# [/ I& Q
, i* ]2 ?$ w$ h6 c. a. l/ i- w5 B% O6 @) T! Y- a/ ^. H; W# a" P
}
1 u" r5 W; u# u& H& p' q g
. d3 s% r# k. Y$ P5 C
) ~7 l1 ^( d1 X9 d8 \ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
4 V- [4 x5 f3 U4 W# w % u; H, O: d" e( @5 H" o4 a4 x
2 V0 j( _5 f5 J( X
接着会进行真正的保存。通过$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文件。& }! M' N+ V d% |; d* X$ Q
3 Y! W6 d' v0 S" d
9 T- J0 N) P- R) E/ ~9 { 漏洞利用3 _/ i% Y0 O3 x3 j: e9 K' I( `
) m, M8 y# }1 B3 X' e W0 X
/ m1 q6 K# O( s 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。& [& M9 \ T- \
$ N7 c8 @; `7 L/ P0 X
W" N( v- y" q! U- r2 \/ k 3 @$ U4 e5 T) T; C: c
9 e/ v3 @8 I$ ?2 ^. z: Z! b) M; \' k8 L1 B% `1 W) P7 M
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
+ p; ^* w% ^- Y3 c* v * w3 @7 l5 s7 d
0 `* d. \! u/ t. H& `: K
不过实际利用上会有一定的限制。7 [* N/ e. \( p. K( P6 Q
$ h- c/ ~$ t6 ^( X: g" O* x1 n+ c
t# b3 ^& ` W# Z2 { 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。+ {# f) w a2 G! A4 K0 {
! E- l. G& I& K" D. i+ j
- Y1 F; X1 M& p9 U
. R' _4 Q5 Z; s7 {! V. s
( x- r- z/ i6 j4 Q' f) {, v7 J9 f0 ^% o
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:" @4 ?# u5 n% R7 e
* J$ ]& [! x7 b4 n3 d, u
" D7 ~% j5 O& _3 Z5 X4 }9 h; 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]);省略...4 k3 T* |' Q7 p
( E! Z6 E3 I% a+ E+ b
- q! u) Z! ?9 y- k
因此要利用成功就需要条件竞争了。. c; M1 H3 ^" u
+ d3 z1 U1 P/ b: U% d1 ]. ?
. f7 `3 R9 J: t7 F" d$ V. d 补丁分析
/ h; T* c8 z6 K3 e, Y1 S+ Y9 o* e0 v* u3 c% c! y& L+ `
6 a" I" Q9 K1 B$ }9 d4 S7 d
/ _2 B+ m3 X. a$ _+ z( _6 n1 t, p2 ~- b " P6 h' ?! B$ V
4 D$ y8 o3 V7 v1 M' o: D' N7 C 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:" t$ `9 _- S: C# f
8 y {$ X& N% M( u
7 J* ]! M/ S8 p8 J# E0 [) m function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
( _. m F" Y3 C# N2 {# I
) m$ |' v( H! L2 q9 ]. a. M+ c
- `/ E/ f' M3 y. [- M7 a
0 F& M8 d! ]7 D- z h m5 `8 Q & g+ a4 j/ C! Q* ~" v) c
( q$ n+ @* i% W1 X# |
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
2 u7 n8 y- L# }6 z" `, o
2 c8 Z$ ]: o" y' ~* D! W( S/ ?# D9 F6 C6 }' c- Y, m8 N9 K
在is_allow()中增加对$this->savename的二次检查。4 U2 z! \. R+ m O+ J$ L( U
! F, Z6 z; O2 p7 Y
0 \7 T+ F. N, K& k4 g" s 最后3 x4 n" k3 j# B7 P
: h" u& l; P% n6 J; J! |7 p5 S# ]0 _
. d- v1 @! J& A) O$ M8 T& H
嘛,祝各位大师傅中秋快乐!
6 r- _( C" V) @% l, O. _
( a* y$ f9 [( Z2 D4 G6 |2 g
}; u7 y4 ^* z' I , s0 o/ f% P. h; M2 P( M' n
# h$ U. q' I) o6 N- s& b6 y% s( D
|