0 _- p) g% }. i! K6 W
0 r! S( r( J2 C2 X* z$ V r3 }+ I0 Y( W7 {
4 [, v, ]2 ]; I# `
前言
8 i5 \" a$ l: p; c6 V I6 W! u
1 V' K0 c$ [! p) [: P9 }: s2 B( E: D' E7 t
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。; ~# o* f' \$ @& L+ \8 `
2 C, S! a5 E9 E' N! v
, l* h% P6 e' K* F3 @/ k
' c9 ?- m5 L. O; T" P( w+ O0 M& P! y
/ u/ r9 F, [- W4 x S+ |2 K) `( s6 x( i, N* [8 x7 l
漏洞分析! Z4 F- T- J f8 a. H, [
7 a- v- W1 o' V" v
2 k& N; w' Y6 ]' \9 l
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
# g. d$ t% p0 t) z ?6 f
. g6 r. f& R a" G' Y/ J' y
( A+ V$ O% G6 T$ o* L# G3 } , s" W3 d* w& H( }1 H
4 J$ {( t" r2 `: u" |+ j. m# r
" A( l5 h9 l8 a& [7 L& @
对应着avatar.inc.php代码如下:7 {. E7 P( f& X0 |+ Z7 T' I( |8 a
- t D1 _' _( G+ F. O. v0 M' E$ T# s3 z' X& Z4 G4 g
<?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) {
& {/ V- v. n: f9 R8 `( L9 E0 Z 3 U2 J$ x; J+ s# Y! N
- M4 W- {0 T' u& {
case 'upload':
9 n& P( v6 @2 y) @ $ c; T N- ]% y) r! ?0 F7 B
" p4 d! ?/ |: U* y if(!$_FILES['file']['size']) {9 L: O$ ]+ V1 n# j: d F
3 V$ V7 M' A+ y1 i% t2 n
3 L& H& X( F% K+ s0 O, E if($DT_PC) dheader('?action=html&reload='.$DT_TIME);; n8 V5 [8 P0 F7 {! U( j8 k
: _ _* F$ t) Q
) u; |5 v& d, p( d7 w M4 M
exit('{"error":1,"message":"Error FILE"}');
" G$ }! A0 w3 o2 Q) G5 w, X, E+ h # x' J( G& u! C/ b0 S, K
. [2 c2 q; ]3 h( a# G7 D9 ]9 U
}9 G- }) ]. u$ q9 e/ d9 q, x* y' t
8 U4 z4 O2 c6 B& \
3 y7 ^; L1 n- W) f- H9 d require DT_ROOT.'/include/upload.class.php';1 t# H( r" D* {! Z) z& @
" S8 } n G: Z4 A) C
" U: [7 c. S1 V
1 U- r& d( O7 s$ U+ c
* ~; B( D' H8 K
8 `, G6 M n$ o, y6 v $ext = file_ext($_FILES['file']['name']);; a, Q1 b, `+ Y
2 Y/ N3 O- S& h1 t! |3 L, l
8 ~ F6 |! e, C& c $name = 'avatar'.$_userid.'.'.$ext;
) [) I7 U4 ?- F( O4 J
2 x ?, Y) b7 E0 v0 D6 l2 c0 |2 e1 n: f7 _+ V$ P8 j' Z6 V$ p) @! u- y% Z* z
$file = DT_ROOT.'/file/temp/'.$name;1 W9 O) ?) {+ R+ f
3 Y7 G: ]7 {5 d1 I
* D# W4 [: c1 k+ S8 e) T1 e0 j ( a/ `( }! q7 d) Y/ j
5 [7 {2 d3 l3 Q1 V: F! v" R: Q
& F8 d* r/ c3 q( R! w& R: _. W3 K4 F
if(is_file($file)) file_del($file);* R) i5 ]; ^$ H3 e% B
9 L( T5 k7 x+ @$ B: V8 L3 H. x: Q: n4 @5 [+ c! c7 E
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');8 V& `. V/ V3 A
/ m2 J, G; n- u( I; P8 a) }
8 {2 w3 Q1 a. z& ^ 5 V& a9 G$ E) F7 {8 O
' A# T3 w" e+ z) }. O
: M: K/ }8 k* x6 f4 q
$upload->adduserid = false;' S/ N% @3 e% p* U& j
/ B+ e% b/ V- B
# r: G, K% ~9 W) |+ k( k
; }# \% ~0 H! Z* t$ a
3 H& v2 f! p0 R+ J6 L3 Y3 R$ B
& k0 N) I5 O- x8 F( Q$ ~! ^! m5 ?1 ] if($upload->save()) {
" ` L. \3 `2 a; M! m % |3 o$ Y% Y' @
) s+ v& r+ e' ^- _* Q& z U
...
, c" f0 @" R5 p& T 2 v* Y0 d& f9 J
z4 `& H2 G i y } else {6 l2 x: Q6 J" T% V. k9 u
7 T' K2 \5 z( o( F T. S
6 Y% i7 n4 Z' x$ d5 {8 V: A& g ..." I- A6 k( N* z: u# J. Q! c
8 h7 v x6 r0 D R. B2 A
; Q5 _& K. q4 d. N7 S- r4 i }: M" f. {2 g/ p d. Z$ o
7 i$ c6 I' ~$ G- V7 _+ {$ c( h! }1 J
& ~+ t+ o9 Z* @# [9 L C% j! U
break;
: @4 i( _; |! K6 }- x4 y6 j
( |3 D# ?( Q$ b) |% U6 n+ V8 P/ c$ f, v7 Z/ p% K" y
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。2 b4 C& x; P2 z4 ^- T& K
, W$ A) @/ ~4 U3 q$ V& e4 D
" M4 H5 E1 A' y4 I. F upload对象构造函数如下,include/upload.class.php:25:* k+ O: y8 O2 F8 m' i) Q# A
. q" ^/ ~+ l) c( n) j
4 o4 S. D: e2 s2 ?5 y <?phpclass upload {# b1 P: I( p+ `$ R [9 G
- A6 m6 z7 I- y; i7 i3 v0 W j; a: E% [5 [0 S
function __construct($_file, $savepath, $savename = '', $fileformat = '') {6 ~1 u. N% h# i+ Q/ _
' G/ i! G5 p0 \3 Y) s' W
( B1 |; V& L0 q) d global $DT, $_userid;
. X/ |9 t- b: K0 w2 v; r/ {
$ }3 u! \- q3 `, h. F1 N* b7 S1 ?
$ R3 q, z7 S+ r+ K; k. D/ M foreach($_file as $file) {0 \. y( y8 Z: J% Q6 s
* O' [% j r: c* _
& O8 P1 C9 o8 V( o- }$ c% T
$this->file = $file['tmp_name'];
. s) u8 K# N' u; O9 ?) O# g " U) N6 g. q% |' s' e1 t% o
" S( z- ^: i; ]0 Q& ~ P0 T4 V/ {
$this->file_name = $file['name'];
2 C) U% N l( d0 L# D( m0 V5 { & k) m. W9 _, b6 F% g
$ R1 t2 ~7 {/ X0 i) D
$this->file_size = $file['size'];2 f, `; d% D- e0 [6 O5 L
. i8 g3 y% ^' F: _/ g# I
& F4 c E0 i) R $this->file_type = $file['type'];' V* X* u8 V4 U
6 L! g$ Z! _% c# ~1 @
8 F+ Y/ N7 B9 ]) K h7 _
$this->file_error = $file['error'];
9 z# H" P5 X7 \
1 @, [$ ` w2 {) j, [: e( i+ P% u9 n9 @* c$ L' s6 n
; A s) ~2 M$ D* y % B$ T, Z0 V; X; O# C
R" U' H0 q: H' b$ E# w
}. b* d+ }' U% a: ^/ E
" m/ Z h9 X- S) M
: ?1 I( p$ r A1 g: b( u $this->userid = $_userid;7 S G. K- s: D/ J
, s& V! @8 v- E8 [6 m" m. I* o, n, u0 g
$this->ext = file_ext($this->file_name);
" \5 S/ g. u) L& j
+ P9 |) q. b. U, r6 q8 B+ F f9 Z9 R
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];/ N2 ~. u0 V* r' P+ ~
# S/ t3 G O3 f5 K8 O/ x, r, K
6 h2 Y. ^% n3 z, o $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
7 A3 ]. k* P+ b0 O- _1 o
) @+ C. b; t; `9 `" E
2 J, f q9 [3 L; S+ ?! C8 d $this->savepath = $savepath;
$ o( {2 w u& ^8 w. h. x3 U / l$ P i3 w% e- @ F
- j. ]! h( K6 L' @+ J/ w+ N7 ?" E $this->savename = $savename;
2 D% n- ]2 |( ^0 g. r4 Z3 u) Q- E* o
6 l) y. N6 L- n* R$ ?' z4 Q0 q1 |+ M
& X' e4 ~! t2 n7 B7 Z }}% ^7 o0 L* w0 T) J
7 j0 ?) F1 z8 K5 s; m" G
: o( X, p P! C8 f, I
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
- B+ Z$ r1 z7 L/ F
, Z4 @' R* Y' y7 O7 j7 P6 y% a" ~
8 s: L9 x5 b+ l K: T 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 4 Y- t% C) w) A2 f" U) H2 o
8 c1 ]; v$ N4 H4 g& K, w0 p
* E# S- @: V8 n $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
! v4 }$ j6 n$ l8 Q1 { e: y0 d
7 T' T1 X# L" P" T# P6 R( E% T" b; ?' S* u* x. K' M* k
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
# Q- ?. r3 F. u' n# f * ?0 g, f0 e# A/ i
# S: f. }) `( J
4 R3 g; ?# n" z) D
+ @3 d) `/ m. y7 X- P3 y: D
7 a& y/ t& U9 C6 Y# j 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:' [/ _1 B! B( W7 h% `+ |2 D
+ ?. y1 g! [; y7 `" a2 _, i6 P
$ r3 W$ X* ^" h/ Q; r& i <?phpclass upload {4 p. I: Q2 R: ^+ v: u
7 f/ y% \$ a* Q' ~3 v
: K; x8 C Z P; W, i7 q7 ~, l function save() {7 V: X. f h# r/ g1 t
' o! w# ~' u+ @4 O( d# M
+ O' Z1 W% D' Q/ M, o include load('include.lang');4 @. u) E1 i- V5 i) e b
1 m% t7 J+ e- T c/ r+ \' ?7 {& }! P" j2 r
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
. w( R3 j' y+ M9 F" L/ V) \ * T( T+ c4 j0 e/ l' T' [4 o) W# H% S, g
' {$ E0 t' H/ u4 N8 D6 J0 ^ " |3 |8 l* L; |# c
! L9 e+ [3 P; x* F. z1 ^8 k" i+ L
5 q) @8 e( p3 E3 b& U1 R if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
7 Z8 a) G _" L/ j
5 [' `6 b; ?- |, v& U/ ^ u1 c8 m( J! m$ V8 t" O1 g0 G& V
! m1 R& p2 E# b7 Q0 x" l
% v% f# k s o
8 \& F8 @1 D8 X8 x if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
, \" v* P7 Z$ {
! C i9 e9 @" k2 ^0 d
" l7 Y8 |' R c8 [ u& A + o& I9 n6 @- p
( {& a* o# y$ W2 D& ]. l
& }- E2 R8 _, J7 p $this->set_savepath($this->savepath);
9 F3 v$ D! B7 ~, c
4 k! A) n R' H: _+ _3 {; G/ } c# q+ M( \" e
$this->set_savename($this->savename);3 L- S' S! S8 ^0 S G7 n+ u
0 D. {' r+ |2 M7 z2 {: O+ x- b* p4 l* {) ]
0 ^* n8 w/ @1 f
1 T6 G5 h4 L" [6 H/ H/ Q+ U/ u
2 |6 @( ^# M2 I( s if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);) X0 ^$ I% x, e$ y' O3 d- Z/ @. X
% {% H/ Z( P! L& e$ a4 X3 h8 ~
- v( r$ z2 u& a8 f2 u if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);6 c% O0 D# q4 X9 ~$ k
e1 [- c/ x4 ~" x5 i
6 \* D2 E f. b9 Q' L; j/ L
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
3 s, [0 }6 g7 A# B! K0 R( R, b
9 l% X0 P+ j6 _" \# d2 _
! k1 o9 o: y9 N, C- U# Q 2 z% |6 c8 G- A) u: C. D M( }' n
' y3 ~ K2 m( k7 A- p" e1 |
% ]& N- ]$ {, V! H" [) r* D) t $this->image = $this->is_image();0 t% x$ a7 a! ~$ ?0 E9 Z G
0 C5 A2 c% A9 ~) V: x& B% `8 ]3 R7 a5 o# k$ N' Z" p' c( Y( Q
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
# b( W+ d1 j8 h
% M0 E& u# |: Q; ~0 T' `* c2 D: G/ l* c/ {. @
return true;# N7 B8 Y6 e, s9 Y( |# o, D1 s7 g/ O
$ U. T; p# [$ Z8 f e: m7 g6 R- L, s
3 p X8 M) l% v& N }}
* Z8 F @. y9 s0 ]/ ^ ' n2 ^$ E8 t- m( a
& K- m' b7 B/ M. v( B3 k8 m S 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
3 A4 `9 ` k1 W) l2 t
* ^- x! Y* d4 V9 _) q. _0 r; w; [4 `) V' P
<?php* @$ v9 @* V# q. \1 P
, R4 w& S( a" u y# R; @! o
; u; k5 U( B) ?$ ~ function is_allow() {
- Y& q1 J2 U0 Y$ P1 P) T4 i, t* c. o # T6 S% J Z- \ Q# j8 J1 Y5 N) c: E
, f0 ~- x. T- c+ v$ o
if(!$this->fileformat) return false;; ^+ m9 G7 \+ [1 m. F9 g6 y6 F
5 ?7 G8 G9 u. d; M' J, k" g+ n1 K: `2 k5 C7 u, k$ Q( P
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;# {: o+ A+ F: z
5 Z4 h% L( t' a" ]
u; G; x/ W2 i( e. K2 i' Y 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;
, M+ J2 `6 _9 c! Y9 I8 _9 l- I 5 `8 c; t" Q) ~5 M. k0 s- i
- {; ~; K3 ]! [/ U C+ a
return true;
( J0 { @' M2 I0 |) T/ o : [& a3 _& G9 |( O2 L
2 Z. k" N- K$ T4 N! r. X2 A4 J }
& D9 x" n& d* E1 y% d
" b. g, Z" n( l% F( B8 K
! o- S* L( l8 J! a 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。 r0 N( V$ J/ V" |5 x8 P
$ i. `$ e- W8 M1 v* |6 Y* L6 j+ w F& `" b a7 S
接着会进行真正的保存。通过$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文件。
! b3 N- U r2 H) Z1 r I) J
! c. ^, B2 t& s' v6 f* b+ A( G$ g
漏洞利用 g7 k$ s7 A+ A
* u/ D6 C- X- z: r0 k4 N7 ?0 u
: O7 ]) F* i, q$ M 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。$ r4 ?) o8 L6 u
2 l8 T, `/ B1 G, D" } P. t& L
8 B$ [6 h+ x, x3 ~4 ?( q$ b
& e. E& ~6 t6 _- @
- {6 q, u4 I% J+ `! ]" K* A) |- D y7 E* ` Z5 t2 o8 e% O
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid% Q) ~; C( c# h k' E1 i% I8 J0 U
% u% i5 n' z' v U+ n2 z& n7 W" z3 w/ L# b( K* b5 X. |8 [9 [% w0 \
不过实际利用上会有一定的限制。
& |" `" n5 S6 L! B- ~8 [- ? 9 Q+ \8 g$ o7 t, N. o
$ ~9 `& Z& t) f* ?3 t 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。% o7 q2 ^. T# i+ n! G0 Q6 h2 W
8 v7 e1 K/ l; v* F5 }1 R M' S* ^. G7 L+ s
/ L! B# F+ @4 K9 [! N: H* O
& x M( g& l! f9 ^. P7 \
/ a. t$ k% s6 Y& v w* u 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
, z" y; o- J- F# K, Q1 a- L
% f7 p4 |& S0 e: _' d
4 `3 A! S8 \8 u9 b* P% H 省略...$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]);省略...6 [ _& b/ u/ B% [5 Q
7 Z1 d- Y/ ]1 g+ |
8 h7 a* e/ a% H: B$ {4 }9 {5 C% n 因此要利用成功就需要条件竞争了。
q4 O& }+ o* y
6 k8 J" y' W8 A P- B6 y
# p2 I# n! {: |0 W 补丁分析
$ d8 K# P A% L7 P
b9 L, A4 {2 i1 Z# h% ?; e
5 V- O# j. @" N
( o( M8 D1 o6 m7 u0 W/ V
! X2 I: W5 K# g& o" P$ l* E+ |( f' d$ ~3 ^! W" i
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:1 ?" ?6 ~& P) b" X0 I3 l# ~
/ i1 W. ?6 B5 \5 d2 Z, z
/ Y1 D/ x. |4 P; s) R; b S" z function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
; _5 j2 {5 v$ h$ \$ ~6 k: K& |
& w3 B, G" y! `: N7 _* b! }7 `6 x% q2 P6 u, A
6 T: R% W) }" I" w4 p
" r; |$ A8 ? C5 ?: ]* J( c7 ~
( q3 y1 F% s/ n/ `! p* A8 t* G 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。7 K D' e$ b% O3 R9 z- k' F$ H
7 Q. v+ ?, P) ]/ h8 G
1 G2 B' H- r% t W 在is_allow()中增加对$this->savename的二次检查。, V; N: O- f2 q
- |4 C6 @/ u$ M7 b- n
+ R% ?, c+ l; O5 B! Y 最后
3 a5 F6 r4 s* q, y6 _4 o3 [0 L; O2 d. R3 D8 Q9 L
0 D' z, g# \3 f, l
嘛,祝各位大师傅中秋快乐!
4 a1 |7 F+ o7 e4 R
; @4 X0 I+ C7 D5 ~- R3 a
$ N$ V2 p" i; O$ l! Q/ z1 P
! l) H2 _( G9 _6 _( ], |
7 k& S) P6 m8 _1 `3 w: d
|