" [2 J8 k, C1 C( e3 ~, U
2 ]! [( H, U5 d) T. R! A; A, N
" r& @* `& U; l$ }; h: h9 H
; D9 u2 Q m7 |. T 前言
' T; p( L3 O- i* h2 z8 Y4 ]
4 ~& _9 S% G2 n$ N n% ^8 \6 ?6 q2 z) p8 @, Q' ^
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。" M8 Z, w# `; h( k0 E* z: A1 ]1 ~
+ G- a q1 ^; G. o
* q" @/ n0 O9 ]: e* \
; U! [0 R6 w2 I3 \; s ) |* f' B6 Q6 S1 j, J1 A
% k6 Q9 z/ o: l+ o) [* Q 漏洞分析
, G) G# k+ [) v c
2 M* Q: t* R: l" j8 [" L( e0 A
' b3 j; X4 V/ ~/ K/ D( E C 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
- ]: j" H( I: q9 Y
. T T0 c* _+ C* s4 T* E7 F: K' y4 L6 ~
# n% H3 ~) w' C% G& z
1 l$ R% [' ?! T$ y% f+ h+ Q+ y4 x: U0 M8 ~0 w' R
对应着avatar.inc.php代码如下:
' h$ o3 Z) F* I2 |2 M
' i9 P: V9 L: r1 K4 _
/ @* N" f7 y4 S: q, D, _) I9 P <?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 N, ?' q r0 H0 j3 M J3 v 1 h; m9 m+ o! `7 W1 ], _
. Y; I8 Z o6 h; X5 j case 'upload':
8 i4 X. P' M# c0 \ 7 o) ` B! I/ @8 M' h
6 `6 y) r: V v/ b9 K( }( ~
if(!$_FILES['file']['size']) {
" Y ~4 |9 U6 O; | # r0 P+ }5 P. t- w6 C! |0 h
( b, }# V! }; D5 s0 k- M
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);) M. A! ?( |' j! t. Q, J
/ J7 _, p$ f, s* W) x! d w4 h/ M5 O& |0 \: _) L
exit('{"error":1,"message":"Error FILE"}');
4 Z; D* {% X4 C& X / G. L$ U6 V. a9 O$ H6 i0 ?, o) Z
- I" _5 L! Q' _2 }2 ^
}2 k0 w6 W ?( n; t0 }' W; a0 r
7 L1 X/ e' x, O
# l) B! Y. G _+ c% m$ ` require DT_ROOT.'/include/upload.class.php';* K9 V1 ~$ R K, w: b; y
# |+ v+ M+ }, w- c
/ P. x+ a! K/ R) r$ u
9 f& a" @3 }/ i A ) S/ o- w* m+ ^
7 a; h& I( i7 [/ Q( r- {6 b2 A
$ext = file_ext($_FILES['file']['name']);. x7 S7 d% g$ z, d) _% R
0 E' T' n; v& w
# a* I' B# n% g% _% w5 C $name = 'avatar'.$_userid.'.'.$ext;
N/ O4 f7 [. O% r! p' Z- ?/ p- H - N: M( ?( o0 N. n. ]8 ^' C$ e! |
& ~( j4 ~! }# r' \ Y2 b& x1 [$ k $file = DT_ROOT.'/file/temp/'.$name;; L& S2 k) A) @) w0 O. ]! ?) c& X. ~
& [% e2 g7 i: `6 r8 l- n' s7 X9 k1 h- H4 `0 f% C3 X
: U* g$ N5 l1 C+ V* b! s
( f/ D( b# S O' @7 U6 J+ j& Z: I) Q6 q B9 a
if(is_file($file)) file_del($file);
9 M1 u: i' @% Q% n# }+ s
+ O0 h! ^3 {8 Z5 T5 V
. z9 T: ^4 Z0 `5 ]1 V" U1 T+ D) g $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
) h+ I8 H7 i+ @; J5 g& k / i7 i' f/ K5 n) U+ E) i) h8 {
; N( a$ P: } H ?' B3 ^ 9 k% z( g7 ^( o8 t
) A+ i6 }- h" b2 E. u% d; r5 D3 t) O3 I: n% k. h
$upload->adduserid = false;$ g. [ G* f) ~: X/ }
) }9 p: Y- V! J/ B
/ R, \$ m m3 o4 m+ f' R/ N
2 R( y# \4 R$ }! Z7 p% ~) v 0 c! }% w" H( K8 P3 `1 r9 ]
' o5 F2 I) ~/ U( N if($upload->save()) {
0 {! N u, {) a3 y- U5 T- z 4 X% |; N% u& k; r
8 t K7 L( E, O' l ...2 Y6 U1 f0 q7 B5 f' [
7 d' e( E# d1 h8 d
* r4 H3 } ?5 p4 @) y
} else {8 r4 C. X! {; {/ m/ [) y1 |
^: b8 T" H$ w
1 n$ f+ Z! @9 A7 B& L& Y# d ...: x# @4 d( P* {* r' t; o
5 m2 y; i. E# N s; N. T+ E- W/ W) W5 @& ^) N, Y% Z8 A! M2 v
}7 ?; p4 E8 [: P9 S4 x1 n% ^+ D$ b
, g, w5 u& L. p5 @! B9 \: r4 L1 [
break;
1 d2 O$ v2 m! ^! x
: ]+ r0 T9 g. F# T% L+ H
& J7 b( n ?2 J' p 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
; d1 [6 P u2 ]5 r% [ o
- L; m# b0 b: d& y; z, n6 A) Q0 P N; W( n% u
upload对象构造函数如下,include/upload.class.php:25:
- t( j" M$ w8 W3 ^) |- H* I A t- Z9 I, G: }! r1 ?3 u
$ ? ~( {! R4 J, p9 i# b
<?phpclass upload {! c% m3 k" [" o# N( X( i1 ^* J
2 n4 X* @8 C. D) R5 ^
8 f. c0 s2 I: S* b3 h function __construct($_file, $savepath, $savename = '', $fileformat = '') {- W# H' j$ S E2 B' T
' K/ d1 |, {5 s3 {" V5 x# o+ Z
4 N! o; x% S6 s) A7 l
global $DT, $_userid;: _4 Z# [& [% r4 p! S. B( m
7 N) ?+ |& }, p# B9 Q
3 W# t R- J4 C" Y* Y% L$ Y, C8 t
foreach($_file as $file) {9 a! V9 F m0 f( ?) [0 d
& N# W. E; {% B5 x- M8 r& T
$ y. ~% c% t/ a X0 J: N( A8 d $this->file = $file['tmp_name'];
* G' L1 ^: K, | ?5 ?! i/ j
3 z3 Z+ `8 t9 n: A& c
2 L3 G/ s1 t p $this->file_name = $file['name'];$ s- w- x+ V2 h
2 }3 G5 X5 N3 }+ B# C
4 z5 N+ P# @9 a5 B" J
$this->file_size = $file['size'];
* U( u* V/ d/ e
* v- O& |( H: D+ t! ~! v0 T1 b6 U6 J8 p3 |3 x# H
$this->file_type = $file['type'];$ X* O. h- c U& x' `) ]
* Y& C' u9 D, Y7 W3 W% V! \0 Q% y4 j# W& U/ ~; j; X, }
$this->file_error = $file['error'];
7 w S1 W4 s3 o' H0 G# x
8 o2 e. _! c& Z* _4 P, l' j& A8 I1 x3 k0 _
; L( A4 @% u% A, K/ v) r4 I+ q: E ( `; {3 c/ U! T$ y$ K! y# j
' n! i1 k1 }: B1 a, C& z& l }6 p/ }, ~5 {" R, d
: R" `' I& _! [, X
$ v9 j8 l z4 d# |9 m $this->userid = $_userid;
2 v. s. f7 Z2 B% z
: k+ O; y; ~: Q9 w5 E! {! V& i. d- P+ K# h
$this->ext = file_ext($this->file_name);
* o! [# c9 T$ Y9 y : S8 G) c- h$ k; q3 r: O
9 Z9 ]3 k% Z7 H6 t. v D; p $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];. Y& v. K* D( o& e* R
9 }2 i0 m/ N" Z
9 s* W/ ]& c8 x* t$ _- D $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
+ ] L0 h5 w9 N' u$ u6 _! L- d8 Z
; ~% g, O; f" J9 P1 d
# m2 a+ E4 X9 d3 a $this->savepath = $savepath;8 D2 |) e3 Q+ c: R$ n" Q6 o$ C
9 Z$ b) R' b) s% O
% F7 K6 ]* ?) { $this->savename = $savename;
6 G! z, ~" V7 a8 i5 _% p+ x
- G) M2 v D, L8 A! r$ a w# D6 J8 Z4 n) M. P
}}
$ s" t1 ? z, S0 T9 S# w# `; J6 J5 y& p : R( m3 |& F) J
' T7 G7 c5 o* f3 L6 n, {9 i 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
9 ~0 i* c& @% ^
9 P( n4 Q: `7 z3 n3 \4 b' a8 G+ H
+ }# f w2 _" D6 [) _ Z+ T 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
+ e3 Z2 P0 g0 t8 R# K5 m
4 k1 I' S) B5 W7 F6 t! u0 i
* h5 \9 i5 d7 `; J $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: G# l- n/ Y1 [1 g; g, b' y! p1 n
$ R; ^) [: S9 L* d a0 {
9 z2 d* ?+ Q/ b3 e# o- h
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:% t% [. ^# e) J3 h* n$ O
3 n, Q9 d& P$ T/ C5 l# l4 R2 y5 m
6 F8 ^" N) b3 w0 J9 O- K" H" R" H
7 c$ m, w& z- m( y7 ?. T0 a
3 V. Q# C( @( Y+ [- T$ J8 K: D* V+ x3 l1 z! z1 W+ b" c
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:$ f! {$ i9 c# U& Q; t% r8 Q4 U" u
! g3 @" p; S$ k# X9 k" W
( N3 X$ @# `" Q# i <?phpclass upload {
/ v1 F- y- ~) g0 h . L7 o& _5 Z2 q# L* M& T- A! [+ ^
6 `" J9 B: g, v7 N5 B
function save() {
* t! C+ Y$ P. q 0 H7 x6 p8 d! _$ }2 {
+ g. R& [# b8 n include load('include.lang');
& ?5 O& X/ g" g, R0 C& h: R
# _1 [- w8 C5 s( V5 C- \% `$ b& L" a6 X& N9 k: J
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
1 _( k3 Q+ o. @2 k6 L# w7 Y R7 A8 o. E2 K- G5 T
8 O; E* g2 h% F0 F: f 9 N" W0 L1 F7 s; @
# u: D6 x' D! e+ J+ K, [
8 t0 ~& z" I _ if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
- V, _9 [' N4 |6 c: p & M$ y3 }: J4 Q! {8 i( Q1 t
" Z1 x$ k0 t. z' f* f; q+ X) k * a U" J$ S# a3 A9 B6 ]
1 s; m+ ]7 P$ T& Q9 `$ S7 ^# ~' I% j8 \
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
/ o% P; V; p- y, a, k1 Z 7 J* j; T6 `- p* D, E
( ?/ W5 l% O! H* u
- w" z! L, N3 A # R3 I6 S/ O' H0 \( G
. O. p3 L% i" u9 L( M3 t) |
$this->set_savepath($this->savepath);4 F9 N8 M7 O r4 B# f8 W) K
9 [+ q0 W- F7 c- \
- L( v( S7 H6 u$ Y0 f $this->set_savename($this->savename); l) v# k4 v1 U% A3 g- Q, N/ |
/ _% z& ]. d/ S" C8 n+ G
+ L4 @' x$ O; O9 s, N3 d 3 X- N1 f1 {! o4 G! j
7 K/ ^. o0 P6 k; {2 j) T
9 L2 Z8 y- f9 q: f2 L& c' S; `+ R if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);( {7 W/ p7 M: D& B; \/ l# ^
! E6 }1 P$ d$ t2 ~( Q" g/ Q. o( I& B3 `
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);0 M% I( M) s1 u% ]# M
3 `7 j9 G/ W- o* e Q
' @( v6 r6 c& T! {+ Z! n& c if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
) l% r' c- c3 g# D4 { : }6 M0 c/ n8 z/ B1 }; y \. d& A
& X9 U2 }' w1 y+ {0 k
3 l/ J" ` u3 Z- D/ X5 m7 X * @ w) ^$ H1 u0 Y) M; M
0 a, C- P+ |! n7 P
$this->image = $this->is_image();# @5 Y+ I8 T% Q8 _) N
D& b9 E8 ~/ e8 e5 z; ~
( ^; m# \5 }# O; ^ if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
5 E1 N5 d, p; t
( e! V/ F- ]/ B! x y- @
8 G9 b# K% v# ?0 J8 E8 H' I return true;8 }5 ^( G. p9 P5 a+ n' K) p
' H9 U9 u' ?9 D# ]
8 ^, n* G' L+ q y
}}' g- \; k) e" X
( A$ ~, [8 y: P$ n5 ]3 Y( j' k8 p) U6 \ a t
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:1 K2 S+ C% v4 }' E
/ ?0 r0 i) H5 k* o( z' X0 F+ b/ z9 Q( \6 f5 s/ x
<?php% Q: ?. F2 B# {8 ]3 X s
* g) w% ?- i. Y2 e! z. y7 s
1 H$ Q" u) e3 [" f* h! l6 v, o function is_allow() {) G) _6 U6 f' L5 O7 ~- h
" n: ]# r% h/ y: D6 e. W) M( d& s1 [$ b2 I' Z) R$ Q) L4 D6 S
if(!$this->fileformat) return false;
/ t# L5 p# J1 a) O7 Y, }/ o+ [
/ {, _, w) B! s. q
; a& b' ~( o' b7 W& T+ K+ g if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;" e, y& L# a. R9 N d, N
- Y/ E8 [. @& t
* C6 ?/ \% @: m8 t- N$ `' {
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;( h8 F! z4 T' S9 F8 e' i
6 ]) r2 t0 Z' E3 T
% x7 H! b9 ^6 @! f/ q" S return true;! p4 O& M' R- Z, e6 ^& q
. q/ B) z* v! c2 h8 W6 N& }2 T+ J6 A
}
! z" ]) H! v5 J5 c! d0 V4 l7 Z , g3 b% V5 L; f3 v- M
* g9 K2 Z7 U& {0 j
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。0 l1 d0 Y# \+ ^3 |& `
A: z( D" m& X7 N8 b6 F6 O
( W7 j4 B# ^8 u. c6 @4 t. R
接着会进行真正的保存。通过$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文件。' m7 s* I; P2 a% |+ z
2 Y$ V/ g, o/ \% A1 |" } d) H
1 X1 q, Q0 V4 o8 u; P# o4 B* S 漏洞利用
i4 T9 D+ O/ k! w+ j# N- n+ f' c9 p7 E8 G7 h& }/ I; G8 y' N
1 U4 c6 ~: @- P" T
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。, a g6 I2 Z8 |
+ }3 b' R' ]5 N6 [. @% \- h- b, x% s V" a9 h% v. o5 V) D) `9 I
: c ?6 ^. t$ n( T- G( c5 a
6 B+ y7 [3 Y' d/ F. \9 T
. p& `/ H3 V/ M: |" x 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
( e4 D. a( R t" S. d+ | : R! K( w w4 ^, j
2 t2 ^& {" n' g0 a; r 不过实际利用上会有一定的限制。
, V( ~+ \% B) d, E% h2 ~
9 J% `8 k9 V" A% H* [9 q$ ~! y8 D+ E( [1 U- u9 n: I
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。+ E8 _" |! c. [, {- [4 k3 e$ P
8 v! e" A( e/ u2 F( p5 @9 Y9 |2 U# b# R
% c3 n4 I; \( u1 x( ?
! v' e# H" P0 ]3 G. Y7 |- |6 y8 H4 {3 @* }8 a
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:& h! m* }+ K# Y h+ E2 s3 G% \
9 i* a7 ~2 @! @& ^( A" u9 n/ J; Q& 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]);省略.../ y7 b" j* ^' t+ l3 U1 J' }' ?
) g. Y5 e8 n' i$ d8 a1 s: ~
! G, p4 S" I$ q6 ]" e6 X 因此要利用成功就需要条件竞争了。* D0 |# I2 B, I8 P+ O/ a% f t
( s( c1 U- L) L( m [# [& `& R
% G7 g. ?( S8 a, E! f! g, v
补丁分析% Y) v' N; e( w( k
; }, [1 S( o- H) l) [, u g# g
6 S, w. G8 V0 ^) r& F . Z) U) r2 A% a8 G+ E) [
1 w" F/ p0 T1 s g$ M& g: h: d
3 \/ c1 M/ k& k3 G2 ?7 l 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
! M+ f) Q+ _2 l- ?7 G. I% [6 E$ ]/ n # U# n) e$ a: d- b% T
. Q: R6 |; |0 r8 K% o
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}0 G2 a6 M" S \% l* C7 u
# n# [& L$ o/ Q( R( U' h3 H1 T# Y4 `
8 |7 `; ^" c7 r, h
8 n( r8 q. P* F) y! N1 Z% Z; h5 S. r5 W" M3 [
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
1 v( {: H4 J7 _6 ]; ^2 U' t ) K) I, I* D: m# m
; v$ l/ g# ]9 ~5 s3 c6 Q3 k
在is_allow()中增加对$this->savename的二次检查。' e) i7 s! k0 ]3 i
# J' u5 Q. P! X: l' |9 A7 L' e' S7 [+ C6 d! Y; F
最后) y1 j+ x5 F& z' j& G% h7 H* v
, G8 e. _: {/ @# Q
* a1 T+ B* [: ?. ^" A$ c: V
嘛,祝各位大师傅中秋快乐!5 V4 t( K( v7 ]
2 L3 |, m; S8 t" r
+ O( h4 Z* w! k2 M, I) b0 o( m
( P. `' r' P. F; F- {
% c G2 B; z! V. g
|