6 a/ Q2 U$ ^0 Q( q N) s
( `% j4 H% a, |! C# _% S1 `6 \3 \2 Y
$ U# l. I) g4 a3 Y% F
2 e5 Q1 u7 p7 {3 v; C0 y7 j |. X- n8 b; @ 前言) S+ b/ `* Z8 C9 I0 U7 J/ m" |7 Y
5 P% e X& B# ^$ Y
4 I M6 d/ `* o1 z. v% |, [ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
0 E9 {1 R: P$ |+ H- E8 a ; m5 }; O0 `1 m( k% K4 @* ?7 ?
) R3 X ^0 m% \6 e 4 T* q; Y% } r. i* K4 m
5 f) D* |- x( \6 x U
& V( c+ Z3 u8 f0 Y2 a9 ^
漏洞分析9 X7 d+ `; N7 S+ d+ X0 p
# L& ^1 C+ ^% Q# H
' c) @: `0 o& h7 K7 t+ z
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:% }$ V O8 ?- v
$ j5 t* i, C- W. K' F; y9 }% u/ F' M+ }* S3 J0 f) U, S0 `
0 N8 {. J0 o+ Q( Q8 z& z
* |: I: L3 t" f/ x' i* ]0 G
. V B5 |& n( Q$ u$ j3 E' o* } 对应着avatar.inc.php代码如下:8 D( Y5 M/ y% D
6 E5 Y a1 n6 R! S
( `" W* n* }4 I- b <?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) {
$ s# ~" R. {0 ^2 J
# g/ x1 t: X9 y
. v3 e% [- P" I* P+ j case 'upload':
1 o3 m4 V& i% [& t( v) Z) ^
( n! \% a: h% A+ y9 T P, B) X+ Q) z
if(!$_FILES['file']['size']) {
/ v* A0 E& ^; `0 P& F * X! O' U6 H [3 W$ l+ a* T
2 T( b8 W: R/ @7 x* t8 N; A* ^
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
8 j7 S# h! |( e; e ' D R2 h; O% |6 G6 P' M `9 r
" W/ M6 a- ~7 E1 o, O [: g& n exit('{"error":1,"message":"Error FILE"}');
: ~) o. y2 ]+ N. m6 t
4 h: N9 J. s( Z3 v! \: v
$ E3 `1 W: U% N" ` V }; _# d F. i$ f6 P$ _7 G4 |' J
2 K0 x# M* ?; r1 M, L$ `( c* j, y& W
2 X9 N. }6 r3 D: u" _# L/ t" ` require DT_ROOT.'/include/upload.class.php';
# D9 v, O" E$ Y. l/ S; P {6 f# M
4 W$ w9 a0 n% Y4 k& ?" i2 v: ~- F4 W+ W6 X7 {* _* Z/ Q1 a
6 J L& t7 s3 a
0 M. l8 c, B' a2 d
- \) J/ T0 F3 v# \1 E' q $ext = file_ext($_FILES['file']['name']);
* b+ B2 j# u$ i3 U8 ^0 H
, S9 g3 g* _! F- Z4 D) B
- U* ^% ]* E: [9 L* D4 g0 M+ _ $name = 'avatar'.$_userid.'.'.$ext;
- U. p; } ~1 A6 @( M" R
1 B. J$ w [5 y; J8 X2 L
% Z5 G! L- {% V) H* i' }3 { $file = DT_ROOT.'/file/temp/'.$name;) l1 ~0 W5 [' r2 g
) u& _0 M! e2 n# J
0 v- j6 l& b6 D/ ]3 o" F7 B' x 1 y, w2 i/ L' \3 V) n, `
; V( X* s9 B! ~; j% j) O# ?4 e( h* H/ A% }" @/ f2 C
if(is_file($file)) file_del($file);( o' V. z+ B( K+ `+ ^3 o
, @; q7 g; S9 Y }# I3 }6 s
: P% @1 u$ [. \) Z. m7 K9 z; q $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
0 o. i9 M/ a. y) c . m# |4 W* E" a' A% Q! ~: P! K
& F* Q0 S2 b* v: {& p; E
! t7 a+ T$ J [' N: F g
! O- T" `" q$ E. r1 B
. x) P& f U4 ^4 K4 Z0 h/ ]8 l $upload->adduserid = false;
b" |/ N; p0 w9 O) U 9 F4 m) h! K! |# l+ T5 r0 e
+ { h0 @ w) _9 B
+ D) v) F8 q/ M& o7 O 0 F/ r* b9 q" P0 Q* j
1 D! _" c3 g4 i3 g& U
if($upload->save()) {. G6 Q0 H1 I H
( @! o& y. o2 \
9 u! v$ k# r# }- y7 ]8 S3 _6 m. `0 B
...
- [+ E8 \5 a, @! r
2 T" t0 M: V3 d- W& b# p
# W# d2 a8 G, D6 M" ^5 L3 x } else {
5 W" S: X. j8 L8 p" `" n+ [; C2 V
- s" v& K( z+ g- x+ C# ?
; r( y7 Z4 j0 }4 p) V! x ...+ j* L" a2 p3 w' C" h" C- T, Q$ R
& S$ l( M) E) O2 I) p% N! P& z$ w
6 s3 e7 ^* q" P }0 C7 L9 Y M2 L9 H% Q6 f
5 }' }: i$ H( q5 T j3 X6 u, ?+ s
) T, H d5 ?* h* v6 z* H" }" y break;3 e/ U" I+ Y r/ {( o5 U1 H* b
2 |' z/ G" f0 d0 A' c7 w6 B2 n
; I6 @6 e) e0 V9 B6 h; ^. B
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。9 |- v) f9 P) n8 f$ R6 z r9 o
/ s$ r) H5 d" A" t7 l& L* C
8 k7 Z% y' ?2 i% q( O$ y
upload对象构造函数如下,include/upload.class.php:25:- ^9 k P; M( R
4 L/ S6 a* i# L" n. E" c6 G$ S
4 L! J) [% O; H! P6 e2 Y <?phpclass upload {2 V C6 J( o4 u: ]
# u) x: B% [. H! K) f; @4 j8 O
1 I+ _7 G- ], ]' ` function __construct($_file, $savepath, $savename = '', $fileformat = '') {2 [/ {, N/ m* ^( W; ~1 r' D1 ]8 I Y
, f& t) J8 y2 q/ Q: Q7 x
* \4 w1 n3 B8 k% Y* w global $DT, $_userid;
% H4 n0 F8 ?7 D& [ 4 K/ t- P. J( `" s: e- \6 O: t
9 x7 J. M( v, W
foreach($_file as $file) {
4 b$ M* z0 u3 N0 l3 c) T6 Z L2 O# D
% ?7 \( e) V8 L. E. ]) G
9 t/ N! {2 H& N% e' Q9 f $this->file = $file['tmp_name'];
( c- D J$ v3 {6 Y0 L 2 w' z6 h5 @! B
# T" V, g" | \' t6 u$ N8 r
$this->file_name = $file['name'];
3 z8 b& c3 k- a( K: l4 }! |
* |6 N8 }! e( O: {+ B
& C5 @2 M+ e: D. ?4 P $this->file_size = $file['size'];
1 Y7 b; s' F g+ T7 e3 u }: t 0 }; J* a2 d! P2 e% \! q6 _" v: S
" ]* r1 [; p2 N# Y# `" `+ a$ n $this->file_type = $file['type'];9 C( V8 [% n" [9 U) l
6 {% M) s" `0 t$ l( p' ^0 p k& K& }. r- j
$this->file_error = $file['error'];
4 c) q6 L! d: B* ]
0 s/ S$ y& c; Q# G% z
$ e8 J! L8 `: d5 e; C
/ {2 u& ^4 n6 L% f8 U! b
/ Q* f( G; |# ?/ U; F
4 h# r. A$ D6 i5 a }2 y7 N' b/ y- o& n$ X: g
- ^$ `* ~1 H+ D/ G$ V+ r$ ~8 w9 k; M. P9 s) m
$this->userid = $_userid;2 r6 d+ Y @' V1 u
' } c4 e' y# e
* `; r6 G. H3 R
$this->ext = file_ext($this->file_name);
9 z! T* E" ~6 j2 d, W9 C 6 V( W& v8 K1 u, p
+ r2 K k, y& N w/ j
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];6 l" K2 R3 H; ^! j1 J
! ~8 c9 z1 i: B/ b% s- w8 a9 \! J7 U, z7 D) R# w6 ^; m
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
5 z/ W* N( a! O; q0 y8 N' {
* `# S) m8 H6 g. s( q+ P4 X7 i7 x" [
1 j& ?+ i, D/ s) ]- S1 b $this->savepath = $savepath;
6 R. a# U2 K6 d6 u" w C& t: [
5 N N9 _* z: \, I$ Q; t3 M; F, N- Q+ k
$this->savename = $savename;- o: l3 k: d! _% \( F
1 O. W5 X5 D" W
! a# H. f' q) ]3 [: y2 e% _ }}
/ T6 X& `' L( T9 ~, \. k6 ? 6 w" P1 H# Y4 V7 v! b7 p. |
2 n: ~- _4 j$ D1 k# q1 C; R4 |
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。+ z9 \8 |2 I5 z0 J" L
" ?4 ^1 e: m! a# H
2 @ m) D5 g' S2 a% W5 n' ~( _$ \' S 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 8 r+ K/ d& q# U7 q
! A5 y8 u$ s; V w
9 N' ]% I+ {( u $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 f( L+ L- G, Q8 T" ] % K# U! D5 \ y3 k) j ?* B' S0 Q
6 W; v* p8 E6 [- I2 z, K 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:! n6 G( K: W) H
5 t% l; C/ V7 k9 p: G t8 W/ a" Y) n; H; Q
7 f4 B6 z, k( ^2 X9 J* \* z0 N4 K
( c8 A- t6 `0 I2 U* h& W
3 G& a$ z4 W2 e2 _8 w. g! A
7 o/ i& T3 R1 M 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:8 [7 c2 f! J# U% `7 [9 g5 ~, f
: ^ H/ W' o( U2 l
" {+ r" C B( a" p <?phpclass upload {4 Y- m+ }2 T1 O' z- D! U
0 v+ \8 X% Q) y
8 G) z. j7 ^- a- G function save() {$ Y% \; b# b5 _% g3 O
: Z( m0 F+ Y% S
~3 m4 ^6 P6 M8 N* ]# V- t9 o include load('include.lang');6 t1 ?0 |3 j. T+ {2 J: p
. U+ U& |5 [' b/ u" ?" H2 S
0 e& C, y3 E& ]3 m6 W: [
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
* b! w: i2 P. y& L
5 e1 X- @6 h1 q( }# g
5 s/ n* ]3 M5 d! \) c
[$ H2 X# S' V5 U. F) f / k; |5 J- x' ?$ f7 O# C& w/ r
6 G6 S6 V' @% c7 `# P, M; A$ c6 x if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');7 c: I5 T/ R' n; [ F
0 ~$ L* m% ^3 f( m" ^
0 u: H4 f( R5 `" A 7 s$ i q# _8 H9 z2 T1 v6 l
% R' Y5 R8 q& |7 k
3 `/ X, ~- V) b* h* t4 M3 U if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);+ V2 w. l- n% @ v/ S' U2 r
5 v, ^- X+ S& b6 m. r/ u6 [. {/ u! k. m) W. F2 m* Y# s; A( w: A0 t
( D* V6 ~% P# g) c0 D. G
; X2 x/ O' J- E, r2 J$ s$ |7 b5 _4 R
$ M) I0 ]: r- {8 D! a: K4 T" Z
$this->set_savepath($this->savepath);
]9 Y0 A. m, I) w# k/ a 6 |( e; s7 y5 P
0 u9 B8 w* z& L
$this->set_savename($this->savename);* b9 f/ ~7 N, C0 k
( y& T$ w- r9 O D G1 S5 L1 } g# Y0 v& T4 t4 R
4 [9 h7 V$ k+ {% C4 V
) r$ w& b5 l: d9 c
% F7 q% F* C) b! `. i# Y( B0 F
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
! Y" P) C; t6 B( w* y2 P/ a 0 g% L0 }9 g9 u8 Q+ H
3 R/ k! t) U: m
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
: w; J$ B- J0 b/ v6 H, F7 y* }
8 n7 o' m7 n {* L
8 a# E% Z# u0 [* ^" X2 H if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);; U! @, {2 U2 |) e7 L
/ H. W' w: m* x. S; {7 D0 _1 G9 n! @' T9 N, [: L5 k
8 i9 p0 k* C, Y! \4 Q% `$ n
) a; N2 N* k5 X$ J2 y
9 @# D% j3 S9 |3 k$ n& v5 ^ $this->image = $this->is_image();
) o" Y9 v) D0 ^) R+ u( ]
( n5 | i q' V7 W; K& v6 d( J# s" V7 Y, x7 X/ ^2 l
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
0 ~4 H' C4 ~2 R" r2 h; u- L
2 B8 D/ j# v. ^4 H$ N6 k
9 X/ g0 s0 G4 n) { return true;
) O% l; \- o. f 7 h, {2 ?/ h R7 J, A! I: Z1 J. j
0 a' W& d! F+ t6 B" H }}2 U3 F. K6 `* w1 J T
1 {- E8 O; ^3 ]5 Z
/ h3 t! z8 ~/ S8 F 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
% I* [( F' S" B5 k7 g9 C
4 G7 }) L* w1 @* d+ S: A+ ^ i* o* k/ C# H% z4 x
<?php
3 w7 x ]6 g- \9 Z0 S( u# d4 t 9 Z/ _1 r4 c# k0 `
: X! b" V' {- x: s7 c3 w4 g function is_allow() {. n' X& ` G: U1 R y
1 t' G1 a% _$ }0 I, w% g$ @
2 u" A1 k. ^, s, R, I
if(!$this->fileformat) return false;& q! h/ M1 }. X% i/ j
% P, {# x: t+ o% p% U/ g7 M
K4 u/ p. E9 u& \- r5 ]7 G0 D if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. B" S" j# G0 p& T; \9 u7 I
' e, u- h3 h; s, T o+ [. N' J: `* g$ u/ @# e) ~& ^2 w# `1 r) L5 b
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;
% u3 f A. P$ t
- h3 S, v* I& M X
' o1 @) z+ }; x return true;
8 ^/ n1 E/ v& i, e( O 5 A. L% ^. V' M, P: p* E/ W4 b
7 W: \$ u, B$ I, c, V, ^, Q# q }
8 S- x7 k' m$ h' j+ z% \ : E' A1 n# h$ @* Z6 D
0 b* I7 O' m" s: t" j2 l 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。; w& Y& S5 g% Y# E+ R! C
7 q* w( S. h* X/ ~0 z" i: _7 R
1 Q) c% d/ `% Z1 v9 g& P 接着会进行真正的保存。通过$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文件。5 v% g! A! e8 S* c6 S' d
; a" |& F4 `2 x% Z
1 y) e# O6 v" @3 R: d8 I 漏洞利用1 h1 |) _1 I! C$ g
- ?# \% @/ x2 Q
; s& O+ f8 l( A
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
+ V& j' d4 Z+ G; | * k0 w% u; V) U Q1 n6 H/ L7 _6 V
6 C0 n! O# Q# p2 G* X
: @* y! s, h5 O 0 J/ ~1 }1 x, }$ Z8 R/ {+ F2 Y; p
2 N5 r2 @% E" C( j0 }( k) f
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
( c9 c: E+ I) t
: E3 |% \3 G9 j3 j, p I5 Z7 x! e1 e! X. L- C9 X ]
不过实际利用上会有一定的限制。9 c9 I. v( a8 z8 _+ j
, U0 R- F6 W0 E! L3 E
. `- }$ K5 m3 @' J. I 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
5 K: `% Y8 G& `, H- G* C
' Y! m! O$ q0 b3 q4 k8 c+ A
2 U; |. ?$ T) F1 U4 ^ 6 k( ~" z: Z6 }4 W2 Q9 N- A
% p7 ?9 Y( I a x3 y; `7 K" P7 E6 c8 F- U! A5 R
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
0 ^; D$ z( Q) I7 N' A0 j3 W/ t; M/ M0 d3 I 7 j$ B$ a7 M* ]$ D7 @. f8 T8 a
- B6 M+ i3 r' ]
省略...$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]);省略...) ~# K0 L0 }$ j+ u' Q
: {) W2 R. C' {. p5 U
5 e* q2 O/ o' v4 {; X6 D 因此要利用成功就需要条件竞争了。9 C/ v, c' D7 R( C: u# k" ]
- h& {3 s4 T7 t3 }5 U* v1 H% T
0 z5 g8 @* C% @2 {2 G 补丁分析2 J5 e5 `% J) R3 ?; j
$ f: b$ m/ l1 |9 |
% N- e r5 b4 J% b( s% F
: p7 T1 o0 k- t! v6 a
$ u: w8 U- Q, x
( y! g+ z- q( n3 p. |( [1 } 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:# x/ U2 f: ]) ^
" z! x( \$ e5 G" o8 ?& N1 L
& N) w+ S X7 |/ s' \& V c function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
% O; g+ Q2 C& b2 }
$ E& E+ x( [6 a! g1 b* I2 Q* R. J! B2 h5 T4 g
: `; \, H2 @2 t9 C R; B2 _
, \7 M' n9 [& U* e- Q3 }+ C
4 w8 n2 {3 }* G0 { 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
+ x' v/ f' r7 b1 H9 ?. D- l5 z + n/ p9 |3 g( r4 K+ y# r: v9 i
( @, h3 p4 n$ i! F* A# C
在is_allow()中增加对$this->savename的二次检查。" b! j# u7 A4 ^5 G6 a1 q, {+ L
/ Q/ Y& U& E) J7 m4 b. z l; X, f" O7 T5 h) s: B' }9 Z$ e
最后# w$ c( f3 W. R3 E
. ]+ D# I2 {8 V) ]& g8 Q
3 \( l c2 m' P. T8 U( U 嘛,祝各位大师傅中秋快乐!
f* [( {3 o8 k$ ^% E* a" j/ T) f
- G, |' j% Y( h# W
: q' V& e) f) H8 C& t! h
" A1 S3 _ B( w) I0 k
. P' h2 v9 X* |6 i0 a$ g6 K
|