- O# a& _% y7 j: }9 Q( Z: y1 e$ \3 K5 @
- c( M/ n3 E) c
6 O3 G+ q# l% L5 T# B7 Z
- c! W6 ?, b, X8 G( I! C1 V D 前言& u, i$ e+ g3 g9 x! Y+ l. ^9 G( o
- v5 z, j: p# g/ C1 |& E+ E% u0 Z) [( l% d* V& {3 v8 d
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。6 U, i% X* u1 m- X1 }# W$ q
7 O2 N, `& I* t, ~* [
. h- G1 x/ t: J0 j& G" ?5 x
1 _) c; z* I6 W
$ N$ C, P# h- f+ ~6 D. l/ A9 m' Y4 ?+ i" ]" Y; @0 k
漏洞分析
8 m+ _. P- a8 ? y7 m9 J* p
1 \; C' f+ r& {7 E! v- [
9 E5 H* N, E. Q2 ~ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:, Q, v4 L( I4 o- k
' C: ~9 C/ \7 z6 a: U( G
' Q9 d! P+ ~" N( g( y - n( ^% d* ], u
3 w3 `" O) m& w9 M: l, |" h8 W5 I( V; E2 F
对应着avatar.inc.php代码如下:
# \5 Y. d% U& N* O5 f& S / e( ^4 {2 ^# U3 j) s9 p" F
/ c5 z+ D1 m8 k2 C
<?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) {" J8 \4 G5 p) D" G; Y
2 ?' w5 L) \2 W/ t( M5 t. K9 _
: _6 H3 l. ~8 B+ S9 V case 'upload':: u- ?; F) U! |$ _/ y
4 g g& ?9 ~. n h1 ~9 H$ a
+ D$ m: X! D5 [. e5 ], _9 h. l% _. Q if(!$_FILES['file']['size']) {
6 y5 Q. V' A2 ~- }
! d; W4 ^+ i9 J y; f
' `9 Z" e# ~- `' j4 W. q if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
- u) q3 G6 h b1 M- ^* x9 S2 k7 K- ]* Z 2 l ?# _4 ], c
; o1 s: d! ], C3 w exit('{"error":1,"message":"Error FILE"}');
( c+ q% o! L! }" X# J) w
4 I: [( M; i- o% m6 ^2 d8 x
! H+ o0 y* b4 F- y- i }6 ?. u& ^, e0 s% x2 x/ q4 R
. ?3 Y" Z% x, s2 ~. k: p/ {
! z) T E7 t: [ |2 I5 w* S% g6 y" A require DT_ROOT.'/include/upload.class.php';
7 x B* ?" P( D& H" m
: t% `/ l& @' X+ d2 M) k' l% I
& ?1 t$ e1 N; s O( z
3 d* X7 U# b- e
5 T$ ^- M/ v T( A4 W# \8 }% K9 c2 R $ext = file_ext($_FILES['file']['name']);* x, i# m1 I. I7 c9 i1 r1 e
8 K2 h6 y" \; `2 U3 n! Z# C6 T( }+ g, E7 z8 z
$name = 'avatar'.$_userid.'.'.$ext;
1 n& `8 Z }$ P* L1 } . u2 a% Y% @2 a$ l1 g; o% {0 J( P/ t
. {' \% y+ { p $file = DT_ROOT.'/file/temp/'.$name;! z% X8 T9 @7 B( |% z k
: T( U% C' Y* N0 |
& P7 G2 p8 h5 z# n2 s4 i, {$ V1 r! o
- D9 Z: L( z9 ^+ ~6 K: [
2 R( O! Z0 o3 L; h4 W( `" a8 [7 _# H
if(is_file($file)) file_del($file);! O' c1 ?! q5 v2 S" {# b
" Y, z6 |- l0 M% R4 s
5 I$ ?* P6 S2 c: w, {+ w2 s $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
5 q, ] k# _% T, {5 F/ ? 4 s7 \, w- a! j2 u7 ?0 a
; @2 d8 r2 ^- A3 P) p7 Z/ D7 N) |
3 t( _9 F2 K- d
- @& `8 Q& F9 i5 f! |0 s( Z
2 A0 g, a$ r* ] $upload->adduserid = false;
$ [* V4 |+ s S7 ~3 ] 6 T: z8 m6 M( n! I* k2 }9 i% ~
0 }0 p, G+ H2 X! O7 C
" }1 a @6 G6 x
2 T/ r* W5 |0 w1 |* V9 P+ U; }6 r( p+ Y* ?- P" @
if($upload->save()) {3 k0 I1 Q" e( v4 p6 @ h; u, f
. _2 h6 I5 x' ~& x) `: K6 U z
# r* _, N. k, b- J! p ...
; g+ {( s% t! @/ Q; ?+ N . B, M- p7 [' C/ _
# X, q$ M/ h9 P2 n# d
} else {
# b5 c. G( F9 J+ d+ r/ w) F
/ \# o2 E }7 y' t* b4 m) @; h x# j4 K, q0 t8 J) W$ c# \ Q
...
: ~+ f9 X; C' ~" }' }( h% _; o8 a
( z7 N* A. d6 L0 I
4 ?7 Z/ _+ d# s* F, g2 d/ d# n }
, t& K+ q6 @7 R! y9 X" y, k5 ?' m8 e # R) p# D) P/ U7 T. S# A$ a
c! P4 m. ]7 Z break;
8 i* a( o8 W: E/ |, ^5 {
4 Z+ w+ Y" z$ m+ w! O! b3 N. s \& X# L
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。+ _) y1 D. L" B2 C* a& {2 @
8 U/ \4 H8 s+ f( m5 Q
e; @: s+ a- U2 e upload对象构造函数如下,include/upload.class.php:25:6 _' ?, c8 g6 F
% R ~% u) x& m) g* W$ `" f: Z" d
0 D& i$ B2 Q0 V) i5 a5 f/ E
<?phpclass upload {/ u+ b1 O+ x6 c' x8 E
" K4 O7 Z( j4 S1 E4 @/ Y! _* ~ J' _$ L7 V' y k
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
( `+ @# h0 o% s. p- d5 Q8 D , ~& b; I7 b4 v. u$ X' c; s) o1 A
2 y% {6 \; r+ v
global $DT, $_userid;
1 p4 E J7 L0 h9 U5 j' V9 `( \' \
* V. H _% o \) v( }7 t8 V* B1 C! N! ^: X
foreach($_file as $file) {
' X6 }3 M, {4 f5 B7 E 6 o" N& A+ H1 _6 I1 r& [- }
3 U: Q8 H6 d1 S, m) j' D; `
$this->file = $file['tmp_name'];8 Q; q R& s9 J+ X( a/ m
( K. W+ |4 v/ Y8 ~( n1 L6 K- V; C5 z% J
! K% C% ?, I2 z- B3 m1 _
$this->file_name = $file['name'];
" Z1 ^: F' Z$ c, y9 J3 g$ V ! t/ Q2 Q$ R4 [7 ^4 a% H$ b0 Z
! H1 j) D# Q, r9 a- s $this->file_size = $file['size'];
+ K q) y% r# j( p6 I
9 |* n; b" o6 R0 W H6 Q0 f8 m4 L$ l
$this->file_type = $file['type'];& Q/ o4 s9 Z' z
- u) _+ I1 x8 y6 q' J1 H* _* I1 j9 F
% R- y' p, S; l2 u/ S7 U $this->file_error = $file['error'];
8 R3 z Z- i# j" x5 s0 [8 l+ x: Q; y 2 C) Y4 j4 g& c; i
% E) \: C0 R! u: T# m" j
& |) I. H1 ^ j p
0 C( P* d2 R; ]; A5 b
; `% R$ K; E$ l+ S. I. p% q, \
} K& F* Y; g# {- }# {
: I- o& \! J: S* t- Z$ A1 J# c
" ~3 E3 z% K- Q7 ?% | $this->userid = $_userid;
! ?9 E, U3 C% q& c! b
1 U9 ^7 ^- ~9 V# Z b. C
- i, @$ I* E) ^2 W" \% d $this->ext = file_ext($this->file_name); \4 i" A! e! T0 f7 h( J6 ]
% h2 u8 ?2 m7 I' v
7 j6 f4 v5 E" ?$ E
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
, v6 E8 }6 E9 \
8 X0 s: _' Z; i: |( G2 c% |% Y# c- M* y, E
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
" _6 X- ~- r! W+ m- }. f0 f- L* p 5 E" ~" q5 _$ k7 m: A; S( X
. v+ H& o1 ~4 [' o
$this->savepath = $savepath;$ B0 Y8 X8 b9 b* [
8 l2 R g4 M6 J, f: E/ _4 M
& }" `( f; \+ h0 I% x/ j
$this->savename = $savename;
- A% e* Y& ] ]. X+ `
1 I2 g& Q2 `) d+ S; E) t7 k% v# q3 \1 P4 ~( x
}}
# s$ f- O0 ^) ~- ~3 Y Y ! d+ v* r1 y$ ?$ w1 c+ ]& y
8 u3 ?) L* ~7 f, c
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
7 H4 p& P6 z# z8 V ; L# D& ^! Z5 J
" I+ S# V4 s# v8 i3 B! d
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 , I; ]+ q; N+ b* b7 a: ]
X0 E' {0 N- F; x' D% n
u' Y+ F7 l% T. H4 e $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# w2 @* B5 l; p
! I1 z" u: b- Z8 f, }7 ^9 t: {
% ^$ O3 {7 B# k# B
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:& L. e' X: r" n1 I3 e I+ r" M
M. {9 R# K4 c+ z- j4 Q; g- j& g" J1 T2 @. Z& Q. L7 F
) D' ^/ v+ A9 b/ M
# I. q9 R6 f& R& q# I9 T n$ u0 m8 J& J% [1 r, r' P: P
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
2 T3 o/ {, m: [9 D3 j4 h! Z
2 w& c* ~% Z8 M [; `- j' r
6 o% l Z. ^9 }4 w$ D" ?4 e <?phpclass upload {
" f2 m; [5 t B: Q- y
& i& M( `: X5 s1 K0 m3 y. [
$ @/ d) |3 S4 t. C: W function save() {
& w" w$ L0 U/ c# p" B E6 b7 k0 A ! b5 A9 m) r- `' c
0 ~9 K6 p# Y6 [: U+ I; v
include load('include.lang');* o% ~ A4 A5 c% L& @
1 I, j$ S M0 D/ n/ ^8 ~
4 P9 R j; D5 x. ~
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
1 v' V& u& w- B4 |1 N
9 U; L4 A' i8 K
; Z1 u# e6 `4 M, W4 m
) m; C4 r z3 Q2 _' m
) f5 ?/ H @9 r7 R% P7 D/ o8 z" p6 L# Y
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 a6 ]' `# W; M3 r! Z0 O' @, `
. h) D3 T9 D2 F" ]. C" S0 y6 r
1 B7 j9 B: ~- P- g
5 Y2 C% U* c% u" c: U* C) C
2 z5 ?/ ?, \) t1 L/ Z
8 ~* G* w; O* x2 B& q% C& L if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
8 Z- J& y+ Y; V3 e ' Z1 |( p' b; c: f0 a
$ Y! |& ?* z& r. L4 u" l - @' B+ A- Q" f, y& q: u
2 Z( ~% x+ J8 V" `2 t
$ K3 q( l( w: z, ` $this->set_savepath($this->savepath);& v, p' M& G4 ]% V2 k6 C
. l5 v* f; n s2 i% U3 A
6 c( T7 }6 @6 s' Z $this->set_savename($this->savename);+ g! E) _+ z( F
) Z' ?; d, ~* a& S5 b" T" S# T
6 x$ c3 d, W% F5 n5 l
+ I0 j* V. t# G, j+ M2 k . z4 _2 }& e3 S8 g( H+ Y4 b
# }& G$ A0 Q5 ~ if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
2 ]) Y9 l( q7 u( E% z: `' i
" O+ R7 o( l# f
7 j; F! G9 y4 ]: z+ F if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) O' u5 n0 k* z
9 G6 `8 ]" |; f
0 z" M. _# t4 w: k if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
- t$ [% [. | C2 W3 H / R/ z0 |% h& i& E ]. \
! f; @: U1 q1 v1 S6 y/ [
6 i% Q* M# }+ F8 c- i ; r9 _1 b4 h' a* e& K
) X# A! e( b/ E; D! o
$this->image = $this->is_image();9 M" d$ W- L5 X# b9 C9 s
! p+ y1 T9 k M6 k
5 V2 Q- J% F) _) | if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);; Z4 y5 v" y" o7 Q$ [
q3 p9 n$ r! k0 G6 n/ O1 z
* E, h2 ]* g+ ~, [3 f4 i
return true;
+ J/ h* q t# i9 x1 j6 S8 v y/ t6 t& W5 D, c% ?/ Z. J5 v
) j/ Q1 ^& g) \0 y# j
}}
( \ r- q4 N( v
, q2 [1 M4 z2 Z8 o& ~! t0 |/ Z4 z6 ]9 x; p y0 { g3 z
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
' l* z# c$ Q% Y, z! F3 F6 D 1 ]5 Z! X. F& p" Y. L8 B
7 W# h5 e' M6 }) v <?php: U: f: o4 m; H0 Z" t* Y1 N
" x; K9 D. J0 Y% m3 W. z& c9 z, g3 o" Q+ n) h$ H
function is_allow() {
1 |0 d! @1 [4 c/ h; k8 p # @3 a4 Y* E- b8 T5 j
2 Q9 w, c; j7 Q8 g
if(!$this->fileformat) return false;5 U& L- N+ t9 L, z4 }5 d, {1 C* s
$ H9 A& y$ d% w: }; A I3 e; L. r N/ I
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
+ I! R, T% j4 g , r9 Y2 C( ]) D! h
8 v g$ ?* ]4 v
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; s5 [0 y, |) E0 g+ ]! p
: {8 v* c4 k* ?/ g3 t% y# |
K- B* v2 n& O% P* f$ A return true;/ V8 V$ z1 T5 k+ T s S
4 W0 K5 b9 }% l8 }# l& P3 A9 S/ {$ _3 \2 }* `
}* ?; [, j" q+ d! I
% O, \ r' W0 F, \8 ^# K% E7 Z1 \" [( p
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
9 d, D* f' W1 |, I2 F/ `
' ]% H- ~/ S# ?( m" E5 X8 x( X! y) P9 P- M8 Q& G5 ~0 |, x0 i* O/ K. 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文件。- I! K% o1 d. D, s: o& y0 Q& R
$ g, I1 C$ ~- V. a( q* P( Q+ Y# p$ W, Q5 j; ~; I$ M& d
漏洞利用9 c/ H4 R- \* A
# v0 b4 g! I2 `9 U& {
7 q! X" V- h8 u0 a% u/ q, m) N 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。: `- ]' T& H2 U: S* I4 P; t
, u( \* b8 r9 w2 W/ z) ?
0 Z( Q( }- z4 U1 e' B0 K 8 U& S3 ]' N# ?
0 Q' u" Z1 \6 M3 }
7 X# c) U6 w9 g9 Z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
( W: N$ e; {2 k' Y& r5 q/ U4 k
7 m- I/ E6 @1 J8 d4 A0 O& ^9 R$ g! \/ u+ R
不过实际利用上会有一定的限制。# d* @6 U7 D4 k* u* k
. g# c$ K% F9 M
* \' Z4 L' R# b" B' }3 R9 L4 u 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。1 W, H( ]* B* y
: o6 G9 Q2 Q) T) |* y+ r
2 | h- {- v: z2 m, Y# i. O
4 y6 P$ D' `: }4 V
G$ d( K9 u- i& X8 X% T3 x( H: a# m1 X% C
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
: {. k# c: H. y' \ & S+ ~. {6 R7 E) {2 F0 O" j
4 U6 t4 | V5 ?" ?3 k* L 省略...$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* H* l" q. m$ F/ Y) N4 x" N
, e7 k. W' P3 q2 R
1 C$ Y, E, p) u) r) S8 Y 因此要利用成功就需要条件竞争了。, A% w( y8 [; d
" |# `0 R0 H/ X& b6 B% p& }# j* M
4 i! e$ J# B. ?: [2 u) J
补丁分析$ a# u5 z$ N+ l$ k) S
! K& R1 a! M2 [$ C
0 q3 b; ]/ ~( P$ U
! Q+ C( h% U" {/ p u/ a8 f6 w
4 d t6 ^$ w0 B# y5 H) }, M; u6 k3 }2 p; {# q3 X
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
) u" o5 T3 {7 K1 k4 l- H7 B# r- y3 { - H8 ~6 u1 f5 m2 }1 r8 V! A
* R4 n" {0 B' h3 m) W4 o- A5 H function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}/ g; {9 N6 F s4 i
; C( W; Q& m! s+ A' \
, B+ ^, a4 F7 p7 r" V' Y
4 F* S1 Z2 X) l& k% T& D0 B$ w1 n& |! v
6 Q6 G3 F0 l: D! f! X8 g$ D- s1 I& b' f+ ?2 {/ ]' J
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
! |7 ?3 J8 f7 y0 g/ m- [7 G+ G5 u
_. b$ k! k- ^9 y) Y6 W. @, y/ s
6 U8 Q! X' [( y4 ~ 在is_allow()中增加对$this->savename的二次检查。
% c0 j, h0 v3 g- \; k 8 A7 |7 C3 P$ \8 z) x3 a- P
B0 E/ y3 p4 w
最后
. `/ W: p, j; o1 h) m* m2 M" `3 y: P \* L2 V& x5 J
/ R- [& H, w" g7 c* T+ G7 ~: E 嘛,祝各位大师傅中秋快乐!
; N/ I6 e- {. S8 l
% v9 E2 E# R( b" n+ F4 R% E6 ?% H& u3 P
% @; l& j1 t0 b# i5 J & F2 s8 x0 Z3 }) J) w
|