! W! H' A5 U6 D. i- H
@7 T: l3 K5 P6 Q
) g ]* u; z" z
3 i* t+ Z6 D1 l8 D: n) n 前言. f3 L; _3 |# r! ~ @0 P
. [& }# C' Y( R+ n" @, P
7 y& C) `5 K) L 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。. d- a; H" B% a* M5 ~# i; t
- n% g3 x4 F; ]6 S/ ?5 g
# F7 I4 |' H" f4 O; B+ F
( `2 K5 k. ^. z9 O8 E& D
. V. x b T# J4 ~! h6 ]* h* A% S
6 j# F9 M' z7 Q! W* C 漏洞分析9 _# j( G% ?" ?8 h: }& f. l6 x
1 f+ k( W& ?, u9 A( x$ [* b
' r6 ]% R7 C( A 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:( b. c4 M! [& [1 N/ I/ B$ D( D5 d
: E+ P- M3 M$ [, H2 ?1 v) T& m# k
8 b* K& E P9 P 9 h4 }: h) j$ X% t' H! d0 J/ v
- [# m; ~) ^& G/ X1 F' a7 y* z. l# E" I: o
对应着avatar.inc.php代码如下:+ v4 e* a" }. p9 ]
" j8 r1 O" U+ X+ U$ I! y% T9 p- \9 t9 h# v$ c7 e0 t
<?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) {
2 c! p* ^" ?* z/ L( \3 S
4 Y* l6 {' I t" r. F9 {8 t& k
5 h$ e3 A; Z3 S- B case 'upload':9 C! j. G' m, a. i- U
* O1 @1 S: V/ B" V1 _( q. @! X
: K0 O5 V9 T% D4 O/ o* L2 o4 Z; ~3 J if(!$_FILES['file']['size']) {6 _5 Q# U1 h. j% R S4 V. M6 ~
- c, ^3 C) R; i) `& u) ~& \; c6 ^
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
8 J& c3 `0 @; Z+ b# z. g
; Q: x3 I, d& v% i; s; K
/ r# l7 G1 Y, K exit('{"error":1,"message":"Error FILE"}');: y2 [8 n" x$ x1 ^
8 }# Z6 I8 ~* W
) N: Y% `; _+ y+ ?+ J
}
3 ~5 S" \: ~2 f; y 7 F+ k6 d9 d, A
( Y7 l, u6 G+ k# }+ A6 L3 g0 k require DT_ROOT.'/include/upload.class.php';$ Y( R6 r$ Y+ ^1 L8 ^/ V3 ?1 c
4 k' E# M/ D$ O; I& I/ N
' A1 G* t% g( [/ n& }0 r& V2 {( v
0 W3 M! q' b5 G( Q! |% t
, t% w1 s$ t1 u B5 X. Z; L. w: l/ ]
$ext = file_ext($_FILES['file']['name']);
: |5 O! s4 c+ T9 d
4 f; I1 {& U/ ~0 e* k- ]/ M0 ` e% L
! |- _2 K* H" L* ?) d3 ^ $name = 'avatar'.$_userid.'.'.$ext;
' ^% g7 W5 b# O: k3 b2 j ; F( o5 V) c) |% `+ P: V
1 o) J. b' m7 P& A
$file = DT_ROOT.'/file/temp/'.$name;9 d4 X3 M, b0 {) o+ C, ^
0 U9 [" e. ^6 C& L& ]
# r+ h! p" [+ T$ G, S; l% X4 F5 A
7 `# R }6 s" Y% @+ r
7 C. |7 s! a7 j8 V M6 q. j: n! x: ]. H; R
if(is_file($file)) file_del($file);5 y Z* S7 g( J- f5 i( W2 Q
; \* F4 k, M: @" G" @9 r* N0 t) C$ ]( w9 K$ n# P
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');6 ~, A% ~+ {3 e- u: C4 H
) R2 f! g2 D0 k7 Q+ V" \7 G
G8 b+ A3 o: j; |. Y
1 ^0 m2 b6 y+ w. Y+ e$ J* O6 I' s4 ] & D, e1 i0 z: U! s8 T1 \& a9 g I
) P5 P( X1 ~4 g
$upload->adduserid = false;% k& Q4 F$ o4 O
4 w' P% B) k" Q2 b
* N5 }! h2 V6 J2 ~8 c" t5 ?
/ u0 u4 t: N4 t' @4 W
I* m! j- S" r& ]+ i) B
! B+ [1 @$ V& g/ u1 g: n if($upload->save()) {+ L I2 s7 a) N, G" a( U6 ]: V
- x3 ]( h: _. K# A% N! @2 F5 }8 x K' z& g* F
...
2 t, ^+ ]( M; u Z& Q* K. M : K9 J. R/ R; ~
8 d8 k5 w8 L5 j s, q& Q
} else {
6 M$ ~+ D; z* y0 i5 _+ W; F 3 K5 d+ j9 q% e" U
3 k; w: V9 n2 S# c+ Z* ]6 w+ { ...5 K* g, A' @6 n
5 i4 m0 A! w6 C# _( }/ s1 V# ?
2 i8 G% G& f3 W( ~. T! _3 P; ?
}; G9 d. b# b/ C9 F; J# P
; f5 t7 o: W: P1 z1 i
1 R# c' e/ [- w+ K break;# Z6 d( z( ] ~, U# P( @
4 R' E1 z" z1 ?9 H5 F% Y2 x" r, G7 c
8 L7 j( A% l3 S, j. { 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
' I( m q- E! ]) v3 g' D( s9 W+ z
7 {% ~- R3 g) U+ y+ v/ }, r8 `* J8 ~! I* K/ P( I ^- |
upload对象构造函数如下,include/upload.class.php:25:
) {% ]" ~ `4 ]# d1 ^ 5 ^: S" o9 h/ s
% K* L7 G% o- @
<?phpclass upload {
0 v. k9 D! C" m+ v- g2 D
/ |( K; X+ K7 C. W0 r/ b4 g& S+ \' }, k( l
function __construct($_file, $savepath, $savename = '', $fileformat = '') {" d8 C. j% z, e. w; z* a$ [( w: Y6 i
. d9 ~# q5 i* X f2 L3 t2 ]4 b! P' W. j& K+ d( j
global $DT, $_userid;
# s7 m/ {" ]. u1 y* V
' M4 g4 R# g1 I, R: p- [- Z& e( _) x( Z' y$ W7 l1 E; L; B
foreach($_file as $file) {8 Q9 {, j8 w8 ~ J/ z5 ^4 C4 p
' u9 M2 ^: R% X' r T6 E6 s* V7 y" j S" W C& J3 V C
$this->file = $file['tmp_name'];
* f) t" s0 W: \' k
' ?# ~$ f& u1 S& @% m( ~
7 ~1 d$ I6 `; h4 Z% S5 ~) d) F $this->file_name = $file['name'];0 I, J! t; Q W
% o% A7 X: W' k& O4 W2 e( Z
+ Z5 `0 _7 D+ H' h
$this->file_size = $file['size'];
! t2 B" b/ e% y# q1 F" _# S, k & A( h& G( B( t: v+ ~& @/ m
" c2 o6 Y# X6 A V ^4 {( _ $this->file_type = $file['type'];
! U$ T) A' Z" {+ G5 u6 _$ b1 c
3 R& C+ {# @+ L8 ^# }& _% M7 a( t7 ]) _ C4 @( i6 t% {5 @
$this->file_error = $file['error'];
* K, U/ g# }- k2 Y 8 l a/ P) T' }* v3 I
+ _- |1 ^- h, t
5 u5 ]; N: [1 G5 X' d
* `% W/ z6 \5 W, f
( D4 `# O {. z }
' p$ ?* y% l1 B6 B
/ R+ L" @5 Y5 T6 ~5 d$ q# j/ [% @1 c( W! l
$this->userid = $_userid;
. v( y9 P% | p& U! o6 D
7 ?! P. b) H- U/ m/ o+ D8 ?# k7 f: _
$this->ext = file_ext($this->file_name);
# e" B6 a3 p" ?/ ~( b! R5 D5 U e: Z/ L' b8 q8 l
/ s, d3 v( H, ?6 M$ x5 k
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
u* H0 [7 R/ ?( X% ?% T0 M3 ^
0 v1 ]$ a ~$ k' t- l% i6 D
# t/ N# n3 F. o$ `7 N( ? $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;6 G1 h& @8 L8 @ ~1 F
+ ^3 n6 m7 [, K( L+ V
' N" e& g* p% x. p7 `3 K
$this->savepath = $savepath;6 ~0 U/ j) Z8 M# \' Z
$ _& H# i" d$ u4 Z; x5 R
8 Y4 M5 A; ]5 |$ K; Z" J
$this->savename = $savename;
6 z7 D7 |% h+ M' L7 y 0 f" l1 ]* @: U& O+ z+ D5 Q7 \; c. e
, l) C2 e$ r( w3 W- ?
}}
0 I! C8 t+ V# z! f& X& ` & G- x4 M/ v9 t* E* V$ h
7 R. F0 E r% \( D! @ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
6 A5 A+ j/ V: l6 @$ V1 K3 [# D J: \$ R2 f* O4 M0 G' \' T
# T6 [1 S+ m2 Q9 u/ Y( m% U 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 # F. b/ f7 m/ }/ a9 h/ t! T" U+ I
% p: w" ?: p `4 E7 A: A1 t5 r4 U% S/ s) B0 M. S
$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
* P. p3 `1 Z: E) r3 d" X 5 t) M( V7 F& K, i
; d; i6 p- ]$ L! L) w8 }6 y
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:; O8 j$ W9 ]( U q
4 b" W. o+ k- A' k4 ^
8 A0 J3 Q5 J7 J# A8 [9 s* O
* j3 a0 o0 e5 L( a& a: ~
: F3 v, w. h! V* i
% Z) T- e. G, t; A+ W6 e 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
# F A2 N# U# p4 `
4 l& M6 `$ n6 y+ ?, C8 j I( m" t9 e! t7 ^& {' p
<?phpclass upload {" R& }" m: H) h1 a6 i3 H1 d/ i( J
: w$ t7 M: t, G5 O
, D# `4 f. ^. h0 H) j3 H9 { function save() {
$ Q- d" |+ B' r, z0 q0 }. k: d" b3 t
# S/ ?) R N0 f2 c2 Z9 \) U, O' ?6 w1 D' e( M# M
include load('include.lang');5 {5 {9 t- p5 O
; B5 M1 [$ D2 a+ C; C% v i* K# B
8 m- K' i) v* C v) o! g/ ^/ E' H if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
4 Y- c8 [7 t6 a b! U2 n " N' o3 M, W6 P6 ?. [: {: s
* P7 ^* O1 @! x' b% ^6 y6 @: `
/ ]: M0 g7 N z. K . M- P9 i5 {; s9 P+ u# f3 h2 I- @
9 _, Y6 Y/ g( O v! p if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');0 W" [ Q8 T' X7 ~/ e
& \+ `1 d. S, b6 `
Y) W$ T' v. K! t& B! {
+ Y, F* \; j4 T2 B/ y0 |0 x
$ e$ |9 W% c6 l8 Z2 ^! W! C% s# Q8 C
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
* F% N- T6 @9 G) P9 u 1 [" m, p. k" U( Y
) e4 m% @! f, R6 e- f) S3 }
8 e1 i" T/ ^+ y% u6 }/ E
' D2 ^) p/ \2 U6 W# q1 Q6 H9 S: ~* k' s
$this->set_savepath($this->savepath);
! W" a. g6 e7 V; j1 k' R9 d% _ . D/ v: j3 n& b& T! x& @
. _8 y$ s# Y9 o8 v& E4 {6 Y
$this->set_savename($this->savename);
8 v# {' }6 F' s5 D6 \4 ?; E
; G* f& @) ~7 Y/ R5 C+ X9 ~, R3 V+ r
- n: J" w {* V( l
* h. m# u$ W V5 ^
. W5 _9 W# M" n3 o3 f if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" s7 }5 z# @0 l- h2 Q- X) a, Y
! V" O- s& k3 v& a
6 y7 `, p `- D* L* M/ K8 Y% s
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);# X1 c a' H0 T( X& q) \
5 j* X' D6 `6 {& d0 d; p1 I% c& R& l( b" Y* t1 d+ e
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
' F1 D- A- N7 w; m l4 P0 G
" \* q' N$ F3 t( v2 `
& T2 ], a* Y9 u) {3 I& `5 v7 S n- a$ w( Z2 q! x2 n
9 D) n" T$ n+ c' z8 V! j
]( A1 d4 @1 W( o. S. c6 o
$this->image = $this->is_image();4 ?4 z$ ^3 D) y$ n1 X! P5 p
9 @, I G( W6 P' ]
' H; J) v1 \' }7 g+ w2 n8 D if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);# h. q8 ~; z' L' ^) f1 W
: H+ ?# Y; J$ ^/ R8 p. o1 p6 y& o, m9 ]2 Y8 g
return true;8 a9 A0 g# J0 |, \6 Q: I6 A3 Q
z4 K& e2 h6 f# k$ ~
0 A* U0 X; A3 n0 N& Z, ] }}
! O0 {- c- y/ i/ A: J( r( I ( p7 V c, {. i( T7 |
8 B6 O* W2 z7 \ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:1 C* _. r9 s, T& A
# I& g4 ]8 C, w" g
) i6 O* `- H, E e9 { <?php
6 t0 w3 M4 @% E1 D% r ! ?, `" \2 F) k9 @
, S! n4 l+ i% s$ Z
function is_allow() {, H5 }9 `) }; |' P" d; X' c+ n# x
" s D) ^9 {. z- b; k* A
% } L, |( j5 q$ b) P K' O* A if(!$this->fileformat) return false;! Z- G* r5 Q9 Z, O7 Z
; I- {: [$ K9 v7 ~4 n2 o" c( P* Z
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
$ U- P* v# T- O: U S / E; n6 U n9 n/ d
' O3 M3 F9 q% r0 X
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;$ D" g. l* M" n8 w2 r* h
E+ w1 J# f! C L5 ~* W! Q. ~8 O" j) F9 t8 L+ _' a1 |
return true;" k d% P+ f" g6 ^/ Z' `
, Y& }! N* M8 R; t+ @" c) {& {! ]! a5 t9 _( N! \
}7 R$ c; }$ ?+ l$ {6 l H
2 ~" `3 N _( o* ? h
h% b5 Q$ s \+ G) K5 w
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。4 f: @, F& R7 ^1 d" m) ^5 k8 r
6 |$ [, z. M( @- B0 o( J
: r* B0 T/ |7 F, @1 b5 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文件。+ r0 N2 X9 g1 S+ c
* i) P8 p2 J5 N( T8 ~
+ F7 N( ~ K& i, A! E( G2 Y 漏洞利用
4 I- k) N- L. F7 i) Z D
; l( t n# B7 V6 V4 w1 J5 c5 R! o, C+ z. ]. m) g: k
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。- f/ F" V0 ?) a& l, f
5 @4 B% h9 M U2 E$ y
* t" [5 B) }, e& K# F
r' P3 Q4 \ [) ~5 Y b
+ W0 R a, R; | c5 K
5 }2 R }2 f: A. U! h+ o0 u1 R 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
/ M, S4 S9 D" ~5 t# \ : o. j1 w* {: F R4 i! x
& R6 `# o8 i5 U- z+ S. e/ G 不过实际利用上会有一定的限制。( \! X' L& O) d* z% {. M
- s" ^! ^- }# K, V, N0 b
% J6 G# ^* {" a3 V' `7 X' B' B 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 ^& D+ X/ m: j: q$ J; G
- _& P D& W! S$ `" }, N! C* C1 F' z7 O) B3 w, O4 C, U5 Y- R* N' H
( s! x4 T3 N4 X; U
$ b0 u# R! J3 @2 B" x
3 B* U1 S% t* c
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
! J9 }" p/ P# r1 |$ H+ r) U
6 D( A3 a1 s% U' l7 H4 @* M, |+ V
省略...$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]);省略...
8 Y+ Z& V$ }, V' V
) `; b* X4 H; N1 Y8 L' V! A0 L& k: h3 ?0 e @1 e
因此要利用成功就需要条件竞争了。
! `8 e/ p+ i) p9 {$ B- y! F $ K- H! k& f( A2 I
4 E* }5 H+ G8 ^: X6 {5 ~8 E5 N. e5 B 补丁分析
4 `# ?4 a6 ]8 D b* F. r3 _9 V' g1 ]% _ b* C2 C- S; t2 e
+ K% o! P5 n, j% o9 Q+ C
9 C- w' N% l. K* _, w2 k3 ]% N
" ^# U. L1 A |: x9 l; [; e$ w8 p5 {
; P6 W: _* T8 c$ {. t! j
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
% q4 i! p0 \) X9 f* r: b7 _4 | `; h+ O: w, U0 v+ I5 a
( Y. H; o0 J$ A% q; F# h# n
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}* }# c& u& m7 \1 |# C! N8 O
0 ]/ Y6 b# h+ H1 R, [ {( C' {8 \$ G! k8 E1 G# U4 S) H
0 C4 d$ ]: X: W
8 W- y, n: I7 o" j+ {' E$ D! X: w
9 T S" n( D N 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
2 v2 Y* s$ E5 T4 C' S* b & _2 j% e; L& B& T# G" O
1 H6 j- `# S0 R$ G0 R b5 E 在is_allow()中增加对$this->savename的二次检查。% j8 b' g" u% g/ D
6 [) g8 n; ?: f B2 p
( w& T3 O* h* F$ A* h0 L- ]0 } 最后
# T: k, h& O) \: c% x; }* t& G4 K5 S& b
% v5 [% R2 ^6 K- T4 k" S 嘛,祝各位大师傅中秋快乐!1 b" D2 m5 G5 e- T/ c$ K5 p
- Y- }1 c2 s) h5 x. W
8 y( ?" X$ Q9 z. L
+ V: ^9 u6 q. x/ E2 L
9 }& R3 D# ]) B+ _+ W
|