6 \+ U' e5 [/ V& {( ]8 L4 v6 y! b5 m* [
& R) C8 k4 {" `% c+ f [0 H
, G- ^ k4 w' s% \1 g3 }
8 E! ]; s$ F8 k, Q& N 前言
2 H4 N1 r) H) A m) G: \/ c3 l/ w! A9 } {
6 z" M. Y0 t" m 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
. ?! g+ i3 Z7 @) I Q/ f - w, K+ T* h" x- Y% v8 Y+ t( Q' [
( N6 i) C; _0 v# v3 J) A
% e! F! R- z" i3 S 4 ^ O* b) Q8 U' z
. ]7 m5 O7 Z( d* F# D/ e
漏洞分析
! y2 i! T4 F3 e! v4 c
; W* ~2 U) T' v* m3 w. q q$ |+ j( n3 C; o
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
' o1 O, y d# i3 }& ^ ' Y* Y7 |5 ^2 D. `8 q A
6 f0 L+ R: s6 l1 d4 o% j3 A
9 m3 [! }' S9 k, u/ Y
7 U1 X# j, `) M- [( T% a- G6 G
( {" ?! ]/ f* b/ c
对应着avatar.inc.php代码如下:0 l; o0 P J+ L- M4 Y/ K
8 o' A! h6 c: ]
6 j; h$ m, v: N
<?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) {
+ j3 d& R: l+ r6 c / B7 a5 r3 |2 d: c% d1 t5 x
& P: p; \3 r* F- z
case 'upload':
/ o; i5 T3 S7 A7 {, R
. ]. j& j. J; }# E5 c$ T8 o ^
: S2 _; k$ \, w% B if(!$_FILES['file']['size']) {
Y$ \; f# H& O$ { 1 |4 ~, K, s6 R0 T
# N+ f; K ?& _ if($DT_PC) dheader('?action=html&reload='.$DT_TIME);: q7 Y: X7 Y% o& {4 E E6 k" s
0 ~$ m& a. F$ i+ k6 b
' W/ W& q8 p8 E) @ exit('{"error":1,"message":"Error FILE"}');. c: \4 e. e& c; C( C& }' ^' F
' x0 ` L+ W2 f& s5 F. ~1 ` H, h4 m5 l% o$ M; f: F, T0 s& P
}/ B; u) E& U6 ]& d5 U* G
( H3 \* ~' p& E0 Q. E* i" l+ J4 W2 q" e( b% d! ?, |* i
require DT_ROOT.'/include/upload.class.php';
/ ?/ x2 N- R" K2 p' s- V
- ?3 m$ n) y& ?4 N% x: s4 F9 w0 I, G" S, A
; f/ T6 n5 J3 W8 C. F; O+ @9 P# m5 K
( s, w4 d# s! l5 K/ R8 c& e$ b4 ~4 R- a3 [ H/ Q; }
$ext = file_ext($_FILES['file']['name']);( \! z E9 u+ z: P2 x; U8 p) c
6 a" A V9 | A; q
# c* J5 D) l: X! [ $name = 'avatar'.$_userid.'.'.$ext;
! z9 y4 ^) Z3 V/ i. {4 F & N0 |: E; b1 n' K
/ y( W5 q6 s5 h5 i& o' e/ u; ~: @
$file = DT_ROOT.'/file/temp/'.$name;1 {" L3 q$ o X8 [+ p4 Y8 u1 L
' _8 g- T6 N6 d0 V* k
- b4 _9 @; c1 {( ^; F
% K7 J x/ `- Y! T/ U/ N* e! f p
- v' V1 v6 E- D! I( M- N
: O2 m* k) X/ q4 n! g9 ?) ? if(is_file($file)) file_del($file);, V2 z: B! \% \; f; y# G
0 ?6 f; X8 d- c7 t/ a! j4 ?# J
% m" T# m$ v7 [2 ^7 N2 Q# p
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');3 w+ ]2 \( ]! {+ U; y/ D5 s# C
& B- M) j7 g# p" V* Z; \0 ^: k9 S$ H) L# a# g7 m
- o9 o# [" B! D3 q, A0 |
. \1 Y% ^( I* U% K
1 l* d0 `' i. u $upload->adduserid = false;, h( q5 l; Z2 y! ~, p$ ~: H0 @( \
& Y3 j! ]8 p2 e# W) q. H/ j3 m& _# V- h) F" `) ]$ Z3 ]0 s( v8 ?5 \
- l" m$ I, z& O+ l1 y) T1 Y( o
9 ^! l; Q* F9 B. P3 H
$ B1 i- @3 \% M% y5 C& X if($upload->save()) {
' k& o' |! \+ l: Y 8 {5 a& C P3 l9 I/ t; s1 I+ ]
" x7 U8 s( c, u. g0 C
...& v# t/ J/ Q0 O# I
4 n8 s0 B' E' V( B
* {3 C, n- n( _( x } else {
; h% `+ h* t& o) u( p( f4 t- t
. v& b* U4 q) _! b7 Y, f; W2 T2 d, t k) h* v
...5 t; e. D( R ]! b# z! P! l
5 D* s7 h- F9 x A- |3 r+ q; @
) z1 B: s! C, z$ B
}% q3 {0 `3 N% I3 ? N
3 E, u4 F- g6 o% i' ]
, X' @% B& ^. p3 {7 v break;$ R. s5 ~% K+ T
' H, C1 q( |4 `. ` t2 s
7 L7 j J2 w" E" M3 I3 R 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
7 o9 h& M/ u" H j' K
: L+ e' N- f0 N7 q5 s, J0 Z$ v* f9 X, w
upload对象构造函数如下,include/upload.class.php:25:+ Z; z6 e: c& X' f$ F
- Z* X' L8 t% I: W# X
8 M( m: {6 t0 W* p. b( @3 ^ <?phpclass upload {
. U1 D( \: i& v5 o ( o* s8 c0 i8 P$ [! D) U# G4 W
5 F/ D6 I# f' N% r6 S( q) X function __construct($_file, $savepath, $savename = '', $fileformat = '') {
3 O9 z; R1 M3 g* _3 V, W7 F8 g
* e+ ?, ^! V; p2 n/ T
) V8 v* q9 {* L3 b+ l3 [ global $DT, $_userid;
; V$ k' O8 c$ \' D
% Z) c3 `- y: C* ~( I y' _) J9 J) w+ C: R {
foreach($_file as $file) {- b0 {$ [5 Q' I( v
. l2 \ R$ |* {7 ~8 F- B4 P% F6 `5 ]5 H* O5 T5 F6 p
$this->file = $file['tmp_name'];
+ v1 U) d8 C+ Q! `9 h& p
9 b. Z& O, }: g. _& |8 e
/ V# H) N0 o5 h& Z $this->file_name = $file['name'];) |5 N; [1 Q0 R
" z: u' U. ~+ u0 i
& v4 p7 b0 N0 ^9 g! l6 Q $this->file_size = $file['size'];
: |- _' Z- s6 ?+ Q% Q5 w4 s# r. W/ g7 ? ! f7 q) h; {0 Z5 A& r2 E& [ L
* N+ e$ n5 m+ }( ~5 T
$this->file_type = $file['type'];
* l0 w' [/ b' H. S, ]
) n+ C9 \0 ]. A" t% z# [9 \! P9 G$ s) F; l, ~, Y
$this->file_error = $file['error'];+ z# I9 f" @/ m, m
2 d! s( l7 ^+ g) n7 E- b. Q% l3 X* M6 D+ l( u9 ]
+ n$ d5 ~9 ?; G0 Y" K( w8 E
9 Y, h5 v& J$ i; f7 q; q, p. U& o! f8 I8 h. B$ S: I0 [
}
8 [$ Y( \! r/ R
' @0 e4 l! J5 M6 b8 D
6 s" k$ t4 q. q( i3 _ \3 P $this->userid = $_userid;4 V& Q- o$ Z: c% i
7 Z( b, T. H' G1 l4 t, n
: k* S# G& w- D: n% o+ H8 i $this->ext = file_ext($this->file_name);# G9 x1 o4 ?% u
% ^6 r; G2 S$ J$ V" h9 n8 N1 D
2 G2 b8 C2 S# U8 q: u0 O, \* s $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
5 Z0 g- }8 l# C2 T3 z W" D7 [ % }8 W( E6 W+ D) Y8 A2 @/ `
- P' y8 O9 L* Z $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
' e& P$ b2 p% D4 I 8 ~6 C0 M0 s8 H1 ^
0 j/ f' F, N7 d# T
$this->savepath = $savepath;
8 b3 S1 z# U+ T, P$ k 3 c/ }. _% {! \! m
. w+ D$ o. j& | $this->savename = $savename; u+ e; C( \$ S/ O6 p+ F
, P% b- Y! z; G9 |' y
' `$ N/ _+ B, ^3 g }}0 C- q2 t- I9 h& L" c) ~
- s4 l! [* ]1 }( [. U( V- z
8 @% E$ `1 u* _& X, h' x' H 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。, l2 t, Y: g1 P* T4 V3 a9 B
4 R& p* {* J% o
# @% ]: N( [1 X3 K& L. e2 t
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
+ Q$ x. d( Y- ^3 Q1 F
2 R9 d3 ~7 @* O0 J8 |3 t9 |
N5 L9 y& v2 T% u. n2 E& _ $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.php6 h0 a+ y- O3 C3 @1 A2 r3 X- p
' @$ ^! Q' L8 }9 a
1 U" j. H" g" U
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
" @4 C+ a! Z8 r& Q
6 \% _) D* _; A$ B, u2 f5 `: p% |% q% p! G+ @5 A
/ w }& l& R5 I# o, l: C$ h/ J' q
5 }/ G% i9 |% ?: \3 x" b. o# y2 n3 A: K% P$ H f
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:+ b& q; L% {" W5 K
% }7 b& F; `- }. O) {( r6 t6 {$ F
4 F: f. @* B) \4 I4 o( J <?phpclass upload {. U3 Z; ?& l! v# k- l: J+ u& h( T
6 n% @7 z& i; T. v: X% x
. z; @4 N: I/ J5 r0 R/ w% Z
function save() { a8 |7 o" V: Y# C& W/ ^- J+ T: c
- P2 `3 X- Q" T8 @
- z# w& T& m2 g9 Y4 [7 w3 J include load('include.lang');9 x& u2 x+ \% k/ {( C
" D9 G: d% z9 o! Q
D1 O1 b% s I4 P+ i- b! U if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');) X/ R5 G2 }2 A9 N# N8 S
q" h3 X5 y5 a9 B% ~6 B3 p* v
0 L% N* y8 f% z \, l- D
`; P0 E9 g6 }3 j7 q' l/ M
: r% t+ m M5 A/ R% R
u: Q% T+ k7 a5 X if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); E- k( g( J! A
0 }' x7 j& L. V, \
% P' ^9 D6 _. q/ L1 a
/ Q! Q% d& R# a5 V; E# t 8 k \& T* Z# P0 c4 V
. |+ P0 N) r) J: F# f0 X6 |2 L' D if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
* v1 W7 F# Y9 ?9 I+ ~ 3 }: s7 r- P; w- d' I7 o0 @2 Z! _
" ~0 }& Y6 Z" m* p* y u6 E8 h* X
" d/ Q- B, D. T' [7 `/ E G; [ 3 o0 Z+ n% w, k
% r/ v& c0 `: u$ ~ $this->set_savepath($this->savepath);
% h+ W& q7 T) }" ?2 d' z % A6 ?/ @# F. [6 Z% g6 C
. z0 w- `: q/ Y* T0 t& o+ O7 @# b $this->set_savename($this->savename);: _( h$ x/ y8 {' r
0 V5 _* S W m7 x$ t8 M, e1 g5 p5 J8 _: b9 [( r( h p
* L0 e9 O( \; S: e( V
$ n- m7 T# w% u# d( g0 k Z% ~' W+ R" Y% ]
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
4 A- v$ m/ k( o8 I 2 Y% a* I0 x# ^7 g
# n3 t( K9 E* v if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
! A8 ?- V9 f4 E4 D; e
" V& k: Z6 ?& N N5 n0 I5 F: X
$ T% L9 ~( D* M, b( g if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);2 X: \# {% m' H* j0 F5 {6 T
4 Z' [, \! K7 H& R i( I6 \
! |! M" |- y7 E% }7 D7 @2 m 7 \7 y5 v6 Z, M1 I
) K: _3 d0 C6 P4 _. G1 y& D# l
% t! B% K g5 K6 g
$this->image = $this->is_image();5 ~5 _' v' X" S# l& M
0 X& I$ B4 T/ D& p1 J
3 ~0 H- |+ ^' ~0 P4 S
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);6 F" U7 ^* E9 `' H
7 D% a+ B; i+ N# l! B2 y- H# E$ v' V4 e \( W8 G7 O6 }
return true;* Q& P8 H7 \7 y- ~" d/ r
7 G+ L2 c+ Z @$ h1 X! c% q/ Y: S
* c! \ x8 T T0 Q$ f% O }}6 ?7 h u) g0 S: s" Q$ w
" W& I1 R, h. B" P9 \
3 L; k6 U1 V" p# P7 F
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:" L: _. `: M& v' L8 j( N: I' k) P
6 ?% ? i; B8 f6 J; i
: H: x4 G2 s1 B) m+ I% o3 {# }& m
<?php
0 ~6 K; o! M+ I! M4 e 5 y0 B) n- A2 j* `# o
e) }( D- ^7 V. R7 R1 L4 D function is_allow() {
6 b9 A" v! J$ @( K2 D / O5 E8 n n# Y; p
: A! D0 z+ P4 C0 J; ~4 j A
if(!$this->fileformat) return false;
1 ^6 @3 ~3 Q4 V2 `7 o
) M8 b% @0 t1 B2 A s* `3 J1 C6 U% ~
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. L4 M( {7 E7 v5 Q0 u* X5 j
$ L/ v* e8 ?3 f0 k1 ?
% j% l4 s9 P4 I" i1 i& b' Z/ U 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;
9 b. ^3 Y. |; [. _8 R) I' a6 v$ g
6 d! }/ J/ W) B$ K3 C9 z9 `7 a n
" n# w# ?/ |! i0 n$ b* Z return true;9 G! c2 F A3 d/ v5 S. A2 O* X
( d! p7 m* Q! N) q& W( \ I
! d0 q1 P( Y6 _4 i; O$ a0 o/ j }9 Y( _' p$ A/ z
1 d# U c; q! s' l. H% [: @2 D( B+ D- r# x. g- x
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。2 D+ k5 ?! Y% r7 H) [5 ]
( n7 h7 {; n: ]$ z E5 n
4 X3 v7 ~7 y7 g% W! X! n% B 接着会进行真正的保存。通过$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文件。
: }7 ^* I; M: V5 w) z 2 F+ Z# _* p- Q" j
& C: ^. h3 Z9 G
漏洞利用! c; u+ \/ B0 g6 |
; l% _* [7 E' X1 @7 K. Q5 O: ]( `: z4 L2 B! G, i- ? i# ^3 ^
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。! m2 Z: y e. N, h; }, e
7 y, v ]2 k# {" I4 E, v/ E7 r3 c) Q, K' h5 a9 X' e
! ~: Y) `- \: n, }/ b& k ' w1 O0 |3 p; k
/ m& Z; t$ U f 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
: t5 c# N' m/ n; B5 g" K
$ Y, z" V, d: R0 `
! I% a" q! R2 K. u; S 不过实际利用上会有一定的限制。! d# U+ b/ a. M$ X
+ k% H4 L; e) l" u/ V$ @ g, }8 h) Q
: X) p8 ~1 n2 r1 J+ h 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
$ u/ w, a; u% F 8 I- P8 C0 w2 u, s& ~% Y9 J5 \
( o' x9 q! F5 E! O: i9 ^$ ^
/ s. ?- E7 Y$ Q$ O$ x8 h9 k : R# b; ?. s: O
7 V( Q2 R4 L/ ]- _, ~+ K, O 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
, ]- L$ \" U. I3 T, f 3 k; C5 t8 [3 i/ }
& ~+ h% Q8 H+ @: K$ i8 v3 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]);省略...4 X7 W R& s8 }$ b( M9 v h
8 M9 b# `6 y) n( \+ ~9 l& b/ Q
& a7 T* {$ M! \ 因此要利用成功就需要条件竞争了。
, h) l5 W# T- m4 Q q- d
3 M# h6 O, C4 ] @, B9 \; B n( G3 Q! C9 N0 a6 f3 [) I9 K4 b9 n% B
补丁分析# o* r8 E. {" Y: T4 @/ S. M) w
% x3 R r! F6 F# A' U
1 h9 i4 L' c& n t7 k0 Q
* t1 b7 M) q* r+ v# _- n
+ K4 K' M" ~$ Q. t; i
& R, ?" f( y' [ 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
3 |5 F) b1 @, Y; | R
8 A2 p' T& v. q+ e
3 _. d- W, D) x9 ^. F function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}$ K8 B& e! p7 D* i: O
/ c3 Q% e- ?* ^6 k4 u7 ?2 i
. |; s! d$ h- v: A, M9 C6 G! k# S
8 G2 h: X# f; Z5 [* M+ P* X$ x, } 1 ^- I( [2 @, J% _ l6 j0 U
( P6 a7 F+ R! Q' b1 N, ^/ j8 ] 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。' d7 H$ F7 U. |2 C
) {) q2 G2 l- G. e; {# ^4 j6 {+ P# v. [9 Q$ H9 Y% e0 n
在is_allow()中增加对$this->savename的二次检查。, Z! b6 F0 X) v1 t' }) {
7 L# s3 p+ I% I. u* M: {9 j2 ^3 \+ F- ?1 V- h: m% a- ?& y
最后
$ Q6 r* \ k3 Q; O6 s( n. R0 E4 J, z. C$ v/ L5 V( V( j6 u
$ e2 m, l- s0 O# K4 R9 G$ b
嘛,祝各位大师傅中秋快乐! f& I7 b0 N* }/ V: Q- h$ A
! G, L. i8 \5 N
- Y; Z% }7 e; L- X: n# z+ A 4 s5 t9 R+ b% x5 r- [+ s
* K$ p1 z& F* b" |' i+ S" `
|