- z4 d# I2 `7 u& j
" T; b2 M( v. O( J7 ]& o
" n2 y. U- A9 o' c l+ c
3 ?' B9 a0 ]3 v# R
前言
& z. ?2 c1 X) R, ~+ Z
7 z- v: p. ]) ?7 m7 `
$ U0 U9 B4 s8 e& F, M 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。# h$ L; e& `/ g
( S; d, [6 d8 P' H/ y
: [6 @1 e" A% N V- L, V' e3 H" P ` , V4 y% Z+ ]3 K
2 G1 U, x) i* v/ ?5 E5 ^
) i, C1 q# d* U0 W6 E 漏洞分析
+ V: J+ b6 V" I1 |' V! X7 j8 t% x; H3 T( |/ ~, x! s
" O. Y5 a7 b- E( ~& t* c. m 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:+ w% h, i7 g: |) }
) X" H; ]9 P6 i" t
+ x0 @9 R6 I2 j6 |7 D
( _* ?9 {) N( I$ z
: I# L$ W0 X" ~1 T$ F8 Q* p
3 P/ {. T& b% @5 [ 对应着avatar.inc.php代码如下:4 ^1 F& k% `) h8 p3 p' K2 p3 L& M
1 f1 K2 t+ g" n7 y% U7 v
9 Q# ~" E8 N( z- M6 E a <?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& V/ S. L2 A$ E
/ ^" V% A9 |4 |( F( W* |$ a! y& i; E3 b
case 'upload':( s& h; g2 i$ d6 R$ M2 J
, i, e% X6 F. J4 d; x8 L4 D
' \: E* s* p' | v if(!$_FILES['file']['size']) {
3 L9 O+ Q0 q# F9 P3 [2 @
: [; T! D, F5 z8 a2 a- L2 k4 F6 e' m# U2 z8 A. k# @4 o
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);8 E( s0 P. ~( H, q" C @1 ~5 ^
7 G2 \. S! H, o* c+ S
" y# f! t" H' J' B+ b: R exit('{"error":1,"message":"Error FILE"}');
! l. l) a3 k2 A % a9 m/ h1 F" B: {" s
4 h: M' Q" n1 _, X
}
# Q" K! V$ c0 t% z : }7 L" Z. E8 G, n( ?
. i) K# d" s5 v6 M
require DT_ROOT.'/include/upload.class.php';
" M! u a# e8 |2 p+ R2 J9 V1 Y3 T5 ]
' u/ P0 I, g& V7 a; z2 u9 Z9 }% Z2 E
, P9 K1 ^ Q5 l. `
2 U: i, l# Y7 x+ [6 K1 k3 e9 v" U0 ~" y O8 T! Q
$ext = file_ext($_FILES['file']['name']);; X, N- {& V2 S' I& n) U$ @
/ ~) ~ V4 i5 z; W. z: _! I _3 o
& \! N4 l5 |9 v6 x& \- {
$name = 'avatar'.$_userid.'.'.$ext;
2 E) g# R% T! \( Y/ t ! M# D, I: V8 @' l8 P; }
- V; ~% B( m% v" M
$file = DT_ROOT.'/file/temp/'.$name;
! i8 e; x% D6 a, X# p# d ) M! Z- k9 Z: R# o% A' u
5 P; b t8 t( S! L- a4 m! }
" ~+ K+ A8 T2 o3 U/ i/ a5 F $ M( v/ U6 [+ r8 |
# v! ]$ I) L1 R+ l2 M: c; p if(is_file($file)) file_del($file);
" N3 L; l3 k4 I9 l |0 m( { + m: W$ F; J/ p7 a
8 u7 Z/ o% v1 R/ |4 y5 r+ ^
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');! u8 ^ E$ W, f
; u& h# L7 O5 g4 [ }$ L# U/ A
& {' Z" t) M) v, y
- b# ~) \8 Q' g; S2 G$ p) H0 B" u . d B9 z3 o. h; P
# H2 {, [: ]. ~, @- g# {
$upload->adduserid = false;* _& }% T" o0 e/ Q* i9 [& @
1 @" \1 B& Y, w9 w+ o
. {* M9 }) ?; d% l
) B, U; m% l; y4 h3 g2 t$ B " h8 `) m# H2 C6 b/ T/ [
+ o2 I( \. m9 C+ I$ ~
if($upload->save()) {
+ j9 o8 Y0 Q& k8 ~
2 k) z- b9 V9 N; B. {) [! X( v) l5 H) ]6 S# ~1 Z
... f4 a% h: I) t1 e: v, p/ F
3 U. [5 X$ F$ G
: U9 b* Y S/ e0 S/ o2 b } else {; V: n2 `! S1 B3 X9 k7 a i
) K7 F4 l6 a: b3 Y
! Q- j4 {* h" k: D- t$ H ...
" i0 C6 I' g5 S( F5 o( f% l ! m7 K, x! `( L- e+ X8 y
W8 \) k( W4 _0 p3 k* E. e }
9 p1 {( r, e% ~0 d n5 ~+ Y ! O7 Y8 u, b- k: f- b! E9 Y8 g
% H0 B1 `4 }0 ?
break;( k9 u6 A0 p1 |) H4 G# {5 m
, U( i( d" D+ T% F9 ~+ V" z3 e
, z& W* B& k. h u; s) a 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
9 U+ Y9 T; N7 B 9 B2 H& `& U% a1 g% }( `- C
, h/ ^6 d6 E& O1 T' C upload对象构造函数如下,include/upload.class.php:25:: K! K5 G7 V+ E2 N
9 p' b. ]1 B7 l( k' i, A& Q) [. F, l$ k( L, G' z" C' K
<?phpclass upload {
+ y% B4 D+ ^9 W! y
6 y3 V# k# X1 v! ]8 y u; x( ~9 t6 H8 u; r1 ]
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
* M' R3 m, \7 f- k# |6 n
- G. T2 W/ Y8 ~& L( q
/ S- g% E- W, W4 T global $DT, $_userid;8 k; ^3 t- c, D
" O0 ?! {( Q0 _; O
7 t% r1 S3 Q. o( Z6 q
foreach($_file as $file) {4 M, s# _; h8 W- W) _+ |, N
8 G( | V" K6 q1 X$ V& G) F& k( \" G2 v7 X& N/ L7 |9 b# X+ E8 c
$this->file = $file['tmp_name'];
6 ^0 f* a5 A8 x* {! ]. M1 g: o+ m' I
7 c; E7 o6 ~( F' Q$ b! \7 H! T% z0 O
$this->file_name = $file['name'];
- m7 Z" C! R' H9 }. B& ? 2 N( u1 B. F- t8 U# C
6 ]' }7 W9 D% X7 L9 @& I
$this->file_size = $file['size'];
6 P( d5 \7 p8 o! o$ ?; r5 o : ]0 s, H/ M' o( H L+ ]
$ L* P- Y$ u3 e
$this->file_type = $file['type'];! c* Z3 Q0 b' D/ K
9 s, E- e+ T$ T& \
# Y- F; i% f( b( u7 ^7 _ $this->file_error = $file['error'];3 `+ Z" c3 d" e2 {
$ Q# |- k+ W6 m/ B9 Z$ j" I
2 a0 ?0 C! c/ ], j0 [( Z + Y& w3 K0 r5 I7 i ~ {2 O
/ v) |' I' I" f- X5 p
- T% s& D# w1 t/ [& A, j: ^ }
( c; b2 k! f- S! [3 E
' c. _. T" u- h: s# q# p4 i4 f: u
$ ]! \4 \/ \: \% s" V, z* ?# E $this->userid = $_userid;
/ c5 m$ M' ^: u s8 c( f0 I 4 u" l; D9 R4 r2 d% o6 N8 J: |
5 w8 z, q$ N" l
$this->ext = file_ext($this->file_name);7 f( ^- s* y- g: L0 }8 l% U
/ H: Q5 y* ]+ Q. {+ _% G
! i/ b# e6 b% [% p4 o $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];$ ~3 k0 ^* c" e4 M% k1 h: a
2 K( S C+ n: i, ~; N! |) D. n+ b' ~- ~8 E0 c( h, I+ L" W5 d
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;: ^2 W2 X# z9 Y$ r+ k G; p
. `( a1 @4 D [. M; q+ S- Z4 p
) a2 a; r* `: n $this->savepath = $savepath;
C- G$ i$ r3 p l3 K, r
* e& K/ p; j" m3 h, ]7 _/ [" {$ r
4 {) W$ B3 b5 Q7 L6 _3 V: u1 ^1 r: [3 ] $this->savename = $savename;% @; |7 s: Z( |
4 x9 W D: |0 v* @- ?% c `" ^
1 e+ e8 K9 {( R Z# h o }}5 q' ?0 f+ @4 R2 Z9 z
; K7 [" h0 J. E0 c* [$ t) q
1 i+ X$ H. Y* ^1 i: E/ O7 @ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
( f* J- J& H$ Z
' J w& ?4 g- ^& e/ Y) i
M+ r* C Q3 L8 I) G% R 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
! r/ N8 ?: z5 l9 E5 ]0 `) k % V3 d7 k5 w7 ]: u' O, h# x
; g8 A) M! P. m0 A
$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
% U4 N& B3 J. R
* |2 t* z5 V# o. Y6 l i% s0 c7 G6 H- T: `/ n6 j3 o
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:) r6 B/ H% b% ?! |0 f; w
* J; k8 {* D" [$ W) C4 K0 F. o) O4 Q/ O
' H( L! C% E1 N3 |9 R3 _5 o3 c
4 e( V! S) H& G2 N
% d0 a8 w% f+ \$ l 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
: R7 p: n- d8 v. q8 ?* l" K0 l+ Y
# X6 \2 N' O8 } Z( F/ K6 T% O5 T
<?phpclass upload {
( ]4 U3 h- V% k4 ~# t
, l4 r+ N" w: S) N9 A" e. Y
; h1 h. y- g+ P, I6 b- s function save() {! ~$ z- j* ~6 ~' U/ q5 d
/ Q4 z% u; p$ D( U
& K$ N! X+ ]: b7 ]( j/ [ t' E1 L4 H
include load('include.lang');; b6 _$ x6 \ ]) P' V' u
$ u5 n3 x: m7 L5 F+ E5 ]' T: F
. [; M7 N5 h& H$ ?3 B# p; w6 k if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
' a: j0 m+ w9 V& N+ ?* V9 B% s
T& C" X% }. z5 Q) T9 r Y: _, Q( B; k" {
+ h3 V$ X$ l4 c" x) M8 f
, j1 S, s% B x+ t% t
7 J' P; V0 q/ ^! u( k; s6 E9 U if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
" }0 A3 f! J& Q5 o3 P' p
. O; {) `3 Q c: P9 d& V# x; A& v7 P& W# I
- P0 G' b) f$ s; a0 f5 I& Y0 u2 {
& u+ E' m% G3 E* g6 a7 Q
6 O2 C) n* x2 e" ^( G# ? if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
/ u* Q2 b( s0 V5 k
. l( k7 O2 W( L: ]' r- G4 E, [# y$ J7 e1 Q8 r9 n
/ x. z5 ~; h3 N3 i g9 s* l $ N/ ]7 d$ @' K; `8 I' V+ j* ]! K" o
) t- E, m+ l- Z u7 p $this->set_savepath($this->savepath);/ a" a) h, J' J2 [
3 ?7 N; ~. S7 w7 A
) J1 @9 E' B+ x( K: \4 a- b( I $this->set_savename($this->savename);( E' O: w+ b5 n' P
# I. s# Y5 c U8 Z: o
/ F1 f/ T& L% K; l
4 y% b- z, T- D2 m# G
& U; I, s0 {: P, V7 W; A+ d: d( D1 E
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
' T- e0 h8 e7 m + W/ O4 N+ \& H+ R+ X
+ E: _ D& b% [8 b- m( x4 g9 {
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
( s; C2 ^, F s9 o6 s) ? 9 _6 b8 x$ b4 Y/ ?1 J4 j& {
* O% r8 Y9 j* e% j) s" H2 ]
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
: U* @/ k* O: B1 Q& t z: W / r9 y7 N( e3 x6 D8 L# u8 }" S, i
( b0 u [- M- G b$ X, d2 a3 ?
: J$ Z; Y: S$ F. E6 x0 C/ c0 ?
) ^, L% n5 ^ H( [; x* S& u
. |- s9 n! y! v6 H: |7 J
$this->image = $this->is_image();% \% q/ L' o3 \1 y) D
9 b4 s) M" B" u. k" F
2 q5 P1 p3 p% r6 X+ G3 ] if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
- h" {8 L1 I$ `% H u8 d4 G 8 a0 q; }1 ]* P3 }) K! e5 N: T
+ U) B: d; r* `
return true;
- f4 k, u! U/ V1 x1 g, g- h
! Y9 l2 B( V+ Q7 M+ r. v( A4 r
4 d/ |5 Y8 u) D$ V1 Z: K7 M }}" g+ s& J# F( G1 S" r* Y; H1 R
9 a" q% u. j3 G3 P- S. y: @: G! j$ M/ i/ F, V2 E
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
6 A: n* i% L0 y/ H/ F% Y( c
5 X f7 y4 M( o( b4 x, f# R
. Y9 x. Y8 B1 x7 N, c6 s( ^2 i; q6 P <?php
; ?! `& t2 d4 A' F. H4 y
" b6 w H1 x! C! z- a7 `& ^% e% ~
$ k8 J e6 Z; N function is_allow() {
3 z2 F% Z" w9 U Z * w( E$ ^( x3 | [$ R) O- S$ E
: q+ y1 X3 N; c! x
if(!$this->fileformat) return false;$ L4 q8 i1 J; U8 c- K: p0 r
, J1 o' W3 m0 m' M
: T2 f. c" W8 U' f R, A: n if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
0 I8 F# m' L/ M) P% K
# S; L7 n3 d6 }7 @$ ?* J6 w0 f) ~$ Z4 c- A4 g. u9 {' R
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;
F0 D7 ^1 I: D* z
# ^6 }$ n# T6 _! }, f0 b4 [& k# W5 ]. r3 A* d0 M9 G5 O
return true;6 Q$ Z2 s0 u! S' |/ V+ h( [
5 ]. E7 Z4 `5 Q$ T* ~8 W" \* w& ]7 l
}) g, E5 D1 G" s8 v+ m' V. ~
( [* j3 a/ @1 [. P/ ]. D' ]2 H
& S. C9 O, P9 _$ @ K+ t7 I& ^: e 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。, b4 z# k P1 F
- _) E; }* A- b7 U3 i8 M" h5 Q2 Z4 e
4 T' S+ J) S' o 接着会进行真正的保存。通过$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文件。
1 v' `* k4 f4 v 7 S( p8 n5 O: m; B* d3 l3 X
& V6 U& [; M, U$ H 漏洞利用+ K) n, @/ _: v
3 Z7 I+ h, u2 @1 C% F/ M
, V0 q$ D) [, Y- `# m" A/ w3 _
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
$ O. W- w( P* M* k ! K. w/ u2 v' O, Z9 B
* w" c- ^" ^8 N$ c6 ~$ P
% a; {, \% u" K D. p $ f, c! F; \4 a, U$ T) w
# r$ D9 \ ?0 {# R9 y# W 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid; u" ^" Q$ o: N! _. V
9 I2 B2 T* v/ D$ N+ a4 k5 W
, r d- [5 ~3 l6 p) t# J* n% y 不过实际利用上会有一定的限制。' ~8 |7 |% j2 @. C. l/ c9 S
8 `* {3 E' e, _6 R8 B
. R& h% L# E1 g 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
$ u; i9 [# y8 y% }& K5 H 3 `0 K& F* N) w4 Q; Q
* w8 L0 j6 ?' z Z$ k
" f- {0 Y+ K; D- m5 f8 e" a
* k" w& p8 Y- j! N/ K
6 M. F/ e8 V9 J" k8 X Q
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:4 e- k& D1 A& \
. R5 m$ U- q% i! w( x2 L' u
0 @. ] e% C- a6 F/ o& d, n6 R 省略...$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]);省略...* b+ Z. T/ V5 L- _
6 P/ D9 P$ h7 O1 D' j9 G6 p' d/ X6 R* c3 q
因此要利用成功就需要条件竞争了。
1 I/ W/ m! U z1 z) v/ F% y " K. P# Z s* L2 G7 x. t
9 s/ M5 Z) c2 u7 C 补丁分析
: G0 h2 D1 w6 J$ x. y# K4 r$ u0 K ~4 F4 @9 o5 Q5 I
) H9 N! H6 |" ^. K1 W( x
" _1 A3 _( \! z" U 8 Q! V; S u1 I3 L& s/ F9 a
0 t9 {: X: {+ z
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:' \4 S% p1 w- \& }& R2 W
4 f4 B8 V( ?) \
, ?* l! r' C7 T" E) @; w function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
: t. l7 Z3 D! y, S
7 K# Q! F6 d Q8 O3 ?( Q
3 x. q, C7 J+ `! g8 m7 E6 [% L
& P& z' Z- A9 g! W" L
4 Q8 H9 {2 d! M! g, m* f/ J9 n
7 ?' U/ o+ |1 O9 g4 n1 K* w" k I" I0 H 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。% F" u; \/ ~; r: N: A" K
" E8 e" W. q4 u; T3 @# z
) Y6 J! o' C7 K# n: a 在is_allow()中增加对$this->savename的二次检查。 P/ e- R/ n. G$ H8 R) {) b0 t
9 j9 [/ E7 A; P1 n) N/ p4 s$ B$ G
5 i2 B! S0 ?( b+ l' A% P) [' r% P 最后
0 I# F" ?' {! I& J1 o& c6 G; S8 B9 A' C; C6 z! C
& |2 d |2 x$ H, r2 _) O 嘛,祝各位大师傅中秋快乐!
6 y- r& \% Q7 Z5 o4 R! D. d
- v2 i; u$ E9 g9 M6 b F( x5 ~) \6 G4 a
% E! L# U; b5 \6 E' W
! A2 [/ S0 P; a" Q
|