0 ?' P' A6 Y: Q
+ r! d" u/ G, M. H' ^. o! D; K- T! |3 P3 y4 B
" e1 y4 |" V9 }0 W3 ]$ M 前言3 \# e/ b* @7 C! E V. m) o! \
( f( t0 c1 M+ l$ k8 u7 T( [% f
. K6 e& \+ V+ R' M
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。) q( S' c, W0 {7 c
. H% Q( X* N2 L$ `7 d! X( o( X+ s* d+ L6 M7 k
4 l# V8 ^" L: Y7 b5 G
2 N/ e: M& j; @9 S, T6 \. Y/ z
4 P8 m: o9 g! f ~4 q- t; J 漏洞分析
% ]+ R+ C( a; G/ ]) t
1 A% L( m1 v* u0 q1 \: D [' Z' T; A6 I
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:6 d$ G5 l) S5 V( m# W9 U
6 `. B; J+ C4 ]1 M9 k; \6 G
6 W; R# l/ u" X
3 C: V4 B' \+ }
. u* j b' M2 x! x! y
% ^: z+ E4 N- [
对应着avatar.inc.php代码如下:
; E# |) ~9 T; G/ f6 P - [0 i6 g7 N, J/ N w; r
9 ~$ h4 k3 |: z <?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) {
: T- u3 B8 p5 ?1 `! Y
) F$ d0 w2 Z, S6 V) |9 I1 j
8 v/ N, L$ I0 b case 'upload':
% P( {4 m5 d# B, `0 f
, B* `: O% e+ A n9 D+ J4 x& T) h& Q( I0 L1 s/ {- P8 N% W
if(!$_FILES['file']['size']) {
/ B% g7 b* G! Y- D7 a / t- b* l. V8 M8 Z9 v- x! c v
6 r0 i1 a1 _2 x6 F+ ?, t: L" `1 U7 e
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);6 L) d+ I k% l0 C8 s
' e3 y4 Z; z0 i& A
: |! l9 U S: P* ` A
exit('{"error":1,"message":"Error FILE"}');+ Q! `( r# T: G; t, R! M% h' N
* N& [$ l0 I/ W# F
) a5 P) n+ j% N# _! ]; i" h4 |6 G }
3 ] s8 Y) q \ W+ l0 o5 M( K8 S2 G, O5 l
% g1 O; o7 z: j! T require DT_ROOT.'/include/upload.class.php';
7 P9 m' x$ `; z2 J6 w
- l' J& |7 ~! ^$ g* z& @( {
$ ]( q3 f: d+ {4 Y w; Z) x" f' j
/ Q$ W8 @; E8 w: T! c/ t
! {3 [: p: _7 ~- q9 p. E
+ t _$ u3 O+ X+ s' ]1 l $ext = file_ext($_FILES['file']['name']);- q0 ?8 ~, A/ J& t6 L# g
/ ]' Y; L; c7 F5 L" D* H5 o6 b7 F' O5 W- m6 l
$name = 'avatar'.$_userid.'.'.$ext;
1 }/ ?) c$ m* @# ^2 E P& ], ]: Y ! W7 v* o6 h5 g! Y7 ]
9 r& L, X4 Z& U! ?
$file = DT_ROOT.'/file/temp/'.$name;
7 U$ R f; q; W+ F# t+ M
2 Y) h; L; ~; f# B7 w2 ]
4 Z) V4 V& W1 n+ m# Z& B, T
" T! p- ~/ m, U O( E$ h * V5 T! G! I! i3 C* ?& T
) p a' r" G; M1 j! G1 o# t
if(is_file($file)) file_del($file);
& G, p A! C, D0 ^
- @' g9 r! f5 I g
$ s+ ?3 _, Q4 [! B$ V$ } $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');3 L; _& |3 J) B/ t/ ]9 P
' j5 ~0 o# S, Z/ T0 z3 J" x. Z0 \4 @8 m# z" J
& ]0 d8 _1 Q8 L5 j8 M) C) U/ ]# ?) u
) B5 n. G3 Q+ a, \9 i" n7 x
* a# K. r$ E8 K3 r- h
$upload->adduserid = false;- s4 ]( `% h" i- ?0 t
" `2 B: L2 q& w/ Z
! }/ B' y2 [+ b! c8 o; W1 U 7 }) g. |: U+ ?
$ P( ]# T" r$ N* V( y! U- i6 r
+ l7 K" |: s- Z4 M1 }. O# C
if($upload->save()) {: e8 n' Y2 a B* f6 C
v/ N6 t! k% [+ Q+ b2 D
% _8 \# u! u( B# S# n" w ...
$ g' O3 Z- I9 ^6 B( d8 I: L 5 }8 G; r8 t0 Y" y9 e3 G
4 j7 ~0 \# f* E, b7 ~ } else {7 f) _) b9 X- m9 i, i& u
4 O1 U' \) A( {
+ g l! Q8 F: P5 [* C# N ...! [) N- _" ?" l1 U+ z& o5 ^( Z3 F
* P9 b! C* s) H( S) ?7 f a# G) J+ f* a9 J& L( D; I$ j
}
9 u# {5 l. B2 d ^# s; H7 F. u! ?. G4 a
8 C7 Y7 [: s5 D1 R break;
+ ~# Q4 f o$ p6 e
5 e, D* w/ e! J6 \. ~' [
* i+ X6 r$ X4 s5 O' |9 c0 M 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
; Y# @( p, i" J: t4 S& k
% E4 n0 _0 i/ o6 k& y2 Q) ]9 C" y
+ F, ^$ \: x+ D upload对象构造函数如下,include/upload.class.php:25:. M2 V, N: m( X' t' ~) \
. h: l& i) a+ l
2 W( O+ c0 W" \6 |$ ~1 B5 T
<?phpclass upload {) n# z- J( p$ {5 S4 P' ~8 Z3 k
. f% P; Q9 @; [; d% @5 h
8 E2 g/ l4 j( U! M ?5 F. u
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
; I6 r5 \. m6 {( U
/ v; r3 r" W7 W+ N9 X3 o2 W% [. r1 U, `; z' P9 l
global $DT, $_userid;
+ p) D: y, e# @" [7 o - F2 r0 d( S- N
* t9 V# f6 U& _" y foreach($_file as $file) {
/ e) N3 \3 j2 U " {& S5 K3 x8 G( ~. \8 ^
* J( f8 K: D8 i' d $this->file = $file['tmp_name'];
( t h! Q5 {; X" ^9 `2 ^
; T8 Z7 N1 F) y: \* M# v
/ {( u( `, ~6 X $this->file_name = $file['name'];% C2 M. F- }+ H) o
0 H! B0 k* Y3 r/ z8 J, F- q
1 t! [4 J4 ]; d# U& K7 H $this->file_size = $file['size'];
$ o" X- f& d/ ?2 U4 p$ E) v + L( i3 P& s. [, X1 i2 Z, H
5 M$ w$ d( w3 w5 i $this->file_type = $file['type'];
% a. V5 Y3 B, M( \0 j) x3 z6 V
H4 E" }( [' l+ _1 ?0 h, r# w5 j& x5 }* n y2 e
$this->file_error = $file['error'];. \4 Y6 p8 n' m/ V( x* v$ w
3 c" T# m$ t: @6 M
5 H5 Q0 k( e4 T4 d ?* [" [* J : k, J* \6 T, [7 a* K/ G
& N. h# q" C6 T7 J
) S; W: c6 e& U6 r$ G6 U
}
7 Q3 Y" s5 V7 k! W$ W% P" A ! u: l/ `2 n& B( Q9 A
3 t, `! W! b: z% i$ `$ J# | $this->userid = $_userid;
6 q3 J; }" g A- E+ S( b' H
$ L3 ^, V' F8 t; {+ I/ _( C2 J5 e$ ]0 m& {/ P+ ?
$this->ext = file_ext($this->file_name);
& t" t; W4 p& E 7 q2 A0 V3 H0 k2 j# Q( `
+ V- f/ \2 A5 o/ d( N
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
. n% o( b3 R6 c% O* j" R& r; M' B" D % F$ A2 m; O5 F' J
' y* ?- ?* l- z5 h5 P
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;; b: {8 G4 I5 C3 @
# v8 I: h& f- W
& V1 V$ X- G5 |$ y& S% Y2 P) H6 v6 V
$this->savepath = $savepath;
& ^* u4 _7 [2 q6 t- e
9 _5 T+ `( R3 v/ S9 @9 ~/ J
2 {- B7 \: Q, t- d; x( x $this->savename = $savename;
& {2 R% a, q! [( _: M% s
G) h" K1 q6 Y0 `: g
& D/ C; T B( w5 @) `" g* ?/ l }}
# r/ `% ]$ p0 h " ^- a0 [: D. Y& V; d
9 Y" m* A8 [0 [ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。4 G. L3 I9 e, ]9 L! V6 \) J
$ x! z( v% f# Z
6 l3 C, ]3 u/ G2 V) [ 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 - ]9 I3 G* N8 v7 A
( y7 R9 K9 J9 A. |1 u$ H {
- H$ U. v2 v# ~- j& D- C $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* `1 w& p% x4 t/ @/ N
: f% |0 o4 [9 o" I+ v7 \- u' l& F _: N% Z! h
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
1 _' E d) o# i7 S9 K# N
! r/ i, T* L: h/ {9 L0 e
' ?5 N. I$ Q* [- L: [ . K- J4 X8 ?6 j
& x0 c' Z2 w. p& _9 U/ t! P: q% {; @4 L3 `9 w" {
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
( G& I0 t6 T" D) v0 s2 x - v; g& L* s! O- z" U
6 P% E i$ {% }; S) j% y <?phpclass upload {5 A5 C0 Z4 P) w8 h4 L
5 ~$ Q, T$ t4 _% o
5 N$ d/ i/ D' T+ A) C function save() {( f g! ~5 T, V- W, K
2 h. ? F" |+ q3 R7 {: q2 x
( m D2 Y, L1 T, w8 K- L include load('include.lang');
5 c1 O6 c* H% } h* W* z2 z / u) {, Y8 o: w% ^1 r/ q
2 ^5 q( G9 v3 W# h/ A9 D if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
3 Z- _( ] R: ^8 V; l& x
) J; w* @; M" G5 }' z. ]) _/ H! {+ P1 W2 k3 i
! \. z6 y9 L: h% v6 ^" W! }
! x0 r0 C6 h% {; G6 p: q9 t1 p
: C# C1 M2 W, M% z6 L1 i if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
# q( J8 w4 p! N" J
3 d; c# \/ L$ W$ [2 {0 c* m8 d O$ m0 O! R, u* Z
0 V* F: N& i4 u( W
8 K" c @5 M. k% G! K6 g: q% S1 U7 C* |5 `& r! w" ?- S
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);& M2 t Q2 m. a/ j" S a
: i& D8 h4 {) M1 s$ z8 y
8 }4 u" q* D+ H/ U
! b, t4 u* G$ Z2 E9 k
; o1 o# E' Z" I% {% u( r
# R, K5 Y } b( e/ a5 Y- s $this->set_savepath($this->savepath);1 Z) r+ F" ~: I0 M1 M
7 ^( t* m, B2 i$ m) @' p6 g5 i3 I
$this->set_savename($this->savename);( ]5 p' j1 H/ {6 v2 j
1 w* H P- G" x- k* s
4 y) K4 j% B' d5 s0 A5 E
+ @$ ?/ G6 j# h% j
+ \6 M% n: V! J3 Y" E* N7 n9 G' w3 S( P
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);; o/ U! k* w2 O6 Z
2 R' |* B& u' U# e; y* L, F
2 V2 K1 j7 S3 L4 P" G9 ]5 [ if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);% ?2 e, L+ p, b; u) N# z# N) R
+ ^8 T" C" {1 ^7 S6 v# k+ C8 {( N# c! i
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
z0 q6 d; M* b8 o3 D 8 \* G" ^/ } ~% V
. `" P b" K* e8 m4 n. H3 L+ n * H: z8 W7 y2 p3 j/ a7 y S
; G I& e! Y0 q e
, `+ B! s z" ?' a5 K $this->image = $this->is_image();
/ m) Z& }2 N3 Z4 Q 9 {# H: E8 k4 m; a n# b6 |, U
3 m( S9 I x# I* b$ D
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);0 @) W/ o S6 [8 W. w$ O
* B. a: g/ v. L
' S, @$ W" k/ Z' A7 z
return true;
& T' \* [; O- l% F: u, T
' k5 Y6 h0 Q, R( u2 T
" R) Y, `; w( d) @3 M/ a }}
' M- e; a8 Y1 C5 W, W3 L8 E
' a* T! q6 d* \4 Z7 Q9 V( t6 y2 i3 J O$ C
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:$ D+ {$ t j2 N n& n8 X* @3 I V
' d3 c& t& J' @- \* j0 m7 K+ x& E& V. i3 L$ r: H/ K2 j
<?php
- B( O8 `+ T( v9 M; n6 r/ y* Q9 e6 ]% E
( _; J6 a! E' t! d2 |0 \( ~# ^1 g+ R$ }* b
function is_allow() {
; y! A& q6 [( N1 W- Y " R4 b4 p3 j. s
! r; o R$ x& _6 M3 ]2 y" E if(!$this->fileformat) return false;6 ^& m2 a6 p# c' W+ v! Z: ^
1 s8 w2 A) S- k: X+ D5 @5 `5 P- R+ i, Y9 V6 }% k
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
) J1 _4 Q, q1 s+ X6 t# i 7 F2 c* ?# l9 t) A9 L
/ P$ p' ?+ e# W5 {
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;3 `( s+ ]; z# L! W9 b, u7 P
& Y J% x1 i4 _0 p' |! I; y ]" l! p4 M8 z9 _
return true;
* y0 i: w) a% T& v+ a5 q
# D. \# u, F3 _8 k
" h! Z, d3 w0 I8 W6 f8 w }- ]- ?$ i' H8 ?; S7 S* G8 W' ^
3 k0 u* `3 ?' N |4 Y2 I
& {7 M) W5 B* E1 f) k 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
2 X+ `# \, J5 A i; Z; J. | . }1 U! G: n8 W( S$ M0 S. Z/ P' G
5 d7 _6 J! L3 h. `; |- v
接着会进行真正的保存。通过$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文件。, M8 z+ \ k, K' T1 o
9 o Y' Y7 Y* r9 ^; s0 ]/ P+ F
2 u& r) D: B( n) H. o0 x9 Y 漏洞利用
+ |2 q# p: O/ r7 r- k0 u3 W& I
6 ]& }, d) |. G! a* l# d9 p& V+ X8 `$ X5 i3 N! L9 P. p4 j9 a* C8 Z" j
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
, \# ]/ W5 d8 Q W3 g V
4 d: }, `, k6 `1 D$ a$ F8 o( S, Y! b% w# p, y8 k
4 d( m8 x# n* X% r) K Y) I% D
5 C1 e2 S2 C( K' j$ e% B* u3 N) z& j. j$ T5 e" Y/ q
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
3 p# u( r; L& A2 `& z/ q. W* K ' w& Z" `% b k# l, }- Z# E" s
7 D/ H# O+ F) J3 p1 B- N
不过实际利用上会有一定的限制。
4 K1 _$ @( t7 f+ D2 w+ U( D7 y ; q4 h/ s: j3 b: S3 B$ M( f- ~
: [& k: \+ a: n4 t
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
9 T" W) R! h# \/ l- X' }8 B) S
% m& D1 T! d, \8 a, n4 Z9 ?
9 ~* ?8 _5 s6 s$ K
! E' J7 m2 ` C2 x+ d$ @3 C4 k 4 q4 y1 b6 T. {& w, t
/ u" D; {1 c" T: [) ]" U& ~, h 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
8 J5 _6 ?' \. P3 Y; V3 a4 Z0 ]
+ M# i/ e; C z4 f
! H# p1 _, O3 B* E 省略...$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+ @. l! P, w G! e
# _$ Q$ g, V! j* O* D" Y( O1 d# j' j7 K
0 r. M4 w+ a) f* H( N
因此要利用成功就需要条件竞争了。
' a# S. o9 l) a0 p6 | C- t 1 p" T4 V- @5 k# T( f( F
3 }" K A8 U9 Z. l# ~5 I' E
补丁分析
2 s: F: }# n2 Q" l( a+ e
! L9 J7 m, o( q" Z$ R5 r# T" ?8 W! u( X
7 O0 n% v& R4 [% R' a0 b4 P ( Q4 m. }6 r% d
; Z- k$ o! @- O1 x0 _$ | 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
^+ I: h9 R/ Z/ E
4 j4 M+ Q+ J: B& Q2 ~# q9 I4 E7 m; z- e p% p) f
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
8 `7 \3 x7 D" C; S5 H, A 4 y* I5 Z ]# _. c
+ @& |' n6 m$ j / Q) g4 [3 V3 {4 X( Z: y
2 t% _6 o4 T/ ]1 Y
- h# z4 ?9 M2 ] W& g# G 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。# `3 T/ E% l1 ?+ m% M, m6 c
8 c/ U3 L0 P4 |8 J ^; N9 v# \9 a' u7 r b! l
在is_allow()中增加对$this->savename的二次检查。$ B! S+ q. Y$ }8 ^
+ S9 W9 e/ }' Y. }1 _& y9 v
" a7 k: h9 t7 V 最后
' h) x: p( S. d+ E( K' `5 \. i4 f" g
2 \% L7 w4 e) L- m" u4 B! i
嘛,祝各位大师傅中秋快乐!! Z" s. k3 l. q
. K6 A. K( L3 G B
6 \; U. ]2 a5 |: X5 t D ( t. V4 v l4 X. S, ?7 z
* p0 P' `4 f( u, {- R
|