1 o- W9 C) U9 s1 ~( ]; p7 C
9 G' f' I/ y5 z) [- a& T; }3 A
$ D! J% u A# N( g# T" n; c* V. w( y$ Z
前言
4 Z8 ?+ I7 {% R1 K+ L3 ]
4 L1 m& P" |( @ E
+ W, u% O& Y8 R# S 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。7 [7 Y3 p, K1 w& m& a
2 k' j9 z" v! ^8 S* e3 w: M
( P: o. }: ?- L% N
& T# K8 T9 g8 ~4 N, l8 h6 J* X4 i
0 Y, `# p. B( A' O# Y0 M
& a; w+ N2 H0 q q6 ^2 P 漏洞分析0 y% F9 O+ ` _5 A o, E+ _4 ?
7 I; L3 k1 c- m. B
/ O" ?2 T( W. T
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:0 G# R$ c0 ]- A- {
! f2 A% Y" Q3 R) c1 X2 V0 g) t2 U5 n1 o/ H0 ^. P0 l/ ~
7 \, M( l3 T( W
+ y/ Y5 W) u/ c3 h
& s% M2 ^% Q( ]4 M6 o
对应着avatar.inc.php代码如下:
# ~. l5 W. [( Y$ X+ s6 R" L ( ^9 {' v; D8 h
: ]5 }9 d5 A+ n1 }
<?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) I! l! B- X- w- b3 C( Q7 x+ y
, z5 [# j2 B4 e Z: S* W
( r" H+ M7 g9 O+ ? case 'upload':
! @6 n" V- v9 k% w0 w$ W( O 9 z9 {0 y. Y( h9 {, b5 }
1 W& J* S) X7 |2 F% e
if(!$_FILES['file']['size']) {' z0 g1 R9 [4 F* @+ l# Z, x+ _
7 z+ e+ T) R* S! ?$ A& ]$ S0 U9 v x8 S+ g+ e3 d: @
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
8 G0 W( I" k) N! k ; A- n: r* v0 \$ c+ \9 M
. J( g/ {. p. a* M, [ exit('{"error":1,"message":"Error FILE"}');
- @% X# R2 n" n1 U, [% i) p% K 0 k0 k& ^4 E/ ^/ A0 c3 E
0 p' E" n, p' U2 h+ r- { }
; z; O* e% R7 M: e8 ]( S
0 c* y+ k/ C5 h" v
2 l- ^# m) {+ h+ u% `; t$ o require DT_ROOT.'/include/upload.class.php';# Z& E* P' D$ \3 `: X
7 f( x9 a2 P/ j6 @! g3 y ^; s2 U$ [
3 N& U* k: V! ]$ A" y( j- s
& T4 N7 e# l! f! ]& C. L
& e+ z$ y. u, G $ext = file_ext($_FILES['file']['name']);8 F; e, U0 Y5 s: R: @1 \2 S
% y8 G8 P- Q! b5 a; h( W
! Q- K& P# S; H0 p6 P3 ^, U $name = 'avatar'.$_userid.'.'.$ext;
$ m) F* U4 |. v8 ~- U2 d4 O 3 b' b; u; f7 g: F, o) b
8 U0 [! F$ H( X; ?
$file = DT_ROOT.'/file/temp/'.$name;( w0 a6 d& H* g
# v5 p3 ?/ R2 L& r* p
" ]- e7 j2 [; Y+ c1 y 7 Z4 n3 u" a' {/ M* \# }% Y
* ^0 ]* s4 Y1 p3 n
6 S) `7 @% @; {/ L# C% [' g if(is_file($file)) file_del($file);
- \! Z0 N" }; w2 y5 E
\4 G- g$ Q* Q( | X6 H) `" _' k, H* M3 N, K
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');2 A) c. H$ \2 a4 @' o/ R
- m/ R% r# {; P* A4 q. e; J9 c, u9 [: Y/ p
u3 G2 u% C3 r, w; i8 D
1 d* E, O. K4 w: |5 m7 W
; ?: s8 Q4 M) U! f $upload->adduserid = false;
# M! q0 i' j' j3 s" [0 D4 ^ % k2 d i7 P* H; F; e! S5 {2 d: E
) u- J! d' p( M7 T
2 B' g( S3 y4 b; p" A" V2 ^% W+ R1 Q 1 J% _7 D) A9 e1 r0 V7 d! I3 E$ Q
2 \) w$ b( l W8 G2 ]8 n9 J( P& H if($upload->save()) {5 k& J# z# ` ^& O
0 X# i5 e# D' x, x+ m- ^
) q# [) ]3 q9 n
...
% h* H* }, k; A; m; k 4 D& g8 y+ Z9 x0 u/ a1 U% k
6 ^& C1 Z$ e, o5 t } else {6 l$ b8 n3 V0 Y; O( {- M
. |/ a8 k/ {4 \2 W$ y- D9 u" Y+ v# `% q( C7 p j
...2 W& ^8 D" m7 V/ I
0 M2 r7 W; t" d9 T
& z9 S$ K( C+ J0 t8 M! ?1 g7 | } E$ \% }/ K" g
9 A: } m3 H: N. p: q
" d1 b G2 S- d6 r8 f break;8 d8 k4 U5 g! C2 L9 d) ~6 n9 e
G2 [4 L& N% s" [( G" e' {
) h# Q! X+ v2 r! ~ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
- t3 ?4 s/ x6 H1 M; p
6 c( e' M- V8 [+ @' O+ v7 F- H
+ ]; [2 T7 a* J) J" F upload对象构造函数如下,include/upload.class.php:25:) t3 e: R; o: ? D s& o& g7 A: w
7 Q! j& ?+ ]' A* [" e
* ?. `& w m6 @; r+ D2 ^9 D1 c/ o% M8 s
<?phpclass upload {
@: O* w; l, ]$ D ' g+ r6 v2 f% j0 n, @1 r; p
5 |, C% F0 R% v! y4 `: o1 v
function __construct($_file, $savepath, $savename = '', $fileformat = '') {; B) f$ S, d" t: @
1 m% A2 \. J( t' g+ R! k0 X6 T- o$ g5 Q1 l# n
global $DT, $_userid;1 ?& C7 T8 } F6 u0 G$ V/ w' w
6 v* w. l& m9 I' u$ v0 F U
3 u! M# o* r: @
foreach($_file as $file) {' ~+ e7 E9 R* p+ H% ?' Y8 ^
% b/ N G* Z2 H3 E+ y: M
' ?2 g* c5 o" T [5 h W $this->file = $file['tmp_name'];
: k4 `/ D" ?$ t i4 b 7 w6 t* _# B: e! t6 U
$ v! k& q: g. ~5 }. n! u! x' U $this->file_name = $file['name'];
, M5 k* x9 h# U$ p* ?$ A$ u/ p: r
3 u) P' V! i) O) Q* @3 Q, E: q
" h1 t9 o. f8 ~, O, ]! I $this->file_size = $file['size'];
( K$ y/ ~9 a0 s / T- A6 X! C2 e
9 h4 {5 ]+ B' O0 i. s
$this->file_type = $file['type'];+ x3 P0 W4 M+ U8 v6 t
2 V6 ^5 Z; S3 L0 M/ Y, d. L! S% m
$this->file_error = $file['error'];% g3 R5 N N4 V" i
: S' M% {5 C6 y; U4 O! z8 H. j: c3 `
; N$ f4 h- E% x3 x/ G
# w0 E9 Z. `) [0 v3 A- L) F2 M; U) ~3 p" p2 x3 P8 Q
}: T/ J* ^0 g6 i& C* F. c
. i9 }4 E9 O) A* O0 U+ d
, L, C, l9 G8 M) f $this->userid = $_userid;
7 f" r: l6 T: v8 b; Y G; d
- u1 z/ ^5 C. v& E6 N4 s
) {0 C- O( x* ]. \& V $this->ext = file_ext($this->file_name);
) ~' b# ~% D7 H+ C4 p/ l6 J 7 [# k6 b4 P/ c3 x* K
$ V% t, u' j9 {" Q; c! _1 l( K $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];+ W& i0 {0 n. B B
& _* n7 i& E: {4 t
3 | p2 j6 a1 U4 ^ $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
# C( s- L3 d5 i. m/ O6 e3 } + d& M6 v1 j# v* V; H) J$ X; K
}: d; l$ X0 ^+ P. d1 D $this->savepath = $savepath;. i8 c0 Z' X8 u
8 A; a5 Y' ] q, y" q5 V& q; G& O# R" u; [
/ x' ]. }: C Q, W4 w/ r $this->savename = $savename;
& y8 H; l) R# ]) j' o: _
: V2 y& p% t' `' G& a" k _' ~, ]4 @; n% c5 t
}}* k$ }* ?2 N3 k
c( U4 M: a/ U/ @: e. a
6 U8 |; q$ }/ A/ T6 z/ V! l 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
# u2 M1 _& @: @. J M2 O; |
4 j! [9 D2 U: V) Z' I! e
" I# O. ]- V2 k% O) ~" Z 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 3 H2 y( ^! J, G) @! K) Q$ `
! w# W) h+ [" C3 D! h8 U4 ]! N+ W
; S, N0 D- e6 [* 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
# q7 [5 a2 b7 ?/ L! j & v% w9 ` x4 F2 L4 e
3 k. w) e/ U3 f- U0 L
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
G( c( H' R! y( w8 `1 [
2 n9 G6 d+ K# X: b
) P4 q2 Y, F4 K2 d
N3 P" k2 ?5 J8 P0 c: O$ B
/ p2 V/ A; u! }2 ?' c
% P" l8 w( v0 G% X7 S 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
: E2 A( O5 P) X% |! _
Q; C# ? T" i; r3 u. b5 R7 @5 I
1 G! f9 Y# t7 u7 Q& b# K& H <?phpclass upload {2 c7 s0 S3 `. l! r3 x
& A; K0 p+ @2 ^2 ~4 n- B" C) z, I% c f4 g, O' V% Q2 ]8 v
function save() {
6 F2 Q/ Q& H! G* R" |# j" e( } 0 o3 y" ^8 ^! A Y/ [1 @
- _; {, m: V0 ]: E9 B1 [" y0 o
include load('include.lang');
1 W" w& g% V8 N. Z 4 o3 S( v; L. u! I
+ b4 L2 t8 V9 V0 [
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');2 w( R7 n: `( g
' c, s2 F% P1 w( x o. M5 P' E M' Q, u4 l8 u8 x
, _. ]$ G/ f3 ?3 i9 r
" h$ x: V2 `: ~9 y
1 e e; `) P; F. N C6 { if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); z3 i# `/ Y( e" Q% F: }9 D' `
7 n; }* m9 ^) w% i
' Y; g X; t3 K6 h p( w$ G( e9 M
; W; g4 Q: w2 }. R& R 3 v# t. z; S. ~9 n. X3 Z
7 c- o( D- i+ b5 U) T! l, x! s ]( _
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ V; z! v1 Z# N0 M. Z
9 g- E* ?4 m( I1 u5 G
& }3 X0 v6 j$ `3 N; Q
) C0 T0 F% q& o
7 s" V7 |2 c" ]$ n& o7 N6 g, g5 U8 u: [" @9 S
$this->set_savepath($this->savepath);" f3 P2 {% q! u- s+ X$ V
! a# J" t3 z' E7 u8 P0 T; Q# y: a
8 A) D5 S' y% z( M. u
$this->set_savename($this->savename);9 n' v N) w5 @) G
1 `. }0 x0 H/ w
# O8 x$ d \0 W
. x4 I% i& f2 }6 P* n- I" G
9 Z$ Z5 M* }/ }1 t$ }: H& e2 K+ ^5 Y" g$ S1 B) S
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
1 o4 s% H: {" V( w' i l! r
, E" W3 T1 z8 @+ ]* V) ]& \1 O# d) N
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);, I) e$ m! j- B' f6 k3 \( O3 F
! A; l# P2 d' X' F' ]
1 o/ v6 Z5 _, }7 E if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);2 f; ^; j! h2 @7 T( J" s
( [, K; U3 |9 o- W: c) ]+ F% F# o: h! t
+ O1 ]6 t3 m; |; x5 L8 x
' Z1 c8 N' l3 J. G* E: k' N! r
* S1 b& F6 w5 Y8 ^: ]6 J# V- Q( C; Z $this->image = $this->is_image();
4 S6 Q4 t& j0 T# l/ Z$ q9 A
6 `0 u: o4 ?' x N# ~% u3 E
; J( N$ d- w& i4 i- B P L if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);8 Z ^- o8 b- r% x6 ~7 a3 e! C8 ?
/ ]9 [; Y6 ]$ x/ ]! z1 G P |
! Z/ g$ U% L! C; G return true;
( Q1 R; X; \( }5 |, V9 @2 {2 V
3 U: t- g' M$ [& `, ]1 K3 x1 p3 q
) I; [6 B) k9 [ l% G. k0 r }}0 y/ B' Q1 `; i! v) M1 g
% y5 @4 l7 D2 |1 q! |% g! K
3 V4 h9 U# g( ? t- F/ R: J 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
$ s) M( j9 V: L& _
/ v/ `2 {. ~$ b' Y$ X, @- W
: ~; G( r- n$ g+ w- t <?php }7 H' A7 w& W4 R/ R1 M& b F
/ ~8 c2 j, H. C. k4 |! p0 y. g' a, ?! [
function is_allow() {
5 @4 G; h; g: B! I* y- B ( H- Q4 h( ~2 g+ [; e
F* x; v9 E; P4 O+ M
if(!$this->fileformat) return false;, K3 h$ x8 [: k- {6 e) W
/ B( |" D, d) v/ g* Q1 Y y! d
' q9 M$ q4 Y( P- |; A* C- Y, q if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;: ~% @2 J$ Y/ ~! O7 F
+ {9 `3 ^4 Y1 U; e
. {) P( E+ _8 C% j4 \ 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;, _5 e, M, E7 H P
( k9 N9 ~/ d. p6 g7 L, }
( X( A. `4 C$ X6 d6 |7 O' B return true;! @) V: Q5 u# C' a) O- a
* F9 @' @1 R8 ^# z2 Q2 Z' n8 X: k9 \9 ?+ E! y3 \) w
}
& T' g3 R1 o" K; l# c$ C# {8 o0 U 7 j6 e5 I, M* G, h% j( R, s' t
5 j, N2 W. Z r9 l. e 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
( l; U1 P$ V9 t: }* m) m8 p 7 m: }! k. H' j4 r9 S, `1 ~& b
+ a5 Z" W5 c8 \. o( f: t
接着会进行真正的保存。通过$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文件。: Q( B8 v' B7 O% v- l( f
6 E Y# U# O; S2 g; ~
' R2 b; m- K& n! P7 m) [( O6 E7 T 漏洞利用. {: \ j6 \/ Q1 @) k) E
/ `8 o0 g1 r, o9 b1 S8 `# A( L. l8 {( c
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
6 I# y) {+ z8 I9 c( Q/ B+ g0 `: I2 r
4 C# ^$ L- w' Q, b5 t4 w F, `: w4 q% z' { j; z' l
, D$ L) K6 d, |$ G6 o " q: Y9 h$ b: J/ a
& `$ m5 ^& W; o7 Y5 W2 }" ` 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
% `) t. |: S. l Q1 q
, k# P/ A8 |) W# p$ E+ N1 j% m3 W2 Q' v: K* O
不过实际利用上会有一定的限制。! K3 M8 o1 w3 n. Z1 _" W4 R
( |; s- a1 a" D0 D% J+ E. o$ U( q! `
h$ F; h# J4 n- c# r 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。2 A$ @/ ]3 ~' g: q9 _6 ~+ }
! C3 z7 |; a2 \" H' Y1 p0 z
8 i. ]+ |9 Z; [8 a9 `
0 r) [% b& r- ~) T; l" l7 z$ \ $ r; Y+ `6 p1 L& Y3 N
) _* x R0 b3 z, C5 P8 w
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
+ A# Z5 P7 @! m' I
2 k4 \+ W+ | e/ g- N* t/ o' K2 T4 E! h( a" r' s5 _
省略...$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]);省略...
9 t: u0 v% B% G( v8 \1 A+ g 3 j; ?- L; ?2 X: o
q2 \& |/ F! f5 o. T$ ^7 G( s 因此要利用成功就需要条件竞争了。, ]+ \% ?, i+ ~0 e
& A# A7 [1 g. g
/ W* _ M. z; O& t6 @" [9 b5 I 补丁分析8 E1 {. w }3 K! A) W$ j
1 x$ a! W; t! J0 B# A& I1 Q0 G7 [ Z3 a1 A" F! M7 c
6 [$ T2 ~; c1 v; ?6 ^! O# {+ q3 k9 @ 5 P" J9 k) N2 a
% O U0 e5 t) p- z5 |
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:5 {) ?- _ z" \& A7 q) i
8 F5 @) _& G$ Q2 j) ?+ o$ q
' G6 D2 _6 a# X9 r( [+ g function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
- M& m4 Z: X% @/ g- S3 p) U 6 v$ i8 e" h( |% H: F
; `7 N; i: \% }7 k1 W
% O) v/ V, k$ J6 p# H% C ( O0 \ g& r1 [
7 @$ V& W+ r6 N/ W9 H: M' ^" T8 Q& X 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
' a1 [3 |6 P) [7 `
6 H8 O1 \( R6 m) k* V4 _& ~& G8 b! J: X' a, q
在is_allow()中增加对$this->savename的二次检查。3 D4 ]3 B- Z1 |0 l/ m
1 Q; |6 C* q5 p3 ]" e i- Y$ A# d
# h& J' x' [% G6 k7 O% |) |% a, B 最后, m* k7 A0 \; t, u
@) ?" [+ F4 A5 w* Q
2 o; H6 o. l. A. ~: R1 Q" W
嘛,祝各位大师傅中秋快乐!
- {" R i- B0 z , [1 O/ S, ~5 `2 k
3 I/ n9 V+ ^9 t4 m' L* D3 Y; u
0 I3 \* T! {( `5 P- H; A# P( m
- v# B; @& R: b5 m3 n- L
|