: f% x. p! ]' y1 T! f1 T3 a& `4 b1 U" v
' X, g7 D6 |+ A: ~
2 I/ e6 V, Z5 n8 P+ ^2 w2 _
前言
# S- A) e+ Q. n, F, o! ?( q$ @9 i1 {0 E7 i, b; o# R U4 l
! `+ Y. k! F3 q# `) R
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。( H. q) V. Q. ~) \; I) @) J
! P; t/ I+ s* W
7 g3 C9 [* m/ k
1 u7 R! D) K: D& u1 ` / p1 i6 @" y m* W2 K
! P" a% H q: F$ x
漏洞分析4 c9 ~$ p- z; p2 F
1 S5 ^- `* J5 j7 `9 f5 M3 g9 B0 a9 `3 d
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:. c7 A2 T2 Y7 I( a6 O
( w2 d' |1 `! Q u' ?! v. Q
+ N& k+ d0 |9 T0 ~1 U7 \; p8 E& i: C
8 }4 N3 ]* q* y7 Q; M8 b" q6 Q
5 L6 e o/ l2 t( M5 q9 G t) z
/ V* X8 Q( \2 O( c+ b0 \% n8 j 对应着avatar.inc.php代码如下:
+ f1 x6 T& P I5 }1 h4 z , T/ ^4 w- R# w' ~4 c6 g
( k F! e) o! W: C8 R& ]( k5 w% R7 M) o
<?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) {
" f" x4 s w2 p8 D+ l' A- |- _
( _' _% u! ^2 i3 t9 H R( |+ f% p- b. P( X8 E8 D
case 'upload':7 J2 J2 _1 F* g, X
C& ?$ Z' d. O! c" v) D( _ u1 G
6 Y9 R6 ]6 M- k [. q: T( u* Q
if(!$_FILES['file']['size']) {$ {" b, w8 O/ U; G5 Z2 E
- V% l( V: i; Q4 b) V1 {0 b
0 p* x$ a$ m9 [, d3 _ if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
# c5 E, E r# c/ S3 s6 r. Q : [3 p6 i$ k' r3 ^& ]
7 i. S2 ?, K9 P! X
exit('{"error":1,"message":"Error FILE"}');% s7 B2 `6 u* C" e
9 K5 R# d% |. t" H; b* ~8 y3 }
7 A g- ]* _6 B) f2 b$ J
}
0 _$ H: | m* z8 l" |) S - e) E/ A( q+ [9 F6 s# W/ p
2 c o" K7 n5 O( J- w2 g
require DT_ROOT.'/include/upload.class.php';4 b2 J# R- x# j b r
@" x1 g6 Y! {, N3 q; ^6 T( J# H( i! Z; I0 W2 e
7 i% l' q4 x! C _+ }7 V
O; u! z. @* q' t2 q7 b( k
: b! U$ a' Z. o1 f6 B6 y $ext = file_ext($_FILES['file']['name']);: |( M# S# ?, h" u
+ W8 I1 [3 D9 A* k7 a$ |8 V
/ B# o+ T5 B/ Z; r7 V( h9 E $name = 'avatar'.$_userid.'.'.$ext;
D& E- E" y2 p2 a + c7 e! y* L/ j; w: u+ Z/ Q5 {
, x* @( S; f3 K; G. } U. j f- i
$file = DT_ROOT.'/file/temp/'.$name;
5 D+ H' T7 C( X
& C. f0 }# R3 y! \" z
) J* Q1 P# l4 e! E
% Q9 N0 l' M. y: d: G$ O- n # X0 H4 [7 _3 f5 q& G
5 q w# m. T7 P if(is_file($file)) file_del($file);! T+ z q! \2 R, Y
! A* r7 v: S7 x! W% _
$ d& q/ c& ?- e, _/ `; }' ~8 \; m $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
1 E; V$ ]- x0 K1 A+ B. Y" o
9 Q7 k/ J4 B. m+ V7 [# D' c) z& B5 Z
. ~% S/ d( i u7 ~* x# H 9 Y8 i+ g' t9 e5 p
/ r; U) r7 K9 l1 K( E/ p
4 p, L3 f) J1 G: T) b $upload->adduserid = false;; E3 M" B6 B7 [+ o+ E
9 p- L1 H, U0 W( m! U4 U3 ?3 H0 U( |
+ g. U' O# w5 p7 i! I 7 U7 H) J: }1 S$ c4 O: Z( T+ j
: @ ? l" \3 u. n. S# v" d if($upload->save()) {1 K |5 P' J( g3 X/ i
( [% `# j8 T0 n5 G; G
+ ~+ T" ~7 C: M2 a1 d8 A1 {% M
...+ s; C! G4 ^; ~- o
% ^- X# v/ G" {3 F D" `
% e+ O0 f5 L2 o$ \5 V* a6 L2 o
} else {" q; Y5 c6 k Z) ~
2 q7 G6 H5 t* f- D' {! V) n9 V h8 G: w. i
.... i0 V' f2 @* I
- n* I3 b5 @5 D& F' k
9 `7 ]% X- A# L" L8 _) e }
- A! D5 q7 t$ N0 [2 j. _ w
/ n" \. {, }8 m4 ` r
" I* o6 y/ u3 f- P3 | F- e- ~ break;
7 W, Z; F$ j* w1 G6 [6 W) r! I
& b0 Q$ U# y3 t5 ^' X, i
3 j m, l# Z% E1 D# ?0 \9 ~4 |) y% e 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。" e! ]$ n1 T! C4 I# m
* b- Z: o3 y! m0 V+ Q
; E/ v/ t5 {: ?3 R9 a6 {3 a
upload对象构造函数如下,include/upload.class.php:25:
& u& C+ J& k+ s. D( _6 ~; m
2 Y- d* x1 |& J2 i: g3 n
# N- b( |) i! {4 e6 }0 j <?phpclass upload {7 F" b+ S4 w; L) o
8 N- `1 `: x" G" s5 {
: B$ `5 E- j* u; ^& C- J6 g; e function __construct($_file, $savepath, $savename = '', $fileformat = '') {0 B9 r* u) E) _1 l. l
4 A! z1 V* W6 o8 H
- W. \, _! M V/ ?# }+ Q global $DT, $_userid;, ~/ o$ Z+ x8 A$ @/ f. J
( b: V( f- w9 P4 t0 `
# ^" t$ d6 M" E. d6 n0 P
foreach($_file as $file) {8 T4 b8 ?5 t M2 ?; t- k) t
. [$ o: A& j$ ]7 F
) Z o* ]/ H3 Z* y: N4 W
$this->file = $file['tmp_name'];
9 X6 y) J! b5 ?1 n( s. N/ ^
! v/ G F9 k0 z0 c) [0 ]" }; k. Y9 F+ z
$this->file_name = $file['name'];. p- S! `$ x5 k* N2 C
' z" A8 E0 E% f/ \/ E( t; `! ~8 H% T9 V+ v6 s; p+ m
$this->file_size = $file['size']; \- m" E+ x- v& v1 ~3 [
# ~; o* L, Y6 p# D6 B
5 p1 `" T# a% _5 K! t1 H5 _ $this->file_type = $file['type'];. s. L, b- p% L0 k
7 x$ b/ H8 M. u' ^8 w( i9 E
: @, P: y* H. H) ]( h) {0 u& I $this->file_error = $file['error'];- @. g, p1 R+ Z& ?
5 b- K1 |7 }$ ^0 s$ J9 v- @- t2 X0 u6 N' B
3 u1 @6 d& q* m
$ g: r8 U# O+ W! l8 u# ^. U' `0 g
1 T! N8 }% s5 h }4 A. a9 ~( y ~7 j j6 R! C
5 k3 U, U' D" t& j
2 b7 G! E) L8 S% }5 M: N B $this->userid = $_userid;2 G2 b5 s% Y' c; R
/ P% D2 N( L( ^. E& }) H' T
# h6 l6 |' g1 e $this->ext = file_ext($this->file_name);, c) [. T# l8 V" C8 c, T4 N
' E- l, a* X' Z( Q3 }- O5 n$ L$ v+ k1 j& O
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
0 @1 f( U: H5 `3 _: R6 D, m $ ]9 {( B! X0 T/ d8 c2 k7 H( T6 O8 O
) C3 U G+ [, A7 z; e7 r; y1 }" L1 _
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;5 k2 J* x2 t. n1 [8 ?/ W' m
! p# V$ n5 ~& j5 ?. K+ ]) K
) F+ D' E4 j! i# l v$ [
$this->savepath = $savepath;% T9 u6 V- W' v9 [: h/ ^/ u
* A# z1 _0 l5 b2 _( @) J/ ?+ l# z& R
$this->savename = $savename;
4 a1 s2 E+ X: A" K- d 0 a' N0 M& }1 V2 ^: o2 M
! c6 J) ?. I1 l% n* @. `
}}! |) w8 t8 i/ _4 f1 j
- ]8 c" A+ ?1 n q2 I
" N3 R$ z9 ?$ P+ P9 @6 T+ l9 T# \ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。+ [) Q; Z4 B* e/ N! f6 m* I9 T
7 e2 ~" Z0 C3 d8 C: B4 N/ W( d" {$ @ R" r6 Z4 f, e" c
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ( j8 z; X C( d$ z4 f) n7 i
% k. Z3 B" S! W5 Z# _
1 ]2 a, w3 O e' V. U* r. 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.php) }/ w4 V0 L9 ?2 X
' Z* ?' K! j8 ~+ g) u: m
" ~0 L9 O& J; D; Q6 [: K 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
) Z( e4 ^) I! {0 N" x/ Z {( T6 f
$ Q G: e8 Y% V e y% ^5 Q4 Y( H( E
3 ?7 H7 V1 z; x3 @
: m3 Z* o! G7 ^" W6 e* `" ^2 ~ $ Q. {0 H9 o; {
$ u% F' j7 d8 R# w 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:. v, ~( {4 b8 x# d" k/ @
+ x; ]' j ]4 X% ~8 R; G" I! F3 o0 q0 P ]2 w2 ^) c/ V, s& P) o
<?phpclass upload {0 [& U" v1 i, Y
* z8 `: c' x0 F5 H/ W: b- y: `( V
3 M" ?" Z# ?, y$ L: d7 ? function save() {
) k x6 l5 m& b" G
+ }! c& {- {! z. S; H) n* s/ p, b+ ?
3 H9 t6 y7 y2 `5 ?: \1 @- N9 h, e7 W3 g1 P include load('include.lang');/ r6 Z: d ?& E) p% b) u. i u7 B8 @
! v* K/ P+ G1 g& b
+ l- k. t4 ]- | g if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');! y' L r, |/ [' v0 u P8 [
0 M1 q. ^# [* B% u4 I- ?3 _0 p
8 z. w" W) u/ R# B
! j( @$ s% p( p
# L- h9 K: x/ ^) H4 o6 Q9 R3 s' e9 c if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
6 y' U8 i2 s! t5 q v. l, n0 C
! S2 }) _: k% ?" U
8 W- v# R$ u+ s6 W+ N; F g" q 1 a; }9 l% x o" c* m/ F3 ~- B! _
3 v7 D2 K" u9 w3 Q
+ ]7 T% ?) u2 s( c* P5 g( o; _
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);5 ~& n! P; ~% g5 e, B- ~/ p
8 r; r1 I. `2 D* k. t3 A+ Z+ `" N" b! p
7 X- e3 E0 _3 M$ K3 E' r ! _0 c% @' a( \+ n9 f* t5 N
$ t5 R8 [- v# |, T. c- N
, C. l& n) r7 P& Q) X5 ~& R; e# j/ [; B $this->set_savepath($this->savepath);$ R1 B0 j) P0 ]2 y" y
) T7 V: E1 M+ \4 I, \6 u8 Y6 l) ]: b: x9 @9 M, q( r4 ?
$this->set_savename($this->savename);
, T: N0 u2 h" X9 J9 d
% k# x& k1 u/ A& R0 |( x w* w! K( l2 L8 G, D9 G9 L7 l1 g
- D6 F& d7 |3 [: H _9 ]8 A( n4 c % _4 Q, v& _8 w
S! {0 \! F8 i- o' |+ I: {0 a4 f if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
# d$ z6 O1 e* } D5 Y7 F
& _4 R" j R3 G! U6 W" j& R \& p7 ]' |7 b. G/ r |" k+ k
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
+ D4 P& z' |& ~# S 0 H" I% Y4 e+ G8 K" i1 I
' E N# Q5 [- b0 k! P) x5 _- R3 | if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
: l; I! W, [( ?
' Y9 _: p+ [: [& k3 W2 v# v; [8 Q0 K; n2 G
S( G2 U# w" x! M" b+ g
$ b& T0 P5 U: D
2 @7 U" i! M* v( H7 j( Z
$this->image = $this->is_image();3 Q; ?1 a5 t4 Y0 V& s
7 U2 ?, W2 \5 r0 M4 N* n4 m
6 i" Y7 O# B3 |" q; F0 Y
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); Z$ C# _- v7 Y7 W, r
8 R, c+ P4 v: I6 Q0 }2 }
6 h* _- W! T! ]5 O* M" G& c% M return true;: O* m% ~% ~# E% A7 I; X# @$ ]
5 v$ l; Z9 N( \% W% j$ l
; Q/ k) c/ M3 w7 n+ \) t" Y }}
1 I6 \0 c- A8 {1 o! x/ w
* B- m+ K" Y; v0 T2 u- u* H6 y) r5 H$ ?+ A; z; `5 x& n: a: ]
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
2 n- I0 K4 n0 R% c6 U: b( j+ \' Y
& _: v$ N- k/ l( C, q G/ c
3 t$ j6 ]" a, [ <?php+ x! b! F3 B1 c
' l; b+ O, ]. ?; X5 y! n& E7 T/ J/ ]! F, J7 b
function is_allow() {2 B9 T8 W$ q# J: u. o
7 Q* R, P$ |" C/ u6 { C& j
4 [& h8 O: S- W9 C# x1 E
if(!$this->fileformat) return false;
3 X- ]9 s6 K$ L* C1 J
' t( ]! z' o2 g: @( h9 E1 a
+ X2 U6 M) L* }* v8 M+ A3 ` if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;1 f6 Y+ P p' l* x( Q) u% f4 ^
# o% s# d) y: Y. a+ {+ ^. H4 N- c6 y8 }3 \; d4 W' c
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;
$ I, y9 P* k, \
$ }5 J5 u# Q/ n; `2 L7 r% ^, D8 U# s5 D" B$ O2 `
return true;! ?) P. f! @3 [
) h8 N7 A) T5 ]( @( z0 G
9 c# V" a3 s8 H( G7 k+ r/ k }
$ V* _6 s- G- g E: b, ?2 }0 f
( G( @. T J- F l
) U: W- D, L6 L/ F8 `$ \( K+ W) I 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。3 C0 l% e4 p3 @# m
! R) H/ d+ y6 d# J5 y7 Y
4 `: F# b/ s$ n7 Q: z 接着会进行真正的保存。通过$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文件。" c8 B6 t n+ d
9 n* Y+ |; n% F: L* g8 W7 K j' N1 ]) H, _+ h
漏洞利用
. L! x( h6 z4 j5 r. R8 `+ D
. \6 {5 r0 s1 x, E) x: o/ |
) ]# \! ~; H& n* g9 m$ l 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
4 L% w y( h8 i3 C" k3 |
% }( {$ u: W$ i$ N2 \: t7 b5 t2 S( I2 \' q1 f9 N: w$ z- z3 S
4 e+ K+ @. o4 M* }( ?
& z+ P2 b1 r/ P$ l% a7 D& r4 J
7 K" \* T- z' G( q# C( G
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
7 z4 W5 A& d0 x& {% ~ / J, r2 b' s' U. v% S; k- h
6 m: f! t' G1 Q7 @9 H9 Z! |) D* q 不过实际利用上会有一定的限制。
; ~; _5 H6 g7 A
& ^4 F5 ^, O4 ~8 B2 ]# L% ^( w- e2 K8 Q+ n) C: ^
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。( @5 L0 `2 @* v
8 C4 l4 _) _+ m0 B7 E
- N+ I/ b' z' _( W& ^+ l * b8 U% \9 `4 X* j! @% X3 x. o
2 j. ^' r* j1 Z- \( f; j9 r+ n! k, h
; D: N1 K! [ w$ O0 n& { 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! O, D. G! i9 \$ q! H( Z8 I
* t# {* K: ?& X8 p
# p2 L! @7 g1 g5 m 省略...$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]);省略...
2 h$ t9 t* C. S9 e D6 J* l; `# ]& A7 V
* y; C0 q$ I& o- G9 y) N 因此要利用成功就需要条件竞争了。
- C0 \% `$ [( s ; u3 g; @; A6 g9 H
0 }- I! o1 r _
补丁分析( U* F2 ^# V3 d. [ Q. t
0 s% k: b/ r6 ]' G! U8 G& E+ ^5 ]
0 U4 B1 b7 D/ F- q( J( o : ~; }, r8 @6 J2 M! B/ G& x: G* y
! M* R, S# p8 L- p
( s. h# \+ b( \; N- @1 o" [( e) E3 R 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
- ]0 ]$ W; u3 z3 q0 r. }
0 z8 H# M3 ]- O$ u/ s
3 d2 s6 ~0 y" i" e, j9 y function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
- z9 @$ I9 [7 ? : P( A- L3 y: Q& E1 W2 e
t" w% k5 T0 U
j n6 t [+ j0 H8 r0 |5 ` 9 r$ `; e5 r+ q; W" w- ~+ e
' j) {; H3 E3 q' A8 x, P' d- t. a
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 l! k$ x7 k4 G& D0 g9 N
' b" B4 k, q% |! n1 C
! B" i" h0 K+ y, ^
在is_allow()中增加对$this->savename的二次检查。
T" \& |2 S6 Y& b" U0 B
% J3 a3 y' X8 \# U' G: r m
( Q0 G6 k# E3 L5 P 最后4 ]- E' K! X' a. U/ C
/ N: Y" V; m4 C$ k$ [1 m2 E' P
! q' X2 ^, l* l% ~1 W: N2 q$ V# J 嘛,祝各位大师傅中秋快乐!$ R# o1 ^# {3 ~$ Z' T; c
8 |! G* ~- p+ l {. e/ e0 N6 @9 |
9 l8 i: W$ n8 Q h3 l
" m3 F- s" s' k
( G( n+ J! I9 A
|