" _3 s. @7 Q- p9 I, a$ n7 M1 D# `0 @5 y: H
/ Q/ `- Q4 m1 O6 I
* U1 I. n \9 L 前言9 x+ V, H& n2 B& k
/ a$ c/ Z2 E, A+ O( m* |: _ i, O$ I9 I# A9 {2 S8 x
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
4 A2 g1 k8 j7 r% r% {7 g8 r
& t Q1 ^1 q& y; E# h' ]
5 n3 r" q% S# i# u8 Q ; o5 v. B z l- P. u
, }8 d# {* v1 O8 I: @5 B: G
# O' H; v# E1 Q; O 漏洞分析2 g# j' T& M0 @$ a5 A0 W% j% M
" p% b y! G5 L3 D7 k* J
% N$ _7 G& F3 I+ _
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
3 A. {) O1 n3 z8 j" p. D0 @, i! P8 x
7 M. ~4 o- T+ p7 j5 n4 Y* u" r" ~- h$ {$ r6 Y
g0 }. p0 B! t* S
! B, x& o( L( u) F9 ], b5 n! ^0 N5 \* u8 z+ B1 q$ q7 p/ q
对应着avatar.inc.php代码如下:. C2 b; p2 }7 t2 F* }% z2 }- J$ V
% u3 b3 q% u$ X; @7 O6 b
0 `8 N' b, ~8 V5 w5 ~+ w
<?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) {
. s5 f; P5 \2 @- a& P 7 |- f5 d: _: U0 I1 z, q& i
6 x8 G! d, R& O1 H2 @
case 'upload':5 }9 h m1 r. Y
! U5 N2 \4 v- } w: r) f( \' Y/ q. o& U% L4 ^9 |7 { V
if(!$_FILES['file']['size']) {
- _) f# ~, S) V3 Y& a, C& O2 N% l, o . |5 `/ D% g& `4 I: C) b
* C, [4 D! ^7 j3 c% _" _5 l
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
9 E) {: o. I ~0 K# R `# g2 u- G' t3 P5 m4 b
4 z: z0 |5 ?7 s: l" t exit('{"error":1,"message":"Error FILE"}');
0 l% T0 O' U/ P2 u4 s+ T: Z. _ 6 ^* n" F' V: a4 v2 V" ~$ L
2 X9 ?3 W7 j/ `! X( ] }( e& h9 B$ b5 v* v3 z: @
2 q9 B+ b G$ Q w5 [4 }/ F- A$ O* G* x* k9 s( M4 [# a
require DT_ROOT.'/include/upload.class.php';
! o% B; J' R; B1 O) j* R9 N8 M * X8 p; r( Z: |% J0 Q4 u3 L
8 ?1 V9 ~# e/ Y& C3 F
2 U2 M. F7 |% D
1 }) S( j4 Y( D1 O- l. ~' Q7 `0 t& \
$ S" ?5 \& w* K2 f $ext = file_ext($_FILES['file']['name']);
/ W* M4 J u& b T. X9 Z
" D8 ~" }8 ^7 P
4 Q$ y: K+ d" P) Y% P& q $name = 'avatar'.$_userid.'.'.$ext;. b" N2 \$ _5 Z, o9 B7 Y2 Z2 d
$ M4 C) ?& K$ l% W
6 B! G5 W! n6 I* L& a% J" z $file = DT_ROOT.'/file/temp/'.$name;- ]( m# Y g' U0 y. I/ A/ S2 ?0 u& ~
8 D' E) t. r; i- E% Y
+ f* X2 `( q, m( c- V# B
9 i. k0 e1 Z5 \
( Q5 {0 u2 w0 T+ x
- S5 O$ Y, x9 ]$ O q if(is_file($file)) file_del($file);
8 x4 o& g* C: I z
3 C4 @# t* t+ [/ \0 E$ `0 W
; z6 J, Y+ @/ P \4 x' d+ Q: m $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
9 b9 `$ q# P2 x+ Q; c 9 K' p5 r1 i( K2 s* J
& b6 q+ o# H- b. S; T+ J
, f# L8 A7 e5 H4 E2 ~4 T: ~* n
0 v$ d9 P7 b$ B2 F; o
: r7 ^6 F' C8 r* X* b4 Q( Q9 N $upload->adduserid = false;0 d! S; U2 ]& W" Z- P {/ t
$ Q S0 }% G8 H' {
4 a/ R: P! p6 T/ H & f; `- g; g ]
- |: X6 Y0 P5 N2 w7 s! J
! |5 d/ h. U/ _. {0 }- l! K
if($upload->save()) {! ?# O$ |7 n, z. `3 P9 m1 M" G
. M* y2 L+ Y0 S9 z6 P1 k! H. g' a) @* w
...
) j) @) x) t0 L! Y 4 q9 \& l8 c8 U* y
. _/ V8 r: `5 B9 s } else { J9 T( J- Q% T- c
, z, t6 V0 R E& Y) O1 x4 ^: _" }6 z& s3 R, z
.... n# G0 c' }( e9 ~7 T/ y
3 a) s3 h9 L: }
: }/ N$ K. r( L/ u }* ?0 l, T/ `" L6 ~0 ]( D- i+ K
/ [* V) y1 t& r
9 _" S% T6 r6 o1 F) Q$ @ break;% G6 F. }" r% D2 F% p; w9 U
- ~4 a; B9 t& d) d/ A8 d' o9 i1 d7 @9 m0 n# d/ v! K
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
7 I: L5 ]# c) N* ^$ B0 O" o$ n5 ] 9 y+ d* _, e0 b1 Q! f, P9 O7 k+ e
9 t7 P$ ?4 N' ]# p8 S$ o5 S0 H
upload对象构造函数如下,include/upload.class.php:25:
' j% r+ a3 J1 x1 J# F) _6 @ , j8 {' V2 c( ^' ?# d8 _/ g
/ j+ H; C+ k: U/ ]# h. h <?phpclass upload {
5 J! D& m' j( E7 H/ v k
8 \; L# |) G+ \3 c( Q8 S- L. @6 l1 Z5 m2 W; x* X- F6 P, k. f' Y
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
! I7 c# v. h5 R: J! U, g 9 o! W; D' U i' w+ X: x' i: x
8 @3 {6 D% n. E8 d2 _8 w' U global $DT, $_userid;3 J: k% U3 r% F8 O6 _2 d# L
( b, t# }5 N( O* C* }4 c6 t0 V, Y) _% O' G3 U8 W; H- ^0 w) a
foreach($_file as $file) {
2 m' s s P% Q3 I0 [7 N1 A7 b6 @" x
- u: H2 _- D I+ e( K; p, d! o" A- v# n1 X8 ?5 E- R& r
$this->file = $file['tmp_name'];
% i3 R0 d# F2 W5 O M
$ V: \2 h4 |7 Q$ g: g" e; g& R+ C4 r4 l& M+ W3 N% o0 K# X+ t
$this->file_name = $file['name'];; b! m: W( D. g% B' w, M
- g$ f M3 q( ]; Y w! v. X5 A
' Z# m( G) n* D. ? $this->file_size = $file['size'];# Q0 P, J3 `* M8 [7 m* ^
) F0 G9 T" @6 j% P. Q1 d
4 p Y1 {# M9 V $this->file_type = $file['type'];
" ^9 e; h& ^7 X- z$ m9 N ` ; u4 l) [; Z; l
1 ?- R1 N1 k$ X5 E2 `8 w4 ?+ n
$this->file_error = $file['error'];; Y! k( Y& p* G
' c! c3 ?! M- h. ~# C# c5 z- _6 x T! v
* F3 |8 y) R9 e9 n. N 5 t9 M5 R. m! F5 _% J
5 h7 g* _0 ^0 z% A }
6 U6 S, c( K& q2 ^$ _. ~- v
$ O4 x& Q- n: U& O V; ^
5 S2 B6 r. d( g2 ~( F $this->userid = $_userid;
4 _2 S5 b. o- f5 D2 D4 ?4 v ! f: X0 J; V* W
5 \: N4 p8 w3 {( K
$this->ext = file_ext($this->file_name);
" l+ i" v6 Y' K3 P! D3 ^. B
: G' `8 ~: s6 I& I# H' |% J( O6 e* i& ]
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];* o1 \! [* H# o+ S
1 X4 E: i6 Z8 X+ ~' j
5 B- f9 E) h' E$ |9 r# X- w
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;5 q; c; ?/ T3 p) t2 v. D* n& i% `
( K" D; N5 ^) k' ?; S- h- @5 q5 k% a3 Y; J6 _% m, F! U
$this->savepath = $savepath;
+ U) F! V% `/ @& x* t) x
0 U" M9 R9 O: [4 x& k8 s; B
5 e; C+ C* M$ b& l G $this->savename = $savename;
& d3 [' M: J; Q( m3 C. G) r - N9 q) [4 T [! L
0 g8 Y, b, h3 x1 C% i }}* C9 K% o0 B8 g- }: w5 z- @
4 a* b8 C" f5 D& `
! U4 v0 v( b5 I/ \, ` 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
5 E T/ o% \" L
/ w N1 R7 Y2 v& J
) J% w( k) i/ c" W' Z6 D 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ! `/ h4 ~, c5 Z1 ~; R
3 X5 o% k( ^0 ^3 D4 O7 m1 D5 f
$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
" {1 U# S; V' v x6 X8 y- m. x
" U, u% N9 `/ j8 R) D
5 B4 ^, A8 f% R9 F, N/ Y6 F2 h 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:+ i" J+ {: J+ [) E+ T
5 n0 f; V/ G7 k
( h+ H1 ` y: P8 f: ]
+ P2 ^7 F5 ^/ k9 B3 @7 c3 z* i
; H% M9 T3 m- h8 _4 s( P' R* [ Z1 z7 w6 m1 x' c( Z
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
8 h' C# j+ u! ]" y8 \6 e' x
" H/ o7 f; w7 F3 b4 n( v4 n2 k( i* H7 `: S+ ~
<?phpclass upload {
! i8 @% Z9 e0 Y
7 P6 _$ n. {2 G8 P3 S* J9 ? z
) y2 K" b5 v- c$ E9 e1 v function save() {
3 `' g+ X' p' P% A! K
) u' y. U! L5 e
: {/ z* Y( R3 h7 K; S include load('include.lang');" J; ~% o5 `) P0 A
7 [: K) ~" _- ~8 p
( }3 l, X6 {6 S6 \- v( s+ G$ | if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
- P7 w w3 |% M x; u% f G
q6 `- p/ {% z, b8 j4 B+ S0 Y6 k( e5 _6 ]4 k6 x5 z3 J- m8 c
; o# t$ m0 L0 Z, D2 o 5 c4 [4 W1 x' C' m9 r0 i8 c
7 u! K6 W' o w if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
! D+ D( t/ u: `7 F( M$ x
+ Z3 H8 ]0 v3 H1 i1 Q5 G* N1 }* s* H# a% r# ` y
; i7 J$ G7 v7 d1 Y8 l1 b
( O4 @& o/ r8 A
* w5 Q0 J4 f4 h, f: A if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);/ |4 p- Z$ z9 ? x( m/ i
4 Z+ v# ^8 L# g6 F* y% }
" S. R% W2 N" H( j% }, A
; m6 f0 I$ p& }& b. T$ Y' y
) L- u( a; G7 `& P; u, g: Z6 V6 E$ B% l+ D l5 n
$this->set_savepath($this->savepath);, _+ w$ ?! G; q- ~9 T% y1 l! o
' Y" Y* [; ~9 s. f m$ i' K7 _) L
0 u0 W( r! _" G) i: D, _& ]+ I: s $this->set_savename($this->savename);
# \ f" e$ O! a# Z$ l' P- C
; g2 Y, Q/ I! k
+ l7 s' M+ H, x* A% w+ f' o( F
; a7 m: m3 r# ?+ q" \+ I d7 ?6 H. O
I" z+ g/ Z' d7 }% ~
5 R1 H2 k/ K, M+ W if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);7 n. {0 L% p6 g
' S! d( V6 l" f; `, C$ e
( |1 G6 a$ K" n if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
7 r5 p& w5 o& u% ?+ M ; H1 w- ]5 T' j* x! a8 i
- m0 j: j& U7 `" E
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);) }3 [8 S( }! \) o) s5 Z
$ B0 Z" E% O+ H/ C% v( n0 I, S" G6 E' h: M6 E0 `$ k7 r% S
% m6 J) H; g- q: f
J: ?) `9 z3 e+ m7 x
/ @" g* E8 Q z2 O# K $this->image = $this->is_image(); @; k% j+ n6 O% ^, I9 B
j, s: V- w5 n" h5 b' i
2 p! Y! }7 m; C if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
3 C0 l% ?6 A, K: x
: P9 k# x% V) q& H. c
. [; S. J& |5 i" k g' @, @9 o# g return true;
7 P) E) J* a/ F- J0 { # X9 A! I. k3 @2 F
8 Q& k0 ]1 H6 I0 {) e0 j* q( B }}$ {0 D$ T R8 s# F
: Y: F t" S( }& X9 ^+ h3 }9 ?: k' O" e9 z
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:, b Q: `4 I0 d. ~* n- q
& {' F1 m9 ]! ]0 ^: X0 u
1 L% h! [6 q! W1 ^ <?php
) n9 a% l+ @+ J" z+ }* o3 x. Z $ c4 T& u( e. k6 k
. m+ F7 z& n# ^3 ?/ D) R5 l! c function is_allow() {1 t% b+ N0 v3 H/ L. ~; v+ G
! c$ \& R2 Q- \$ u4 h
3 w/ m' J* n& b& M1 P0 r7 h
if(!$this->fileformat) return false;
" N9 R5 T- e" |& e/ D
C8 E1 X- h1 s$ W# `. i
+ q; G* K3 j2 d4 ]- l' j if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
& ~, S* m/ S! Z+ h$ ]1 k( u * S! a: Z: G6 W
* Y3 c! [- r' a. ^- U' o7 C 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;1 r* U6 m' X! U
6 W" q6 N2 |9 v' ^
# H( I. r s5 m return true;
! H4 j1 h6 C4 Q. m& A : A( m. h6 E" V4 k3 M
4 G1 s! u% {1 L4 R9 R! K' L% i
}$ M- `1 P1 }* `6 \
1 Y+ Y* ~: m/ C" j- I0 M: v. i1 x* c1 ]( i
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。4 C- P, y0 f! ~1 A& }2 r3 A
" l$ V6 ~5 ^6 J
, v! ?* d1 m: A8 E- 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文件。+ k6 M: I u- a9 m; H# m) Y
) } I2 l+ V, s- g; x4 E
: j4 O5 a# U6 O& m0 @7 r2 K1 }2 V
漏洞利用# w; h2 C- L% o# B* b% [
! M! C' @1 C6 C+ q i9 c7 w- |1 [) o' K3 T- C v1 v) D) z& H. j
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
9 ]7 C( B% [* F! d, q8 Y 2 o3 ~5 r' l) u6 r7 Z. f
0 N: k" b+ R# b
6 q+ @6 i$ U) ]8 B
% S' R. l/ X. M) P( {) ~( i
: ^- x2 F# X: [' c3 M- f, n y 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
# w) b; f( a; e# {
/ N! U2 D. q$ `
% W4 M' \" m& H' [0 [% D 不过实际利用上会有一定的限制。; Z/ u) }6 n4 T T4 V8 d) e
' `+ c1 j( R i+ o* C
; d$ z3 c& y4 s9 ?+ v- \* \ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
8 q& [, r( T! p& i8 @' J " Y5 d3 _% o/ _
5 j8 W: u! `+ s& U# z. u
! q1 Q I& ]- v3 i" B
5 I3 f+ {+ u/ E2 S4 j. j+ E
7 Z+ x, x2 L1 M, S( |* g- N, z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:9 w$ k p+ Y1 J! t
! A0 H% l8 B' y" I4 I. x! X3 O% B* O$ `
省略...$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]);省略...: ~ I8 I' G1 ^. Z) j
# D0 o! j& R% {$ `8 W) D, @0 Q) e7 d: ?; ]6 Z8 J' `
因此要利用成功就需要条件竞争了。
, H; C2 V, V! B6 c- ?& U : x- J5 } c$ F+ I9 C2 l
7 B; W4 |4 t U9 f6 i 补丁分析 Z& U0 ^6 M) l% u( q' I6 Y( h) F
2 m9 f5 c3 L/ Z. z$ a4 z! V
. E" J- H& T9 S$ Q$ W0 w' _
- @ g; U" _- a9 _) z( r- K
% O9 M" G; E- O2 Z e% o6 c2 E* c' K! d0 F
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
8 `9 n3 x, o% @2 D " Z) b, w# r# W X
. n$ M5 n* c5 h- I5 t% x function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}& u& I# i4 ]1 p3 E6 S. j
# U* T% K; f4 U& A8 a6 Z2 e/ s! \+ I9 {
r v# Q ^" o& H, o! _
4 p+ f% k& r2 r
. o: y" u9 a; R3 A3 }
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
1 y, F" ]: c9 w5 m
; n" F! D: ?- D3 s4 m8 n
/ Z- G# X+ c" Y. b: X 在is_allow()中增加对$this->savename的二次检查。, |0 m' t: _; Z. G
- C6 v `* ]- G9 o5 e
6 H# k" l0 ]; ]! k 最后
* [9 I" f1 q) \; b, ?4 `- k. [: v, }8 y- D: L6 Z, }) t
I) w2 q5 k9 H; A
嘛,祝各位大师傅中秋快乐!: W% h: W5 \! [% c
, P* G5 s% c, s. m7 D) k. Z& a. L' s0 O3 `8 h
r* Z& _! l0 ?' {( b
9 v8 A9 ^" _8 q, ]7 W* I7 C
|