& x' w, m* ]& ?8 r% A5 q3 j
5 J {& z& ?" W3 V
' }3 |. _) N; ]% @1 a. X
: q, I9 M' L6 T1 F9 |, h) r
前言
7 W( @8 D0 c" ?: w: c- K/ X$ X6 G2 k3 X
m( f! F8 C$ W' Q& @8 Y9 u: [7 g4 m
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。$ ^& }, b/ D7 y! O6 ]2 T! v
. S) i. k" @' L5 \+ G4 J6 C# m6 P6 C* E# [+ T( a. Z2 k
* ~; h$ @- J/ i2 C. I7 z5 H
3 a- o# W+ y3 O1 J
, ~% G# e: @% a3 A 漏洞分析
+ D: K( ]6 G2 g) h6 a" f+ `3 {) x \# F+ ~9 K$ x6 H0 L
' }! [# e" {* \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:" V# D- k' ~* |9 {$ n* ?( ~
" H' r% j1 Q4 T* C9 N/ f
" g5 {, ^: s% M- [" }4 [: c$ R$ A
' X9 z- h7 G1 L6 i6 {. N
% z6 C6 z. h* G& n; O# m3 I* y
8 |# G6 k3 C7 n% ?+ x( |0 u 对应着avatar.inc.php代码如下:
& ~6 g. y I" B) ?+ p/ b8 ` 4 }0 V9 k1 N/ B) h5 z- m
9 ], c* ~# ?- A7 _
<?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) {, Y( r/ o+ M- i" h' y" ~3 A" f6 R
- N; D" H: x/ r! O
+ K; p, _$ k+ Q9 d: m y
case 'upload':
! S5 F1 |8 d% F' [; J2 v2 y
: U" G( ]1 U8 `- a( Z) M! t [9 @( C+ V* e8 s! z
if(!$_FILES['file']['size']) {8 m8 f$ n0 f& D2 n$ W$ Y
. a/ y% a% ^+ r& o6 h
8 w/ \# l# }% r7 q% L8 ], n! ], \ if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
+ @# X: W0 l% \; K( \' `7 E) H( W
1 ^: P4 {: I! q4 Q4 [
+ ^/ r/ t# N0 d/ ]. p- E% K5 H& G exit('{"error":1,"message":"Error FILE"}');
. q3 F/ b, h! Q' a7 Z4 A
4 M* S9 P( C! [; d. Z, d& b/ \* D+ Z! A; O1 @
}- f! C) R7 Z1 Y& |* f
0 Q9 D$ M' X; \' j& \# O' O) N) J: M: L* i n% Z4 j+ b
require DT_ROOT.'/include/upload.class.php';" m5 Q* Y6 A) H+ x
+ C0 g: [: e9 t0 \& U
! s$ {% f4 j% d0 F2 B s+ m0 f
: W& D1 ` c; Q6 j
- x E, E- q+ l, {' a3 S1 X& k( f* v8 C8 L* ]- ~7 b
$ext = file_ext($_FILES['file']['name']);7 ]8 o2 `, t! Q) h9 t+ L7 t6 O$ I
( |: q4 \. a. s! L' Z0 Q% ~( @( j7 O3 r9 V3 l$ K. ^
$name = 'avatar'.$_userid.'.'.$ext;
$ E* {7 a u$ Z0 o% p' S / r7 P# Z7 M8 L
" C! s; m7 S9 p' L
$file = DT_ROOT.'/file/temp/'.$name;8 @) a0 ?8 c6 l4 U: A6 Q
9 U0 ^5 J9 \# C( U; }
2 D& z' Z. X# Q& B1 x& E6 h
, b+ ^4 N4 H: s0 n1 W8 ]2 C4 h
( q! S9 Q" j. |1 N o
) O! y2 ^& v% n7 |7 b if(is_file($file)) file_del($file);
* l6 l2 z4 u) R; p6 I j % E5 }* e2 a! E* q% y
- R# T' i0 _3 n2 M" D/ }4 h R $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');0 z$ c! ]/ B( {0 c5 }) P
; h" P0 W/ `! L" i' y
# D+ ^* l1 [3 v9 m/ e4 i$ m9 p & Q O; z8 U7 d8 A/ h/ Q- ]+ R
8 K" ]3 R1 _6 a2 j! i& G" p
! `/ g* G1 l+ u/ d: o $upload->adduserid = false;* ^ l* A' |! f8 i) v7 t4 t
+ b( y# ` {1 ~& Z! T% N0 T- {" F" m6 U% \! g! `# q8 q% m
8 V( N5 ?! @( W6 P
5 s8 B+ O. z$ V3 ^; T, y
. o6 ?6 Q' y* n
if($upload->save()) {
! x- H+ J% _6 U! T5 c8 H4 u( `, M + n1 W. |7 y- e* Q
A( Q, @9 n% A7 `( K& I; f
...! i. r$ s+ B W3 c3 g7 N
2 w7 l* V; U0 Q6 r/ C9 ]0 h% E
! x/ \- b, _1 ~3 r } else {
. |1 {5 l. T4 b( j- G$ c
( t# m& M V5 t8 j/ K7 M/ }5 V" c8 l4 m( o* Z/ ^
...
' h$ q6 T+ j$ k6 B+ R( Y: ^
b% K+ h( V; g& J$ P7 d8 |
$ o8 }! Y- k5 | }
' t; [- `% R% z ' v f. C- o7 f8 v! s
4 L! l m8 L" J5 l: o% h
break;
* r/ Y( y# {/ ]: f3 n+ c $ t4 m6 S* ]; Y9 U1 q
; U; {) y" U8 m7 b# a$ E+ E* ^
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。" [( W r( L) V
9 U# T' Y: @4 T
2 M5 D! c/ N) k' ^% \, d
upload对象构造函数如下,include/upload.class.php:25:
9 j" s) S' S& g* O6 T# j# D( R
0 x5 m7 H! Z$ Y! I. ?( u
5 w0 K4 z- N6 ^1 X6 y1 @ <?phpclass upload { ?2 L+ n! T9 }0 h- f& x5 ~ Y+ I
$ _/ C p& F5 q; I7 h- j6 C
! L0 j0 I' V& j* f5 @7 |
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
2 |9 q: R( I( M1 A
6 `* a ^0 E1 X7 k, P
) z6 ^. C' `: W, c global $DT, $_userid;
" l3 f- L7 x9 X+ N 0 C- N3 ^5 \& k" y! L( a3 z
" u( b0 I$ A8 s7 M& _# b foreach($_file as $file) {
4 |: S$ M& r; J+ I4 H( W8 I1 m
- Y6 t# @2 v0 l8 y. Y. Q4 L
( R0 ?! z- G! F $this->file = $file['tmp_name'];7 {; ^/ ]7 J- b' M" g! Z# e" @
0 I- J* R0 Q/ {0 K5 p
2 G' a' A3 e1 }
$this->file_name = $file['name'];3 d$ k7 Z% A0 u( ~
1 m. r X- u# a/ k1 \# W7 v2 L# ?9 Q
& [6 L: a/ p% G1 j
$this->file_size = $file['size'];
# T" s9 W. a" J; j
3 F% w! k" N+ K- u( V- a. a4 v: a# @3 P0 ]/ ~
$this->file_type = $file['type'];* o) f9 e8 @! Q: Q/ m! A- H1 U0 {
! [8 r; h( d' I; K+ |! i9 E9 v' _, E
$this->file_error = $file['error'];; v$ I4 U7 L6 J
7 g( }! `4 b( N0 n, N* C: w
6 n; w" `- V5 k( N! s( D3 E+ l
( ~0 I9 U/ _( v r5 ? : k( A. s5 m$ N8 y" s
- j( `+ o( p2 H( G \; G
}
# Z2 S' r5 p9 e3 J
) b) A2 F3 a! v9 e( n, u+ O
1 F# J- S3 H, \ $this->userid = $_userid;) c5 o6 D2 S% R' z! A, U
+ Z+ c" S$ d# ~$ @+ {( ]
k9 J& j; P% K+ k+ ^- o
$this->ext = file_ext($this->file_name);
8 J; |, I# u- s/ l& v5 S; a * F4 b2 i0 S0 S
& D. a" V- j6 _7 N0 U9 h3 _ $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];' ^% g2 U3 z- Q8 R+ k
4 a; i/ A5 G% E0 o9 B% S& M. n+ z' d# H( y( f6 R
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
( F2 b3 g9 X1 ~0 p6 s7 V8 R
3 z) A! A, S3 \" `9 z6 e4 H- K% V6 L2 _4 Q( x& [
$this->savepath = $savepath; n* U1 H( J2 R) L4 O* u }* [
& C: Y1 [3 z% [+ U. F3 t: w7 ]% | w0 h0 u
$this->savename = $savename;
4 ]; s# D! {+ b0 T1 v ! y3 z& x( y0 ]4 h: r, B: U
2 ?, I+ ] w+ q! v
}}) }: m/ h3 M# @# H& I7 F a$ q
* {' L3 I$ v! G% h
! J( |0 T( R! O" a: U j* j
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
. e- S8 u" P) m7 b: @: n1 \2 W! }+ i
5 P% w& A9 s7 v3 J1 `6 s: _, b0 w+ l. X3 A7 S2 B
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
& E {+ V: f* r3 @# k
3 g1 I! `6 R+ I- C
) i+ f7 I+ u5 { $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
2 j2 Y; P1 u2 p _ 3 [. e+ G0 q" E
! D; v: O! p/ s. i* P
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:, c6 k; n. i# A* [0 k
* |2 C% R$ R9 m# p x- k: {8 U6 w5 {& a$ v" U, n5 Z+ _2 J
; h+ y6 D( i- \; N3 c
+ p. ~! e: x' z
0 l, _6 ^: x# M 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
; G+ Q# B. N: B6 H: I# x! K
$ ^7 d: |; J7 H9 T6 G7 I" r0 \3 k
<?phpclass upload {, G' ?2 E% [3 n
: F8 w- F, G) S4 w7 W
+ R. c8 G+ ?; Y function save() {
8 t+ a2 N4 a' N/ \
4 H5 w. f, r, T8 q. A7 z; P w' C5 L4 h7 H3 o- `6 \
include load('include.lang');
7 x% q) x- c1 Z5 ]! `$ M& } ; V- D& N. J( d- N
8 a% ^) m; O) X! r4 H
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
5 X2 ], f- w+ w9 O/ X. h5 M 4 A6 c" d# V+ i
& e$ k9 i3 R+ t2 s) W
' f' e% ?/ s. X& [! B
2 O( m( [% L2 V3 Q0 T! ^# V; E; p3 e0 |, z: i/ q- `
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
! g0 X _% S# _+ k
4 L+ H/ L+ V' `# G' W5 C* S$ z# s9 |/ M. P$ C& H( c8 D2 z/ v: i. \% ?
. P% s' p3 @" ]8 f: B: ]- S
" J; K9 ?8 F0 s9 T$ o, ?
- n+ B* ?* Y' J" \ if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);8 v6 L2 W) C F& {: q- n1 A" i
+ m# F7 v& x- `" n+ `
) D! L. D( J! d" B9 ^) q
( k8 W) s \: t" R+ q; I: A
5 l+ y; |) e. N. W+ O# M
6 r+ Z5 P! |- e3 c* i! P" K4 H
$this->set_savepath($this->savepath);3 P+ G! j4 o* C, l( d
; J& \6 ~3 p- ?1 [& j5 @6 n8 u9 E' ]' a: O
$this->set_savename($this->savename);' K# C" w1 g" `+ U1 n) Q x
3 q( c! S0 P' t$ C2 M, V
6 Q0 ~1 E0 b! Y3 ~. X, [ # {. M3 l5 ]& Q* |! a; I: P& Q
0 E2 ?- A5 Q; b3 Z
' {2 ~( P3 m$ ~5 w4 f! V- Z if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);7 S F3 k1 D, G( w. Y
4 c8 Q+ @* [$ v4 u- D
. A+ q: X4 a2 ~/ o9 r if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);4 |3 `$ b7 j k$ i! \
$ C- G8 F2 B0 X8 n) K* {& Y1 M
! E- e! W7 F. [ f; U if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);8 q6 V& R4 @4 w5 A. |/ z) r. w
3 P7 Z# U4 q% B1 [9 t
e' k# i o- n0 I, \ 6 _$ i3 c* w+ T1 l9 K' C
4 I2 E- X& y7 H/ n% I" ~* j
b: I0 @. ? [# b# C $this->image = $this->is_image();4 t/ o% ?+ W# x6 H/ [: ?& {
, ]! k+ M. Q' @$ _( f7 V9 J2 D) H; V) s- M( B! S
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);8 P) a+ e" d* R1 E, k. ^
! J9 k7 K! J" @7 h
* f8 q& R$ a8 l) F
return true;: l5 G( |! P+ t
( A, z4 R0 x/ f4 S" s# F/ y g4 v
$ g$ x. g8 w. v4 k8 F }}/ r5 e# A3 O- ]7 p' U
+ \# [8 |4 V& ~# a& s$ D* ^
/ M8 J& M: k" B( U/ q 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:( H+ Y Y8 M$ _7 `
9 N; |& g/ `2 H2 `' R' ~5 y6 j; z& O1 j
<?php
& j1 e6 j0 f1 M; x + c \+ N; v4 w3 O4 m
# e7 E4 A4 w1 v+ ~: g8 B3 Q
function is_allow() {, t# {+ }) S: V, J4 p9 ]
+ \' F; Q6 @. Y
1 R; b& {- V, ?0 U& W3 O6 o, P if(!$this->fileformat) return false;
2 d4 p* X3 `8 B @ ) U0 v/ E; B* T$ X, t" c+ \
t0 u0 ~- s2 W7 A4 }, o
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;* P3 q k/ L' y
; S( m+ ~1 Q/ X- U+ V3 O! G
- t/ v: K# ~* l" H& g9 Z7 A
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;
9 Y: U' n j% C g( g3 S
4 A3 k% h& o. D" J; }. E' F1 o+ M3 a0 [& v
return true;" f2 v/ G/ Q, l5 I5 M5 I
! T% v/ ~% |( Y& ^' w% e: }) y5 b
2 @/ C7 M( K; M+ A( N; z }9 F1 T' b6 e" v% h8 |
* c+ \' R' H. F" g) p2 C) ]- a3 C
# ?# U, h; f& V0 V 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。; i- [& W: g, l' u% D
' ?1 Z& A: M3 I+ v" W5 N% Z9 s6 @" b3 Q8 C
接着会进行真正的保存。通过$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文件。
: r" F! P1 w2 z5 V, D$ g
# i- u2 }- Z( h2 F3 T6 V h: W2 D0 j8 ^' k' c7 w
漏洞利用
' O8 H2 F v* M% I! D
7 g, _$ p8 ~7 _. u7 L
) r- X- }. W: {2 C 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。) ^* j9 H6 Z' m0 ]( T
& X& ~. i& q% X& E) y' X% R: g
" G2 t2 h4 Q, a- W% A' ~
* [+ m( o; S+ {: t
, J( \$ W/ D. T+ h% {# X3 s8 [
7 e! e. _6 O& S7 K3 d" E& @" n 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid6 R7 m6 f* z) _# b/ }2 c; x
4 G7 L+ H! @6 J& p6 x* R
2 E: T$ G" S$ ` 不过实际利用上会有一定的限制。
+ k0 k/ j2 t' c* R- U) s( O 7 d6 G* ~9 P$ a8 K; S4 E
5 Q9 J$ r" W0 C* Z 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 I) e6 l" Y/ v( G* x: A
: p) u2 J' {5 ^3 X% H' p. f. d! n7 G
% d2 [/ C; _& g- X* f9 I5 x, e
* b% K/ q5 T: X3 b* G. ~
4 p7 F# A/ W9 e6 j- j7 k5 x 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
1 c' L: p+ M0 i. s: o+ u/ F6 D
9 ^ |6 y# z) T1 w1 i9 c/ g: Q7 u. k" m( c. w8 s( u
省略...$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]);省略...: i* l" T4 x1 D
3 a! z% A, r3 a; `3 ]
$ S" ]- q3 Z" C3 x0 X r
因此要利用成功就需要条件竞争了。' Y& p0 G6 |& Z
1 t3 s1 t5 [ S
( I% _5 e- s# O! I 补丁分析
# L& O$ z) |* [8 J8 Q! o( {
$ J k" ?4 c6 A. H3 O \
# v' y B! w7 d m' A+ z
' F3 N6 I+ s8 _& @, C) p 7 q) _# E; K( U; Y
% a0 N) t L, U* f. V# b/ R
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:& _/ A6 u y4 E! V4 p* o$ _
9 i: `6 e$ L4 W8 |) j4 l4 X
0 E" N1 C8 t% I" C
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}& ?4 w% H2 K5 p( d0 u
4 u) C/ U% N/ _* P& s6 N
9 \" f/ |0 R& F& f9 O; R 4 Q; i7 n" }( z8 r
" b' ^; t& _1 Y) K: J; C
6 B, q1 M! j5 z6 } 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。7 b8 i8 ~$ r) R3 l
$ n, {+ N; F) E$ {' c
/ X% R( e6 `# H% k: ]
在is_allow()中增加对$this->savename的二次检查。
( b9 Y) H" ]3 a6 D% ?+ V( } : m) J4 [- E) \) j
8 [& s" L( \" J: n5 O3 K- y 最后
; D1 \7 N5 f$ n) H8 X4 y- U
; U7 k, D1 z+ K
+ `) @9 r: i1 p) ]) w 嘛,祝各位大师傅中秋快乐!
& D7 L @0 k$ l& }0 @+ t8 k8 A
# F% V3 Z* f( {6 Q1 i# e2 q5 Y2 P+ w0 i8 |
% Q/ u& Y2 ?! Z' B! k7 G, Y
% @ y& `' G" _7 I
|