6 g. c! x0 _) B2 G
% c4 v- r, ~4 W: `& k# e) m3 p% _# a/ ^" |
+ N t: r/ x! |& J! B9 z) [. I
前言( H4 {# b! B! [
5 c6 {* P. E, m+ `8 H5 l
1 b7 |& f6 H. a" ~" X- v; c( D
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
: a5 g b+ ]- I: i# l0 A1 L0 @" C0 Y
+ X( C* o2 z" a+ F, n1 k! o2 I
7 o7 p. @+ d4 M" Z7 F" a / f Q) `/ \( o, ^2 g- d
: |. B6 _7 m' E- j: F/ A0 g- \7 N) q8 C4 f
漏洞分析* U+ V7 g4 p! m
9 v7 c+ C. q# |6 K2 ~: I+ w4 Z
* v. E+ \7 p* `: d 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
. w9 Y' k: ? B: G" h* L. ~; t! H
: L w; |4 P$ t& ]
8 A( T6 D. L& Z" r8 o8 q/ o
8 \; ^8 h% V7 v3 n% [% V; Q 3 u, q7 ^7 O/ c/ T: l
6 o& H) Y5 T- [
对应着avatar.inc.php代码如下:
5 |2 p% Y! ]9 E$ H9 f
2 O- A0 V3 @! C+ \$ I% @
7 r7 h; G$ ^/ R! f# O( `( 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) {
; l. e) N; B1 j j( j* [' E
3 B7 }1 h5 {$ }8 k, T
I; S+ T! N3 @" |' J5 s case 'upload':+ e2 L& \/ l4 Q3 T: I
# ?' t0 h0 z: N% l: i3 l
1 V' B; J1 D0 t- ^1 K1 A1 n& V' y Y
if(!$_FILES['file']['size']) {
' o5 n: |8 ^! {! S0 M) d+ n
* n \" r, [( P" I! Z A
. l* j k- \' M) R6 u/ h! ~+ }8 k if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
$ z3 G- o( ?3 W% y# \5 R: Q 2 Z J* N3 b) W# Z
9 @0 l" l7 o$ I7 K) R8 Q6 k exit('{"error":1,"message":"Error FILE"}');6 v0 X; q1 Z% [
0 B( k7 R Y9 ]6 G; ^9 }7 c# g) {' \0 i( X N
}
8 G2 q* d1 O2 j5 t 4 e$ T R. ]8 D3 l( i- Y
3 A+ o( O2 ^. [$ N8 j- L require DT_ROOT.'/include/upload.class.php';
6 ] u9 G1 z' I" k7 f
3 e6 H% a4 s! f' `8 n' C- k7 e( c4 L8 }5 _
1 g8 I1 d3 |2 B k* y* i + F. J" S `4 Y: f
! _5 u: R4 \# @& t+ l2 `8 l. r $ext = file_ext($_FILES['file']['name']);
2 I" J" p: i5 u% d3 e0 } 1 m/ |" @( m7 i
. l+ E9 U" X( R6 T$ j
$name = 'avatar'.$_userid.'.'.$ext;
' z- O! S# z: n
+ p/ }3 s' x+ \/ N8 k* v
5 {6 B6 ^. Q: ` $file = DT_ROOT.'/file/temp/'.$name;% b" K- n- ~% Z& j
' X% K2 `) {* A+ n$ X! }: m& q
* S$ R3 w6 W6 j' G- D 0 b$ x1 P% ^( _. r2 H
% o" Z& o" Z. J9 l+ q
% u$ G3 y) K2 _$ [2 r8 t0 l7 {, v7 M
if(is_file($file)) file_del($file);
4 L2 \0 d# y1 Z* F: | - L' F/ i2 c7 M l3 l& L+ v q. Y
( T) U% t$ W+ }, Q/ g4 L
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
* e/ A$ u$ S) s6 p3 p) u3 R . j- j# Q3 O" J; i- z0 ~
3 \. O$ l' O3 l" Q, g5 n/ F
1 Y4 P& _* p& `
4 w( e* z# p( {' ? v9 P
5 k7 }: V. s- U9 l3 j $upload->adduserid = false;; J- F) K* y: @. D6 X2 Y) x; F$ b* _0 Z
7 k# t- E- A7 P" v7 M1 p
' _9 V( ^# l* G# C* I
6 i# |# R$ z" v$ ~, B
9 ]+ o' P7 h$ _. m9 `0 v
; ?. i( ?/ p" O K" A if($upload->save()) {
0 G" C/ {/ d% f) b) f
$ w/ s2 u4 Z* y4 c% A( [ ~2 o9 p d: n5 M" {
...: V$ B* w0 k, u c5 T: v
+ h0 W# j7 S1 a d' J, L: `- m
: N: r' ?8 X/ |, {9 K } else {
5 q ]1 D$ t) q" f0 O P
+ L/ {5 }+ T9 W. b: B7 Z% j- J5 K4 P% j5 a" P# E* ~
...% h# e' T4 J2 X% Z. Y# v: m
; j0 k+ Y5 B- U. S3 [/ E+ F
7 D, Q d5 Q2 J& b3 A/ Y7 E }
+ L0 c& j, x0 H/ l$ n! v# l+ U) I # O) ]" c+ Y- ~8 e& ]* E. I
' B! J5 {7 X! Y- r6 r3 F
break;! l, }. e5 V$ I
5 l+ u4 F" V" h0 v( F, c- A
3 N: m1 d1 m# h' h, V 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。, Y4 x7 ~3 m, j& j
3 H) _1 p% Y) R- y: N I
1 T) v* i% w+ B upload对象构造函数如下,include/upload.class.php:25:
@3 b& z) E* R% o' Y/ j4 Z6 b$ F
$ Q# p! z( B n9 R& z Q% z0 `! q4 h$ t0 g; q {
<?phpclass upload {
2 F) L) d. f i" ?, e2 b& K , L+ `5 p Q8 y9 O( V0 ]) q
' H0 o# D2 l6 e4 {6 x# l function __construct($_file, $savepath, $savename = '', $fileformat = '') {
! M' I$ D) X5 D4 u* M
, F2 g/ [6 { A/ s; D5 A
0 @% k, T7 B8 p+ M( M J global $DT, $_userid;* F8 R' O/ x' T3 h) l% R8 i* I3 a2 k
2 f% |8 l; a/ S) U$ n
0 Z- P. r- ~( |7 k6 u( r8 o6 p foreach($_file as $file) {9 ?9 H" T, a \6 S- k& }/ @
* ~6 O7 Q+ O" A1 B
1 c g t9 z( t7 j/ W $this->file = $file['tmp_name'];
6 x. x& T+ `5 \5 L0 T' f: g7 @9 A
9 i% t# u; e2 [6 v9 ~) }
; g, d {$ ^2 r: n& }$ p! m( R" [ $this->file_name = $file['name'];: V. o5 e, l2 }0 d8 D+ h2 v! E
; H: j# R3 J: r1 A- I. d
) _4 y" `, k+ s; N9 A, U $this->file_size = $file['size'];( C, ^# K$ u6 t+ Q
5 p" f0 v9 k& Z
/ v. F! y s3 {& m k6 V
$this->file_type = $file['type'];
# C C( L! _* X) M6 o + A( ]3 F4 g3 q0 d
( i3 w6 i2 d' { $this->file_error = $file['error'];
6 j' U5 E0 U' k# b- Q K
3 k% u6 l. S0 ]; a# H V% }2 X* }/ X I
+ a0 y) E+ m4 [: l4 R5 W
$ J+ W9 P" s: D3 N7 o8 M
4 ?! _' V l z0 n }
8 x8 f6 `6 a7 G . x' G k4 V6 T- Z& x
, H1 k( S3 M4 k, S" _& x2 @ $this->userid = $_userid;* x5 O" t, X! v6 z/ Y4 r2 B
9 m5 [1 v, z# C7 N- F q; w* c* S% g) Y' v. y
$this->ext = file_ext($this->file_name);
! f: _$ i8 I, I9 ]7 x
; G7 ]5 o, {# a& k: B
% W: Z# I O. Q: v; N) L6 @ $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];3 }1 ]- {6 {7 g: H
! J, G: B7 b/ v% a# W
0 B! c0 J: q8 { $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;+ g, ]# M4 T: j9 W8 |$ a/ I& Z" L
+ l9 I! P# X3 y2 V0 d3 k; q7 P& a5 l' i2 i& K. i6 m/ O+ F
$this->savepath = $savepath;* N% [; E6 E8 E8 |+ y' a7 z- o0 f$ }
* E2 R, e- @4 K) @
& x/ J. g! t" B/ d $this->savename = $savename;) M9 K6 h0 V, C
* Q `9 ?/ b$ G4 F" }8 B1 v
) z4 [) V3 O3 q6 O
}}7 K5 E; s# v0 e& g7 V
( g/ S6 u5 L( d4 s; b
9 X. p8 s7 P. y0 [' O' S0 O
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。# v* o0 w! G- d% g# P
7 w+ c& s& y6 v. W8 \
+ ^- G3 d8 @0 x1 o0 o
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
, V* {0 O# q8 V& L
. [, q* X, d" K- _" @8 V! H) p& ?% O
$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.php9 h! c8 Q5 x4 i) j
8 o0 A+ L9 Q0 r# G0 E1 n* v0 R
+ b6 G3 G$ o! L2 X ]/ J$ p# }3 T
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
* n7 z7 g$ }5 `( z
) F3 t9 [; t) @* v
1 h' v* B5 v8 E9 D " c& C5 B* p6 s8 K. b9 t& k- g
0 |, T+ _; v% Y
) g0 r9 e( H' S5 U
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:+ K0 |6 R# a1 G0 y% K* j# C0 S& P
& H h9 p( r! g4 K, N( U& H' E/ L/ M1 S/ B) G, y8 @
<?phpclass upload {
( V. v5 j8 E7 c# N+ K7 Y0 N * n3 }+ e \! u4 j- Q+ E
( ^+ {4 ^) h+ v3 q function save() {4 |$ z6 c3 j' d: ]1 j; }/ X
6 H( N7 K7 J8 n* A7 h6 C9 e8 Q4 ~
/ T: W. b/ s* {/ b
include load('include.lang');
& D0 Y& {4 T6 O0 k- ] 2 ^5 r! k! v# Q4 b, s0 |% A
# Y) y+ _0 I; j F* c3 k# s7 t if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
9 u K j- I+ u. ~ u
; n4 Z/ ]1 @2 A6 W: V# ?+ E* d, R& _7 Q. `
9 N$ {/ Y* u9 m! W: g
7 {: V P& K: H C: t
2 e# k( i) a' \. Y
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
* A# v% c V$ ~/ t& O1 N
* B" {. P! l2 [$ ^6 E
6 S, d$ f: l' B7 M6 O9 f; D% ^ , s: `% L( ~8 D6 H$ G0 D5 S" F
; f0 C7 B4 I' S
' z: g ~- l$ q6 b# t if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);3 a: _' _0 @& q. y7 |
* [! @9 }! H6 r9 ]0 u! N4 f
" \; R! F9 n* n: U E
6 r0 y3 H7 [$ Z7 T/ d$ j
$ h. z, C, o: S0 r) [( C% R2 k7 r0 N q. g$ S3 @
$this->set_savepath($this->savepath);/ V7 ?* D& V# W- R0 H$ y* ~% a
5 d8 G8 v1 g8 S6 U
0 i0 P6 c) m S$ K3 b2 x
$this->set_savename($this->savename);
5 N- E, H5 j: Y0 E# S
8 f0 s& z0 Z7 L: I* K( s4 X; x" I$ D S' m- V! I
$ ^4 J% v& v f( L% o; g( |9 }/ m
9 G) A* [4 T: O9 J4 e5 |
0 a9 G: ?8 y' r& @ J% \
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);# s! S# |, I8 h7 w" n3 Z; e
4 Y1 l5 @8 Q1 L3 J7 R# E
/ S& h9 @7 @. F# S if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);% A$ Y& t( R; U/ T% X
l1 m" \9 [7 k2 ]9 N7 c2 G c- d9 s% b( h+ [) U
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); V! ^" Y9 U$ Q% f% r$ B6 {" ^
- {# |1 h/ A- G% K& @
5 ?% f' O* O6 E4 \, N( B* _; k/ ~ 5 R- E% r N6 ^8 |- v
( j* q$ u4 A3 R! {7 k
) _5 B" o# Z4 I& L1 f! p
$this->image = $this->is_image(); @6 `* D" W+ C
" |% ?4 l P. b) W2 ?& ^7 K
) a+ w' M& @' @ A if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);" o& s: [2 n4 a5 ~
% o/ s/ ~6 {# e1 b4 B# a# G
2 K8 v5 D: K/ g% J1 b* y$ Q/ T5 i. c return true;" o( G, V+ t0 [6 V) L
n$ p7 T) J3 v J
& |3 m! i- v+ B; I4 L9 d }}
9 C7 B) ]: E. q: r, a' q 1 e( W. R9 |" L6 \
7 n; K3 @$ x& H2 e2 l 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:5 m+ Y( y" h/ P) y( K! p
1 v2 s% u( G& W! e" |
' ?7 Q) y& s @
<?php4 J3 H5 l8 \3 U& p* q, ?5 g
6 r( C8 o+ P E: @% y7 v* M3 I* Z# N: L3 S# M1 [1 w! S3 u
function is_allow() {
( g3 N `7 x6 w8 P( e3 K) H
+ I0 P& i; o: n5 P1 N+ a4 R2 s! n! c# x( @: O$ Z) e: ?! D
if(!$this->fileformat) return false;8 Q* \$ v7 T0 y5 w9 y3 H0 ^
: d4 T. I- B* T B. Y# ], Q4 o8 c0 v# f! e' C
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;& [7 F$ W/ S2 X
2 P( N! R- @4 p! |( ]' P5 C+ k
- Y7 v6 q% _1 G3 Z 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 P, h: ^% M3 R, W1 e J ( ?( b) ]$ V) ~, O: T
* W4 t$ z4 c1 c3 c return true;
: O6 i) P# u, Q% U% ~( d6 c
9 v8 u/ Y; z7 @
N) [4 F" V9 J }( ]9 n( ~8 Y. ?0 B$ Z
$ V' s7 Q, J6 @
; {- J5 W$ s* D
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
9 p# ?" F3 m# Q; D& g+ Z
7 K- i- u6 ^) O/ \+ q7 Q6 N) ^! O4 o6 I+ Q5 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文件。5 k T: t {8 w+ e
6 ]" o: B8 l, t6 ]5 I, S- ^( F
% H8 T [' z. H' }1 {% o4 P
漏洞利用
8 W, p% A( Q3 f% s/ b. `1 \) \, n( p. I/ f5 Y, z+ _8 Y! q
' q2 o7 s+ V8 f( u$ y. G
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
- K; x [) @9 g" `' s1 L4 o
+ X- ?2 `0 h/ B# v! O5 m
[8 Z. U" X6 G8 D+ P, R1 k ) u5 m/ ^" \ ~# Y& l* q
9 L$ J k0 Q8 |5 p
& B+ o% {" g% j 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
k$ `: C; u; ~" t , Q& l2 X1 w7 [
5 _! J( P+ ]9 u$ k$ t3 I- |
不过实际利用上会有一定的限制。. I# P2 Z; A+ m4 ~, ~1 [
" ^5 n. i% x7 ~# t7 Z c3 D# I9 c7 X
* j, ?, E `6 o S Q+ E" y 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
: B/ y' T6 ^, B) l' [7 L$ W) `8 u 4 d; E+ E4 k- b
1 C/ l' x, o2 k 9 ?+ J* }6 O% }% l. u
4 ~' H1 B; v. [; U
6 I0 L$ ` H2 w3 |$ ]
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
2 W/ p# s( O. ~) X : O. G( H. O r) J$ K8 r
& k' K n1 Z8 k6 \7 c; G% ?
省略...$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]);省略...- d7 l9 ]# P& N6 e3 e5 `
% I# L3 n& z8 s4 \7 t+ j
& U+ `; q( j$ O' s4 W# n6 M 因此要利用成功就需要条件竞争了。& a8 S! J7 K* w# |
( ~; v" C* C* I6 b# L
+ m" B: W9 m& f, O8 R( Y 补丁分析" W) P7 S- F0 t0 z0 K: e* d* ^
1 S u& m7 N* r1 z3 G" a9 j+ b3 @# b2 d
7 m1 a3 Z. m2 M C' Z& Z$ [
3 S& D2 x* i* Q" V
+ s- m; i2 P" l. k2 \1 Y 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
" \) L3 n% q6 m9 E) [7 n' v
9 t& q$ y& k1 X0 p& e# K
. _- _' I8 Q9 S9 {' [ function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}1 V5 q: {5 t0 i1 y+ E
+ ?4 |) Y$ `( _+ |" v6 G; j' q
; F1 s& _& K/ |- ~, ?: A4 i
0 e: l( D0 }3 R6 M3 S: n% I# ~3 { 2 f! s! K* Q: V7 y& b0 m
9 J8 c) R! k( \+ D( `" ]8 D 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
! K8 }! g: S& c1 }' w$ G# ?0 p& T / d7 A0 U7 v6 t7 P: {
: _% Z* j! i' O* H- d9 g 在is_allow()中增加对$this->savename的二次检查。8 I# T3 _" {& N- a+ E
- U" P* R9 a7 j4 ~5 n2 V4 i" h- O! N( ~7 _+ F. ~6 O/ j% T H! b
最后
( c" s' D+ u2 s1 z3 y0 s5 \. T) P1 g8 b( d9 [* p! t
1 s; g' y. F( b+ @4 ~. h; Q) g 嘛,祝各位大师傅中秋快乐!
2 V1 b' F$ h* P3 v; x* i
$ }7 R( H$ U9 `# z4 A
1 I* a/ X* H; x9 x. g% m / v6 f) ~9 U2 h9 [0 Z+ b
( x+ @3 S# R& F7 B4 f& A {; Y- d# e
|