9 a# a% F1 p! ~9 r; n& V+ g$ j$ Q% y9 p; }3 O
* G. @& K& i0 X# O
6 |9 G W# N4 o' j) j2 b
前言1 D- }( m. l: I: Q2 X5 K$ r+ o) ^3 ~
1 ^( K+ n6 A; c7 Q! v: \3 O
- b$ P9 q* J/ `/ h! s 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。5 c! f( S4 P" z9 [& U0 A
/ w- F- s8 D: @/ I" `( H, A- \+ Q
( N- c% F- j4 \. B$ ` W, A( @ ( u( M9 y/ t/ Q0 r; e( a2 }: n
( N4 q5 W. l7 v+ a5 e
1 F9 e0 u; W: x" G 漏洞分析8 P% P( H5 `! M6 Y
3 S6 `" Y4 A9 ^& w w
, g* u' Q! \3 _6 |1 h# o; [3 \ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
: H6 W, y5 ]3 T" J. s / o7 d6 |* ^ L4 i1 V4 X4 B
0 [! J6 L) a2 ]& _4 a 3 F; q) i7 X6 g1 {+ Y& Q+ S3 g
7 Y: T1 ~9 Y" D6 t5 [( k4 `# q% l" ~7 p* B+ V7 J/ l
对应着avatar.inc.php代码如下:
4 ^% M$ d" S4 T* G5 M 3 e: y- O5 J0 y1 Z* Y5 O6 ~
: ]: P8 m3 D5 f) w4 O# T# 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) {- _$ p' S/ O/ ~0 l
; S* E, E/ }& I" C
: J$ _' J) z; H$ `6 Z" R( | case 'upload':
1 A! A: B% [- s; H2 @" G! g ( _& O: h3 t% M
& l) w2 s+ [- e2 g2 { if(!$_FILES['file']['size']) {
9 g9 e/ U @' l% ?, T* Z# o
/ w1 a0 g' Y8 e5 ^' v! U
% j* u, ]) a! L* c9 x$ r; L if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
# G) H5 X& V7 \$ u6 D: ~+ s* E/ P+ ? 3 o: |; a$ a$ O2 b' c
; w# F: Q/ g% }* e; t5 {: z exit('{"error":1,"message":"Error FILE"}');
* e+ D* v' w4 ~ % _3 f- Y7 L* ^ _. O9 Z$ D( w
( R# e$ [; B% }4 `: {- Z }6 n: |1 \( D/ @. g
6 W9 o0 B Z7 w/ Z% \1 ^
# V- F9 a1 z* V6 L
require DT_ROOT.'/include/upload.class.php';
: b$ E( K) Z9 E& t& ^; }, Z: p* ~7 b4 h
; o" o% M5 O7 t4 ~5 H+ ~/ f
" w4 p( y) F# q , t& o a5 [0 z2 K
" @* m' G1 M+ G6 P* }: i
9 f( e* W" E( v3 q $ext = file_ext($_FILES['file']['name']);2 C: e, O0 U! \- l, [: Y9 i
$ X# U8 V' E8 K1 B, v5 z: c; p8 P3 U7 `7 W
$name = 'avatar'.$_userid.'.'.$ext;
3 @: s% H# H7 M " L# m1 H9 l9 o. j
6 [4 ]$ r! A b+ P
$file = DT_ROOT.'/file/temp/'.$name;
/ q0 y1 s0 E" s1 a
3 R4 k7 Y1 }: N5 W
9 @* d! Q K& G
7 B9 h/ W' a! j6 s& c
% g# j* p1 z0 {6 N7 Z
# b8 L2 ^0 |6 A if(is_file($file)) file_del($file);
0 n: I" I& o4 `0 y3 f$ H . M2 B9 U- o Z, \8 n) `! Y
5 o" `# L! i" z+ L1 f2 Z9 i
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');# w( w9 `# V' s( r0 j/ V
& Q, j# x' o2 K% f. E- {
2 q/ h$ }' v" ?
+ B- x) E& q! j. ^9 c % @) e6 z" U1 K. L- j1 h
8 m' N6 Q6 S) d/ _4 G( Y% n% e $upload->adduserid = false;
$ f& [. a$ m+ U4 }# ^, K( g! j. O, @ % j# H3 V0 h7 c6 A
& ~2 e N; ?# X" R8 q5 i: \4 d: B ) k, Q' L- A O X6 _$ |
3 ^- O' [# X2 x6 j# R% ^1 z- J1 i! G' H6 p& D
if($upload->save()) {
- [! t7 W0 X* l/ Q6 v# s
* L2 K% ?( X8 j7 K
5 E4 x! p/ c+ ]) W- I, ~! j% Y ...
2 z6 l% E$ Y1 V% e% v
, V7 |$ h& K1 Y0 J' {6 W! N; M- G$ z* F$ V) K
} else {
2 F+ I& F8 p% I' W8 i
; f9 B9 z3 h+ d) M3 V2 y
; R( \( W* W5 D! J( S0 l( \ ...
4 r# _9 ^2 `2 o. F $ Q& `) N/ w" s8 V3 v
: S# Q; T4 A0 H8 `7 R }" j+ k Q4 [6 D2 [( S, H) K4 S% ]
3 T0 r7 \1 e* a7 y" w- p
0 j4 Y- a8 w" R* Q3 _ break;
2 \ J7 A1 Y' K: a5 A! p l1 X v, I' p, r% D% ?1 b# ^0 L9 s3 K7 `! s
) D: T. s# a) j& S! d! F 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。9 u+ l+ }# h% O. G5 o
( {- X& K+ H+ P- B+ Q9 V
: v' g5 p- y# g) f upload对象构造函数如下,include/upload.class.php:25:0 Q: Y4 o/ |+ n4 }' L0 C
D1 A7 m, o+ U) G8 L0 b" o+ T7 |9 }
<?phpclass upload {' m1 r0 e8 @3 |
# c9 `: j2 }6 u. R/ T/ v
7 b4 b. ?7 a# L a$ d9 r' x2 _* m/ V& [ function __construct($_file, $savepath, $savename = '', $fileformat = '') {
% X$ Y8 k4 [9 h7 U* ^3 r! ]
4 J1 r& v" p- M- b6 I" l1 Y
( S8 d( x6 I y- h( J' t+ R global $DT, $_userid;
0 ]) p5 X8 n) q% t5 g0 d $ @* f4 C! M3 ^! A9 d3 e
9 M) {% v- Z1 D8 v" D: {; f+ @% S foreach($_file as $file) {' B. T4 C) n+ `
# L+ K1 z+ C* b Y0 `: Z
& v. n3 i6 O: P* Z $this->file = $file['tmp_name'];4 y0 S# @+ D7 O
* w* D) {. l& i0 P8 D2 F6 _7 O' O
[8 o/ k6 T4 k" y1 k5 z $this->file_name = $file['name'];
( ~3 C" ^. A, Y \
8 n7 D/ U3 ~# L2 n+ m5 w& ]/ ?7 ~6 \2 Y3 K/ j7 ?1 z& ?
$this->file_size = $file['size'];4 g- M/ y# Y7 R$ x
6 I! v' Z/ k( o0 F% X& B9 P8 Z
& E% z6 A* g+ z $this->file_type = $file['type'];; j, l) h0 p! X
& A, c$ O/ I, l, Q! K2 M$ f
& i$ b3 F* `) @1 |9 a $this->file_error = $file['error'];
8 @: ?5 z1 [' I: I* ?1 p5 J; y " {" g. \, q6 s" C, g% T5 d# U
" R; B! o- g! W# k7 M3 S: |
+ U* }3 U% V2 B8 X |& d
$ L9 {5 |2 u+ a( G, }. G2 ?2 ]5 g: _+ p4 E2 u. _9 t. R1 T
}2 X7 T; |: ^" v) A# h' A
' k: U1 i; U" c0 G4 w/ f* e- a$ J. W$ x" t2 j. h2 b* j
$this->userid = $_userid;$ q: k5 v2 x! @6 }' U8 ~8 u: Y6 k
# M/ \, T; S" ?, \7 A& L& K5 B. b
3 `5 V c- K+ i2 T2 P( K3 p
$this->ext = file_ext($this->file_name);
# D6 O! B# I- U ]6 H ) T( e0 b" C4 T+ N, F. K
5 Q k, t" S# ~" j. T5 a
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];! Y: c) T: ] b+ W! t# x8 e
8 f: g6 l, M1 D+ ]+ w
5 q, G' k \3 L0 S; L$ [3 x $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;% i1 W' Y* }+ H3 y
. ^3 G; L& J2 h# G1 Q6 ^0 d4 f0 ]4 _! D1 t% W1 y# ]
$this->savepath = $savepath;
1 \3 A8 B) v# o* r 6 x% p+ O7 Q. z' i
5 {$ q: x6 u# x5 d! f6 j $this->savename = $savename;/ ]: B1 m# s* S% |: r/ @; o
+ g. x4 a, X1 ^8 P3 G/ y" D
' G. j5 N( X! s% B
}}
7 F3 V- \( H( A/ I
' |: e% T' e: {' P4 b# K+ V( P2 Y) F- o' n7 j3 a' B- _
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。1 Y5 ~, m2 f# S6 _& a
& t! @* q# R2 g, X; M8 i- n3 o% a4 x& i: M7 [7 }
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
. U1 a; I$ F8 n( u9 r3 T. y f9 m; B% {' |& h3 c' ]- ?- X
' A6 a3 Y# W$ T- ~
$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.php5 R# T* A5 N2 ^
% L: d) r$ Z- y! B4 u
3 Y0 P) ~$ z2 J 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:- m) S2 a8 b& A* U
; G t7 }+ }, }& h/ y/ D, ]) O, v/ Y3 H e5 z
" r1 w+ S) i y7 E& p2 k
8 S- ~3 U6 L; j( a* t0 W& u' U+ n+ b& F, }& a2 | z) [- z
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:: e: |5 J. r8 N5 h: U
* K$ a/ \, N; z1 D1 A) |, L1 a8 E4 {5 M0 A
<?phpclass upload {9 ^* t H2 n$ F% w: H
- U0 b0 ?9 U6 e8 P$ @0 y4 ^% \
6 S6 L2 n+ ^& ~" {7 I function save() {- c1 O) h) L+ j: M
* y. l1 i( b7 S0 o5 q7 Q6 ^, @
/ J6 L0 B# d" V) e/ n3 Q
include load('include.lang');
, z, |3 H( c; \- T, N # g/ i, c9 n/ ?* K; k
: f0 d* R- T2 \2 v1 \& F if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');- s- N1 N) t7 C. ]% l
3 [( g2 l2 ]+ h3 P6 ]( X
& C. m+ V2 m4 o5 F$ X
+ n* z; ^- c m' X5 d
. [: o9 \; Y: l/ `1 I/ X0 \7 g1 h) q0 f
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
3 |8 C% u, @( ^" ` ; y* u# R# `, l& o, q7 F4 [5 O: S
- p6 R m6 u+ [2 D4 j# @' y; S
" K" L s: n. ~/ h4 k& r F: c
: X4 h9 A7 w7 T* o) L( \) z( P* W
& x& Y: _/ O0 J$ b* _. ]
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);2 H$ T+ Q: }. y0 K5 k& W7 M" _
% X& l6 k+ {/ H n: w+ g" j* y \0 E9 r. v O$ U/ O
2 u3 t _4 z4 E4 _* v4 f
2 w7 N0 c$ P, R' X' a5 N: ^4 P- B8 |
- q9 z: x V8 j- E
$this->set_savepath($this->savepath);
* ?; j' s$ B0 u& ?. B , p( O; _4 T; n1 R9 S5 V# u; f
/ |: b. o) N8 ]! \' H$ O
$this->set_savename($this->savename);
/ u' s$ Z1 z& {; K
! n8 g" X9 T& u8 W" l! ?
+ Y- \$ }5 B+ f5 H& r 3 i+ i! O: i) ^. T. u; f
+ E k5 W& [$ {7 r
3 H- C' w' Z" O$ V$ T if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);/ v( i" x+ U0 o0 \
) j0 l* Z; }/ h- _) m, [
. L3 S% r* b" E* v( E- h3 x if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);% ]" ]2 A! ^# ?4 i+ [
$ D v( d; }6 A2 P
, h4 h @! q( U5 ~0 O- h4 l
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
$ }7 i! S' @) D# s ) T! c0 E- B0 g* ?& Y+ A
0 w& t f# _3 o. J$ K( t) p6 g4 w
, G! m& ~2 ^! S9 k; Q ; s0 z3 T. Y# t( l; m I. S
4 t, H% s' |' g9 B4 F6 q
$this->image = $this->is_image();
5 L% f U, [5 s' O6 A- |4 [* N
9 ?, c9 \' q( {. p f2 K* q5 w4 e5 P4 y; B( m5 T
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
8 o9 I: f' a! Z* K0 g& ^' b 6 f1 I' z. C* N; g- x! F
4 v. q( a6 ]% T7 L* n% Q return true;! i( U1 G$ O9 s3 x+ c! N
: f3 `0 s. f4 f' U' W: `
% ~) E( i# C: T0 S, q- |
}}
$ a/ q, |! x# ]8 Q
6 T# ]% |$ G5 }9 f) x ]& j- h0 c( w( |
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
5 n' B" O4 b- ]; J5 [ & y5 l* i% K4 {, a
. i" h: U/ @6 u7 o/ p1 U- q' n <?php
5 h( |7 x( a$ T: A4 [
8 j: D4 y+ x, _2 q- x9 N6 v8 v' }0 \, g v
function is_allow() {1 Z( R4 @0 p. f3 x! Q
7 U0 M3 R8 t- t
3 E3 m5 e* C+ W7 F$ h
if(!$this->fileformat) return false;$ ~6 @- c# ? r6 R4 ~
: V' R8 T1 ^1 j) s* ~" _
4 @4 T9 j& S. c5 D if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;* I4 K% Y: `, @% m
6 P/ ]0 T! e3 v0 ?! p
: N: k& S2 n( V. a7 a 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;
8 o6 z- i: A& R% @& L8 T( Z& g2 k2 P
; w- J- S9 c4 e5 m# g0 N, X, n) o* C0 i0 G+ o% o$ A w
return true;- t% H$ @# x, Q4 x
) n9 \8 i& M2 [" B& T
! r' C6 ]* x" \ d }
7 Z+ o& \" o% t- \. B : C" }8 @$ t8 G$ y/ b) |5 @- p( Y( r
2 h4 y' h" _ g0 ~2 [
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。+ y. [6 a1 u8 U; F7 U( D
M9 Q4 L% J' C! I5 e& [* C
8 |9 R& ?; K5 Y8 K% i9 l- l! \" ? 接着会进行真正的保存。通过$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文件。2 c& C; L; F( @* Z6 K
& l" z) }1 r- C E: n
- c( P2 \; A) P! g1 w 漏洞利用
4 k4 y! P5 S& z, x; `6 p/ D
: Z7 f3 z, `4 Q+ {0 K" {$ N4 @3 S% G
$ t5 C8 T! x+ O3 q$ R, C# H 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。: p& q. y# n g! |
; m4 i( f) [, v. e" q! Q, z1 v3 q
3 z7 t& c F; P9 G
5 b2 b# Q6 n3 M& }! ^" Y. c7 u* b, w
: k, o6 C; P0 m) } a5 P 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
) m# O; T6 F0 v: D$ D/ ] 7 S' p: O! E2 ^5 V. `6 P. V/ j L; S/ ~
" i' y0 K$ j2 P, `' j/ x 不过实际利用上会有一定的限制。' u% w% l5 S+ Y0 u- |' O, N
3 V2 C, J+ m3 Y1 ]6 D: E7 `
, M9 q! d# M( H 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
/ a) H4 n, S+ T8 ~' z/ i$ o ; Z* }+ D9 c0 X) ?
- {8 F! R/ F* B3 q. L
( x& J2 g; C6 F* e5 U6 e; ~
/ i% @5 c ^: f) G6 Q, F, _, s
" P/ `' M) A/ z4 V3 @, r) {7 o i 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
7 n/ m3 n! J6 B- b0 P; T @4 h7 X , u* e8 I) ^. T" f+ m ?( w
8 V' R* t, A: i. _$ N
省略...$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]);省略...
7 P x W% o9 x( n1 e6 e4 Q " q) L2 }+ L! ~* ~( N' U
3 ^8 B: j5 I% [- q( h0 W9 L. C5 ?
因此要利用成功就需要条件竞争了。- V8 x* D k, X% X
2 ?% y8 W3 G1 L6 ~4 M, l5 W/ g: Z: T) [0 l! ~5 g0 b" M# e
补丁分析
. W- ], E; d5 J, b5 p
8 C6 n- p0 h6 R0 r* @- G. L6 |* b q/ t
4 r/ i: x9 ~0 {5 E
9 k" _1 [+ q+ H5 T' g( B& R# R
0 S' m' z0 @9 @+ Z9 p' W9 \- l V 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
( u: B. Y9 Z2 F
) S. `4 w/ e/ Z4 V ]
" R& i2 J( h( o function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}1 \# ?7 w6 `# n7 d& i J$ U
% M; k' q8 }% n2 j, q
4 g+ Q7 c$ z& J
4 i* {* G. L! j/ I
$ T) n7 p' m+ H8 D A( @0 n
5 q6 E, l% m* X( | 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。2 l" X1 r( n; M9 z9 ?) h
5 @, f6 X" ]8 d1 }# T
1 t* h& V4 ~ s* p( u( \ 在is_allow()中增加对$this->savename的二次检查。
Y! x1 B) z. a( | / C* y# {( N5 X. v6 j
* Y! z0 y5 Y6 g% n; x
最后' F' J, ]$ z0 y
+ [( b" C* H0 O0 ^) L* g
& P' ]; J( a4 Z3 A' A1 v% w3 {: X; y
嘛,祝各位大师傅中秋快乐!
( L# S, ^* F; w* Q( d% d
# g0 M% p/ r6 K9 h/ e
/ a/ r2 u8 e* k! G1 y( F9 Z, N3 p$ _
. Z9 ~3 Q, z7 M * N& _4 T3 Z/ g% D& M( d$ s& T0 Q
|