5 {5 y$ t* W0 g8 N, V/ c7 A$ a' A$ x4 q) \7 V
# H. j0 s h/ _9 n+ B- x2 d
3 Z& T' I! v, ?3 {9 _7 _; i0 } 前言
" L3 {% M; z' _0 m" {8 J+ E' M/ L3 g3 A6 p M( ~2 u1 w$ W
% k! ^4 ?" h6 ?8 G D 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。# M* z6 g A! l
) d& K n; ~: G+ C$ Z: R
" |- ~$ M/ S7 T" T
8 j+ f6 ~1 w3 y ~6 W% l1 Y& @# k
# @; U$ B: j5 n8 S' k0 a
$ w" M+ ?* }. n) o5 A" @
漏洞分析
, k; I7 M i* y, @) D! p0 X. v6 s8 J& Z) c* |& W! R- V* b
/ Y' N/ J0 q+ S# d+ `6 _+ f 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:( @6 i* [8 S ], V7 B3 _
1 F1 ^* q B% Y1 Q, S0 i
3 b+ X; V F/ P! B - K9 T) o5 O7 F/ g4 N" y
6 M6 g+ a3 f; n, D4 t2 n
q6 ? V9 o9 c 对应着avatar.inc.php代码如下:
) Z+ L: E( L9 z; x$ z, ]. a/ Y* a ) y1 B3 i* X: }$ [8 X2 r
( E0 l! i- `" L& I! c1 @' _ <?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 m1 Z! F6 A3 V, R( o2 x 2 Q: C4 P9 d: L
# ^* o+ K9 H( D4 f
case 'upload':0 |6 M* t5 X2 l# a
( r7 M9 s2 b" `
" p5 _( O& }9 _9 L( G* |* w0 u& T% a if(!$_FILES['file']['size']) {4 v ?; Q# w) P# b' B5 w- ?$ W
; ?7 q5 O) n( X' A: V
# n5 A/ `" t% g- L3 U6 w if($DT_PC) dheader('?action=html&reload='.$DT_TIME);' T( k; Q) i1 |! R
3 o3 l( m; L5 K+ O% v" S5 S A7 t' U
3 a9 l$ f$ @ `5 }- K& S5 T exit('{"error":1,"message":"Error FILE"}');( \% q u) Z& H/ Q
" w) b7 V4 L- ?; C5 y) g6 \( _0 U2 ?( g. Y# n5 ]
}2 ]- w; \, I( f' o5 I2 U
; W8 T! @9 d5 F/ K" g5 @
3 k5 U3 l. d: q require DT_ROOT.'/include/upload.class.php';0 Y8 `1 w1 R5 i" e) [
/ T4 M8 c! N/ j) _0 ~/ w1 p
, l5 y1 P( G& C) S, s3 K' e
: G9 B+ U' d- a$ N1 @: B8 Y
& `& m6 J# c& f0 L# B
, N( q) k5 i; H S4 u+ M $ext = file_ext($_FILES['file']['name']);
' E8 E9 A0 T) C8 o
$ V- N: q" S* m0 b/ r' c
" V8 P$ h v" `5 |% A $name = 'avatar'.$_userid.'.'.$ext;
0 A8 y2 h* F* `; ?% R1 ` # i1 h; }5 ? U1 @% ]3 c. L4 \
3 z. b' O+ ?5 K5 B: C2 m $file = DT_ROOT.'/file/temp/'.$name;
6 d! W) f' w, H1 j# ?
$ R: n, v0 m4 H0 E1 d: A6 V8 O; j- h
* S D! D* |0 i1 i # [) r+ Z$ \- ^* u1 D1 b1 `" E4 ]
. L6 k& u% C J" t; v) H* }, ^
if(is_file($file)) file_del($file);. ?) V$ i+ I, [ x: V$ U4 P
; m% T+ y& T; T4 _
8 a% V7 \& L( C% E9 H0 S9 z
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');* q" z, @8 L v( R, |. r
9 q3 ] Q0 q* p% h4 V, ~4 }2 P8 t3 T1 u
?" U9 z+ S' @$ ~3 W2 H6 q 0 ?3 H1 a4 i6 n+ A6 l
' S0 x+ G& j4 | l' V7 ~- y
- H; c% L* U2 h2 |0 y* F! R3 u/ c $upload->adduserid = false; H1 r H* q9 ?8 {
6 g( B' N, m2 w" M' @# W0 t
+ b, H: J& c+ A8 S ( B; r: V* r+ }4 x
0 W) n7 Z8 L, ^9 @1 X/ ]
$ t# p) v2 Y! O( D: x if($upload->save()) {
" _4 c* R: c. R1 p: Q) c/ z Y2 |( f( i: q+ J0 G6 D; Q; y
9 Y, r0 I6 S8 M2 M
..., g- d$ y+ x) n. W+ C
2 \3 o& g5 Q2 N. c4 w
' G* b C2 K2 }5 ?8 t
} else {+ f, V$ j8 T. v4 h, f
. C/ P' c; ^; \+ @- l
d$ ]) W+ O. M, }% W
...
& X) E7 A% T. Q$ L. ^ 9 ~0 i8 t1 s# J7 ]9 f
& h- P0 o2 p9 r9 E7 C# B& y
}! S5 ?- e0 [8 m, m
3 F/ m! H. H# V( E) N2 N7 B) U- Q
break;, v$ Q3 N) B; T1 h( R
$ k$ Q4 S. s+ F- A0 V) v7 ^+ I
6 F; w$ ~2 D/ J0 R) q; n 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。; V$ v3 C4 L! Z- J! d. a
* z: S- n+ {* K1 J0 _, j
. r( u7 J* P1 \9 A upload对象构造函数如下,include/upload.class.php:25:
; H# u1 S9 \8 F$ I+ v% Z
& f. i- ~* Q" t/ X2 [8 _7 M' Y6 A
<?phpclass upload {
1 Q9 R3 D$ u9 u2 C
% O G8 c- M$ |' @, k# ?# e+ I4 A- U. h/ W4 n# ~
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
# }) z3 t% }/ x3 q- [6 Y' E
( O; [2 c& ~3 g ~& n. z0 Q: `' S+ M `+ L1 W
global $DT, $_userid;
: c. A2 v3 h" Z$ X # O3 l7 J, Y% w8 E# m
. C: Q9 A* M9 i# f" @
foreach($_file as $file) {& c! O4 _4 v& Q# {# ?( `: f
$ W1 D* C2 M4 d. u0 G" z* g" B+ o2 A" w
$this->file = $file['tmp_name'];
$ c& n: V( a+ o) R* c+ R 5 g! g; y" q4 y( A- G' l
" R- [; k- d% b8 s5 p4 \! Q $this->file_name = $file['name'];
9 L! p. c- t; R( Q/ b4 ~( \' t
4 x# t' f( G3 |5 H9 T
/ e+ b; A8 L. [9 t/ F% u, Q $this->file_size = $file['size'];
8 y) R! Z* D- K+ o- D* q " s* _* j! k K. _
! A" t$ ^$ d( g' X
$this->file_type = $file['type']; z& k2 [# d& I( I: b: b
2 h. t; Z( z/ \% P8 a4 z' L$ Y1 S$ |/ S+ s1 e
$this->file_error = $file['error'];4 z# l% g- X# z6 ^% U
; h5 S: s# e: ^! T
) U: X$ a3 p0 _) Q3 t
& K9 V0 B* ^, ^
6 R3 w( v9 T5 [1 P7 u7 r% c8 V `3 ^4 u+ m0 i' D3 `
}9 h0 L" k8 _% D5 |* j4 a
6 U0 y; W: _+ @5 Q1 N. F) M. ~8 _' X) M/ `8 U
$this->userid = $_userid;2 `3 i+ `0 {9 s6 V; u0 {" h9 x: C
+ G2 V: h5 n: f; L9 O* Q4 B. i# V: s* M/ f6 s/ R$ m
$this->ext = file_ext($this->file_name);, f$ ]0 W% Q6 V- L: U' \
5 J3 b8 B: Y4 I4 r7 k' C5 X4 b
0 E: [- q3 T7 ]# r L. i $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];; ]8 D4 D. X* | l
% q$ Q5 U$ z2 ?/ Y
6 _1 d8 a6 J7 M% U( t* I $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
. [5 b7 a0 O4 A' [ 7 ]4 C5 p/ E/ N( c# v9 b# S: P
; k/ |# Y' l. `$ u6 q6 i5 I $this->savepath = $savepath;
' ?" E8 A5 b4 f+ _ `: d6 [6 ^" P* L & G6 _2 \9 z0 h0 ]- n
3 K r. W& j" y& f( p8 P; }. A
$this->savename = $savename;
. H$ }- S5 t4 b$ k+ g1 W3 a: Z. ?/ g
' ] p1 A$ b! P1 O y2 J# y; U
1 x" ^9 I& t* |2 p }}2 ~( P1 z8 X( L( M$ D
% @9 R) G! c8 ]8 L Z3 ]4 I0 Y3 r$ t7 r$ }1 M( i% @
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
* I0 I$ }6 a; X+ g 8 I8 g, k. a2 k( v+ A( o
! }4 q/ `1 [9 h& b
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
' O7 u+ ]# t" S3 V0 q) b
9 s/ T! z# c4 W1 Y
) V- A: `" M+ O! ]" z3 F $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.php4 I9 ^$ i+ l% F. {
0 A) T$ s8 W1 `
0 q% b3 w+ Q2 I; |! _/ {! i% l 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
: S3 K( ?# H3 A/ V5 t* E # q0 U: P+ I: Z$ O
& p; ~/ n0 H8 _( Y$ P x
1 Y& ^# X8 T( e; l0 ~. N $ w; o; L9 e& c( q
. E$ T/ R1 q) |( k/ i9 s5 I
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
" U2 ]1 q" E7 U. T. M8 f ! ~1 `# B# x* C+ b/ b
1 j1 P: t" y( {4 A: L. J1 k* V
<?phpclass upload {+ {. |2 i5 m/ K5 H8 i) ^0 M
, o" V6 T1 h5 Q' Q
) V4 K# s7 c E ]
function save() {
3 [0 c) a! n+ F9 V $ e- L) H$ G* a' L# ^ T
9 i2 Y6 x( }& a
include load('include.lang');/ O& P. ^, R4 ]
E3 q" |# W+ U7 n4 ]( d
8 o- j+ {! m; a5 s1 d% e if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');- I, M- Y+ h- B3 p! H9 K4 R
2 c, R: s2 X" i! M( ^- K6 w* }; q7 G6 W
" H5 N, ^* ?( x7 O
: H% a, K% N) [; K: h1 X g
3 j" m6 ?: H2 Q0 I. `- ^* z+ S8 Q* A: q0 p2 E
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');% h3 M& ~& L5 T7 h( { D$ g* L
- R% P( i7 X, c( m* m+ S2 ]0 |; V6 }0 Y2 B. _
$ u" m, c; D# l# M, k+ { z( T8 \5 }# s& S
?3 K. W% c* v+ d, a
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
) W Q( \ e7 G8 k
0 m4 x5 s9 ]9 y' M
0 @: I2 b- C( e8 G3 |# M# i; |
0 }4 O8 k1 j! V5 } 9 H7 O( [7 B7 G% s0 G
% R1 y- R) t! m$ r- x
$this->set_savepath($this->savepath);# P0 d- R% g k! M/ o7 L
( z7 q6 {3 I7 i9 h8 ` H, f/ p7 M7 f$ x+ S; Z
$this->set_savename($this->savename);
1 S5 K- E* S% x4 j5 k ) P7 K' g6 b6 [2 N, ^2 H& h) Q
& G; r: p0 E" x5 k- e/ P
( I, T# ^. ]+ A0 W 8 h0 T0 d2 k, m
) F) }3 A8 P' L2 O/ u8 V" R$ O
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
: j D% W Z' T( L& o& n ' n% ], h, |$ L. i8 G# f3 ]
1 F: o6 a& l3 m) @3 C% p if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
) M- O, }9 k( |# a: _
. `0 A+ M t( G
& L6 Z; u2 d3 |; j: [! J" g if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);6 p# [5 k, u% |) x$ I5 c) T
" e7 k+ A3 P8 ^0 V; C% J& P
( r. Z3 A+ d4 M/ ^ 2 y& ]8 h' }! m5 Q3 k
. e! Y7 I! N. S4 }5 B N
$ R( @3 v, Q' P. }7 Z, i $this->image = $this->is_image();
, \9 {! [2 r z. M
" L( w, O8 {& k; i2 e# R
5 h! i) B/ N9 k+ g: Z if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
) d1 l9 \: o/ o. Y : [1 o" C8 _# n$ j; r' G1 B9 m
: G: Z. [, c0 \6 }# P Y5 m5 k return true;
* f3 |4 q: Y9 s8 T
) X2 u- h$ u% h: E
, o3 |" ^) u" j }}) m/ x2 p) ~& _. }2 k3 g5 ]
( R( C: o8 X$ z7 f) J# |/ a! @1 C
- q5 l5 j* q, b$ a/ F) y9 Y: A 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
, F. |! T, v; F8 s0 L8 B: i3 u
6 y8 t0 k2 W$ Z# w/ `6 s, F7 q# b0 O$ {# x0 ?0 i
<?php7 b. ?. @* Q' a3 r6 S
8 H/ S, z8 J n/ O% x( p( y9 i& k1 p/ ~$ r$ I" g
function is_allow() {) r, L5 Y% Z5 u3 x( ^9 T# n
7 i* a% P( W+ { r& v5 z' Y! w
4 b9 Q7 P6 F9 z4 c% W0 ^# s% G2 V
if(!$this->fileformat) return false;' J, B# R# [- y) e0 ^7 r
/ w- E2 N' z. Z2 A! t
' \- D1 w$ A; h) x. G9 g: c: w4 ]4 ~ if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;4 Z7 V, B/ ?8 x8 s' M# y( |. g
0 e* L1 ]- m% C7 |1 m1 ~' I/ [
8 d& d3 g' d# H$ ?/ K0 _- a; z 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;( c: r3 _5 a0 X4 u! h+ o
" d1 O: L7 y* }3 k# d# x1 J/ ~3 P# ~ f
return true;8 |: ^# z* A) m# P8 K
+ S$ e1 U; t) t F
3 H" R; F+ e2 U- b" \4 j }
5 V( g# K* N/ L3 O0 F( ] ' x& M9 F( k3 c2 g T6 m0 b
; j7 E* O/ H' y
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
* w# s9 n' p- [, I. H2 p% a% ` 9 G8 @% m9 m* _( o8 i0 r$ e
/ V0 d9 I8 Q: f: i+ r, c! O% b( w
接着会进行真正的保存。通过$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文件。. z2 n& U5 f9 U, d6 O( W- f4 B
8 a" q+ W& M8 o2 F' Z% X& Z
$ b$ ~, m5 x, e( [- L. @ ^
漏洞利用4 Q4 _3 |! Q8 V" K9 U
5 S3 A0 U1 D! g. c; R2 R X. f
4 g/ ~0 x) C, S 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。" Q" C/ M: L6 }- P3 D' M# b
" x3 G. F3 ]0 D. ~; U# a
& r& ?+ c& s) L* Q4 B f & r+ x' ~, G! V; G7 q8 p$ m
" }8 P) I+ }# d& S& @, U, J; y/ v6 v N8 q2 W% o8 F, i: N# W0 d: p
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid5 s1 S6 h- Y( m1 \! `+ t% U
. v9 O' f7 _( I* c# w
* {) K' ]+ N8 u( t3 R% } 不过实际利用上会有一定的限制。! ?# t3 g4 |4 v: b" u) A( Z
2 X& G% f6 _" o! I' \) |' M/ p" f
8 s3 F0 A. U( m: L
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。6 d8 \$ |! }3 P! z; j3 ^
$ `4 E) }" i' j9 J
0 @5 b) u1 T! i/ p; B! X2 @; ?
4 s2 f. H/ I' m& G1 } E6 a ?
y& F6 r2 C x) P- w4 z9 P
, v) |7 `/ i) {8 A 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:. I: v/ k) @9 G& ?7 Q# L8 d2 O7 q I
( F2 f+ z, A* ^" z7 w( K! p
+ }4 s- e$ K, T( A6 @8 b& ~
省略...$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]);省略...
- p7 n. j; T6 \- f1 f3 v, Z8 `1 ^
2 V: o( K# H3 Z2 S: {' X5 b/ u" q; ^0 ^( e. V
因此要利用成功就需要条件竞争了。
9 ^; U2 x; A' g- Q" D; w1 a1 | . [' t$ d7 g: [& z) C! g3 p
% e$ n4 ^& i K, V 补丁分析! I& `* r: w* o4 l- Q* L
) w) ]9 u! E0 e
7 a4 C8 M; G0 s& A# X' I $ m* w' X( C: B9 ]
4 u6 r! }! U- Y3 n
. Q9 ^, t5 z7 P" V8 J# H6 m 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:. C. J0 M& W! }7 P7 a0 ^& c& [
& v! T2 ^4 G. x+ |
" J( f9 f% I& G7 R' n function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}8 l4 O: `0 K. } ]+ P% [
( B0 a2 v. d" p {5 ~2 E/ P; Z+ ~5 \: u. N
+ e6 K+ |/ Y& t, F9 r9 K% v7 s, z
/ _3 q1 z, ?3 Z( Q) d. @ m# a) D
4 W5 j& ~$ u! t. }# ^2 @% Q 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。( J, v( Y6 c9 v& z. k9 F' ?2 a
8 v' s' ]1 C4 T$ }! Q5 b
9 O/ m; s8 p8 M$ A
在is_allow()中增加对$this->savename的二次检查。9 Y9 m8 C8 K- [6 M; T' L
2 B( R2 N0 O5 a; U$ s
6 Z2 P7 T" }; [- c- u 最后3 v% ^6 ?# y% \( V
3 i! b' U' c [' V( z; B. ?( A
4 Y" u" x2 M+ K- d6 Z
嘛,祝各位大师傅中秋快乐!
6 t* A, x. n0 f ~: r5 v
" S, Z; [" w! m7 [! K1 e
" H3 i0 ~' Z4 ~$ H1 t$ t) M& n 9 E3 r, t: q: w' \) `' q
# I7 s' L, N& f, L$ k$ a& W0 [
|