0 a ]/ i7 t! I3 w9 k- d4 z
4 I& V z0 K4 O9 A3 `+ N4 c" G( w& Z7 d6 d% g0 @2 M# V6 }
5 O, h6 l( f1 N3 }$ b4 o
前言( ?6 W1 a- G; @) f, {, H3 T
# J! e) O/ R0 \
6 E& s7 s8 j+ `, F" A9 t$ ^ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。7 h! h; I4 n% W. h* p8 v
/ J' b! x4 [$ c' F- W
7 l1 Y% L9 ? ]; Z' b. M! j: r! h
8 C3 {% r9 r1 L( h. \$ h/ j ' m& r0 D- G( Q
+ f& S, z, i0 }0 l) c
漏洞分析
$ o4 W8 T. q* G! E% f) K {* h* F# I: g4 G
' R6 M# N+ f, ~
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:$ q* T+ S5 P3 b3 Z5 c4 g2 `% Y
0 |' f, s. p5 C+ }6 b
2 @# F9 t5 s$ I / Q$ O5 f* C" Z- C! u3 \
/ @; z* L' y1 t, C v8 q
- R8 }! {9 j# m 对应着avatar.inc.php代码如下:) m% q7 i8 A- k6 X# X! @) J
. U6 Z8 {6 w% Q* h& K
0 z2 s1 h4 U: Z, u: ]+ p <?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) {
0 j$ n1 v5 L$ Q; P) j( @7 b; |7 H
8 j/ L. A* I. M5 l3 p+ B u! Y2 Q7 z, f: I
case 'upload':
4 m( X5 F* f. X7 {& [9 Y2 V 0 Z; ]9 M: |/ d+ t6 z7 b& o: ]
8 M. {0 V) w# W$ o1 ^( q if(!$_FILES['file']['size']) {
% l. a+ |9 j) f! w! J
! B% ~9 ? q! M/ ~! T h" e2 Z' `2 m) ~$ a
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
& `! ?! ?4 C9 m- p; d 1 a! k4 x( |1 r! Z* A
8 o! f- s/ h/ J6 ?( U" f7 X- ? H exit('{"error":1,"message":"Error FILE"}');
5 B. L4 N) I, y/ E% V0 Z" ]
7 T- A) A! L/ J
W! Q9 j) H* H7 B/ ]: X, r }. _2 k! r$ z% f$ r
& [) p( d1 {3 J$ H+ y/ x2 u
7 W' k. p& e: ~" G; `% u# e
require DT_ROOT.'/include/upload.class.php';: G" h$ W( W: J1 K! l
. v& V. l) Z' C0 u7 B
0 N' V6 A) A$ |
" k. n7 x8 d0 ~9 `" }4 t 2 ^5 h$ X6 d4 a+ u
9 I0 ~; a R4 S* g. \4 C $ext = file_ext($_FILES['file']['name']);* @4 j- V, V' T& r4 {; D3 p& d$ v4 f
" r8 F( z; P, M1 e' H" _, _, A B
. c9 r: J' O5 B7 H: O% _ $name = 'avatar'.$_userid.'.'.$ext;
" q& p; g. [5 e : Q5 C+ W9 t5 i' M2 Q) ~( F( X+ \
8 f5 k0 o) x9 { s $file = DT_ROOT.'/file/temp/'.$name;4 ^* W! D5 Y. R) P+ P
, B% a4 o: d0 s3 t" Z
9 J# @" ]1 A0 M( m! E 4 e; z, f' o& P; t; r0 t- @
% V; f: B$ E' G# O# Z
/ ~; ^3 H# p& J: V; i8 {- H if(is_file($file)) file_del($file);
' y& q1 C/ j+ S$ Z) J 0 u1 V: T4 t( V+ E* ?
! ~! ~. p& d+ p. ` |# T' I $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');! T! w! n2 s- V# j
* i0 i- \" O6 j, e. q/ I
: S7 ^4 U5 [& s/ q " @' Y! l# g# V7 Q1 l
; \) A) v% C" @" C
# _( K- _4 n* ]6 J $upload->adduserid = false;
! \* C; [& I0 D2 I0 m' [ ! J C( Q2 {9 r0 w4 O; L
( `: l# N2 O* ~7 p5 d: X" F& ]) [ % o, b$ s- b7 `8 }
5 l1 G7 ]2 b9 ~" g) D( g4 c) `" l4 o0 F& C/ T% m% ]
if($upload->save()) {
2 O. N6 @6 b$ v \; r, {8 [
l( C$ h* q, n0 T% x. h( E/ ?# {5 o1 U; i5 v r3 u/ Y& S8 R
...
! N: m, g& _9 s8 b # N5 P: E% c. s3 Q
/ ?5 D+ X$ }7 |0 m, f) x: | } else {
1 ]/ m9 [ Y0 M3 P, u ' K3 u/ j* _# T: r/ `; X
: _9 ~; }- b0 o& H' t( E" z% N
...
' N4 o# J/ u- n9 f0 C) E
/ w1 T9 \% M5 c- |9 D! }- d! m; [& [2 v1 d/ C' w$ }6 o
}
$ i7 j& ^. `: l" F ( e% Q+ `: ^ x# L
1 S2 m( J( Y! j( Z s; f* U, U9 w! ?; Z break;1 b8 M- X' ?) @7 P ^) G
2 V0 f: V% Y* b/ n, [: N2 n
2 B6 {! z, y. i2 b$ X 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。0 e2 v$ ~3 `5 ?, q. u' T: i, j# Y! X' ^
, z" b6 A% d7 r% p4 {
! _( i4 @+ Z0 F: v6 Y upload对象构造函数如下,include/upload.class.php:25:
" X3 T. C( R0 s( M5 |2 d
8 {2 I( ^, F5 A8 a. E6 ]6 g# d; ?0 D( }2 Z1 U
<?phpclass upload {4 g+ o" E$ A, E( c
+ r! c/ ^; U6 g
' k- c0 W" k* A( G+ V+ D8 L/ g
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
: E I: w% U h# Y: [ + @ p6 Q- w+ O7 \3 l; j( S
2 D& H# L7 U0 j( q1 s8 F global $DT, $_userid;
$ A( A: {* L, I% f% z- R; h% y/ ^ 7 A, r5 r- R6 w5 ^! @
3 J8 V. \5 {. ^4 v! K+ |
foreach($_file as $file) {8 Z7 z2 A5 n, w' \8 y
3 C, H3 z% r& N% J5 N2 j
2 q* n: }2 O ]$ `6 O1 j' W $this->file = $file['tmp_name'];
+ m+ v: c6 n, X3 O4 w0 Z' q! j ) t; s: P$ D G
4 B K; R6 [( y
$this->file_name = $file['name'];. v( H9 f' V+ M+ D, c& m# d! A5 D
* L- G, ]. D! k) r; f2 t$ Y& z) p6 E8 x o+ n' u! V; K9 z6 s1 }
$this->file_size = $file['size'];6 S; w. a$ o4 @4 C- y
- O; J D B+ L( [: B
: U( ?0 {" x- u! ^1 M, Z( @' ^ $this->file_type = $file['type'];$ n: }3 |; t! S
7 j: m" e% z. ^! @, |
' A4 Y& A, q7 s" N: J- ~ $this->file_error = $file['error'];
% V3 F A: R# g+ |9 c4 E ; U& z1 x! N: z X h- H- {' X
6 c7 ^4 I- E w ! B3 o; Z% J% P o2 p) `( H
0 D) A, I& C( i; |2 m
3 E% J, b( H" a0 J }5 n' _2 a7 S- s: C
6 @: j* f# d) u( \( l; C% u- A
! ~: w7 B+ B( k' T! U $this->userid = $_userid;
( s9 @, L6 U7 ^0 Y 4 o- i' x4 g. }2 G
! h8 \! S3 H* A f5 i
$this->ext = file_ext($this->file_name);
5 h) x4 g6 O- M$ \ # V( H+ S( ]9 R
/ U5 ]4 ?1 E5 z
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
& _$ b# a, b! I6 x1 y
. h) r+ d$ Y5 d& Q3 p0 g# Q
' U8 H- s/ Q k3 B, i) s7 l9 O $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
- a3 ^! F% _1 x' \) g2 k9 v
- g) H3 x- }, H+ c% J8 k$ ?1 f* ^- A0 U% T3 }, S) Y
$this->savepath = $savepath;* I5 p+ P" `3 P' O- P* J1 d
( F: i3 V, G9 ]0 q- ~ _# q8 i% P7 Z" Z! R
$this->savename = $savename;
3 R" D% a1 o Y0 K/ S 9 Y: |1 L1 U, \( J! Q' v
2 f2 o! W8 Q% }' X5 D# k' K }}
' H2 x! }" Y" H4 [3 S; o# P! x ) p' e6 t+ l( a6 u
7 V* P; }+ D) k 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
) L( @! k# v8 h : r$ \; Q( r/ |" M) k2 j
/ g5 B+ V+ i) K2 X; u e; X8 R8 o/ n
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 0 ?, ]+ k( N( f
) M1 z0 p9 A! h9 I! C6 b' u3 P6 w8 ~! \% w. E
$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
. g$ |2 _% H# X 2 z: I) {. ?2 n" \
% @9 k/ ^: h. [( s) b# }, [ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:, ]3 X# U& }! H3 s
( M9 H/ Y, Y6 ?/ Y7 j/ V. G5 |# y
" f& B0 e0 D" @6 D# r
0 {" {; M, W, e
6 k3 h; T1 A1 ?$ V7 ~; `
0 ^# b) c" l2 J( W1 |+ G 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:, w, i$ y# f* T! r
" t. u/ c/ K. e1 p6 J7 \
1 I- p8 R, T4 }- Q <?phpclass upload {3 v9 n# Q) q( d% N& M7 O
. z; B5 G5 ~0 g( M& _# a! g* `+ L) I: X; q( K F; f7 n( y, B
function save() {% E' N6 u- r8 q) E# I$ f. g! |
! p4 ?+ l) x: v4 c/ k6 R
0 |/ S9 c3 W M: `8 ~0 N' l8 } include load('include.lang');9 b3 D" l3 p, f. B# V, }
5 W o. c5 F/ n! _) ~# _0 r
, W, w# V0 m q6 j) k
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');2 f, T* @8 x- r4 b
8 b* G, ?9 _' h! _
4 f8 b+ K1 ?- q! u- \+ e; a
2 M5 I4 t7 O& J2 ?: }' D
7 m" {( Q) {; d7 M
4 l) i" N7 L& R if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
) G# v. T5 Q" i- m7 u) I 4 V1 h+ ?# x( A' Z
, T# W4 k5 b& P# j2 x- b6 Y
8 T" Q5 @* N* ~& { + w; w) N; K, D# G5 |1 D
+ a5 g, N& v) o$ i- ~0 @; d9 Y
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);* ?5 j% r' |' P
% G# p( l+ `8 v" X G
" k4 E) L+ \# A
) C4 j8 N, y8 o: N; F
& ]) E- |( X" F. N
* |: y% M/ z' q- O! p1 v0 d2 Z $this->set_savepath($this->savepath);
! e% o' c0 J( o8 G
6 M& D9 ?+ S* w0 m/ d- i, @ `3 R% x% v. `3 _* m S5 T
$this->set_savename($this->savename);4 _0 U& g/ ?" o6 C6 v
' Z, {$ U, l/ d& H; O; U6 }! s3 {" O
3 {* y3 Y0 j% V' Z1 w! ]
( g/ k. z1 x1 d0 U7 ?. v# h& \9 `
# X. R. V8 E/ |! U
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);9 l& g0 U; V2 g
! q ~7 X2 M, h$ B
9 H. M$ e9 A. B, Q3 x- a3 ^1 c if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
; V5 \. T E3 K& y - W1 f; r+ l3 p0 O# C
; R& O( Z& U5 i- u
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);/ k" ~5 @& O% \/ Z
* w% P8 d; L: a+ E) o* n7 ?
" f0 D; X$ L$ j3 T* Q: f E7 i
* q( b- c. @, x k$ A6 A
4 F0 t" X: L6 r4 z [4 Z- ~, @- p+ m8 N) g: M
$this->image = $this->is_image();, x, j3 T( `) ~) k/ j7 ~' o
7 P8 Q( d: R, p1 {9 q M
% x- z/ U) s% k! X) o) z if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);/ b9 u4 w3 \7 b( `8 ^% }9 v {
: ]( P+ Z$ M0 Y0 t% T' B& M
& M* b2 V4 t6 F return true;- o5 V& Y0 u! i" g5 C
& c$ T: t6 x- N. J3 z" w8 h& k+ p6 u( U9 K7 ?1 K! H% K9 P- T
}}1 k1 p3 t" I8 K5 T
" ]( v9 n+ K2 C- b' h2 Z* u
# A4 _: ~0 R" a" L6 J* e5 c 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:/ u0 P1 n5 P/ J$ F# ~3 T5 ^
, F; z; }7 n% V$ N
2 e+ Z3 M0 Y6 D% X$ L <?php
- x% v. C5 g- b, Q- `( g( e' t& c
" {& O/ x# g- w0 ^$ B/ x" n# W0 K3 I3 |* ~, x1 L* P/ ^% C1 R; G1 J I
function is_allow() {
: ~* o$ z4 f5 C0 `
& k: ]/ \* @# w5 }& f$ K
( m3 h* R, z$ d' W8 D if(!$this->fileformat) return false;9 V( L% b4 M" G. r4 j s
2 h1 @) }% J( a. O
/ R% ~5 L! S2 P( [, i3 p if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;" l& o6 i( I- v, @1 H: i6 p
* b2 M! @5 L3 u" c
4 _( h% X. |4 `! n2 V3 x$ D* 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;
( y) T" u& d0 h( ?1 h( z9 i
- O1 r# Q/ j( F8 W" w0 F4 L; `) a4 b, C; d2 ~* y
return true;
* M& t: l t, M1 z5 K) r1 e 4 n3 k) I: k) \/ p
$ ~" v0 z- g, k2 M& ~* L5 m }) B9 g) q+ F- Z# }
: c+ L% p3 M- G3 Z* l! i$ C' _ R" h; q
* L! L" P# \/ I) d9 |( U 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
! s5 I0 `' D% q; L. b0 F 1 O4 n3 M# J) d7 z! Z" [) q
6 a2 e, x! x% n5 I; ^
接着会进行真正的保存。通过$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文件。
4 g( M5 Y" ^8 X- F
4 m7 L9 f$ P1 t% T5 Q( E$ {! X4 x R- m2 u! B1 E$ H% }) y; E
漏洞利用. |/ y6 [4 w5 w: q
4 _0 V# s. u. L- S3 F% [% _4 C1 m; e* _# C I6 q. u) l, X. W
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
2 H; `( s/ A9 a+ [1 ]4 A9 v 6 \ L+ V5 A0 G; j9 V# S3 r
/ L% K$ W; O" ~( F4 M ; T, a4 r; l6 R( v: q
$ ?# B+ x1 a. I
) e [- ^7 J" X& x 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
" }! r' {' |1 q: v- D- a1 E 4 O- p' o1 ]2 |
+ D7 e1 p" G: r4 R' O( Q! f
不过实际利用上会有一定的限制。- D5 |- ~. N1 m
% `8 w# O% N& p0 e
# z+ i& C; j4 M+ ^4 L0 o 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
/ N( H4 S" Z3 u
9 s4 @ w/ a4 n* x o# ]
7 m8 b2 R! _( p- d/ u, l6 z0 L! s
1 L0 T. ^. c) [ . e. G9 f4 v2 c ^3 N
5 o0 [6 p) Z% k& Y W2 [ 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
/ O3 I1 W7 n* n" D8 s f3 K; [9 ~5 [- B , A, X. `' N$ y k% @4 x P
1 D9 \9 x0 P, F- }0 Z; }
省略...$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]);省略.../ Q* N, _; e' H, f! d
, k3 b k( b5 _" Z/ l. d1 U
0 j( R- F, C' X/ l! u: b 因此要利用成功就需要条件竞争了。- f3 A3 J7 n0 J7 M
/ F: V2 x" {- W
9 S! _9 Q1 ^* v7 X' W R: y 补丁分析
7 @0 E! W2 W9 L4 A( i% M1 ^' B: p
- b1 ~$ _1 D8 j7 {9 \. W/ S
9 t/ B% ?/ b3 x 8 F, R1 Y# P& T# W: F2 U$ k
: ], E: o8 t8 k" g
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:4 _* L! T- O o+ {% s8 G( i
( f' t* o6 \7 x( R9 b9 e! g
" T2 k/ `) s' k/ Y$ ?, Y) o& W
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}! p# k' H4 Y8 a+ Y
) u, D6 L0 a9 b- V3 q6 l- i* i1 U8 x, z( m" X7 V5 ^9 C( x
3 B3 W8 W1 p$ ~. T/ f0 j L% I " `3 S2 s5 e% d% v- f
! u p Z+ M5 [3 d9 W8 n
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。' b: z; t1 G5 Q% C- O
/ R4 D; C9 H+ n8 t. A# \
8 z" o/ u V+ E1 ]0 Z 在is_allow()中增加对$this->savename的二次检查。
5 X1 P! P3 S6 D% f) z7 [
4 d( E- P6 \' H3 ]2 c
/ Q, P/ W1 f ?" p 最后- X s) d2 F' S, B4 N6 M
0 _: d% j2 o( p% i8 l9 [( d; I# q
5 N4 Z, C. p! f 嘛,祝各位大师傅中秋快乐!/ }% v% L; S! Y( W6 }4 R: w4 Z
) d4 d( u# C4 O; J0 t5 a
% t0 [3 ~, _& J; N1 z+ W
# E. |& b1 w) d ^6 B+ F# Q
1 S# u. o$ h, Z+ t2 ?; v+ O% ]( X
|