9 x/ \6 D! g L Z' j( v) u
N4 c7 ^3 h$ C/ [% r) e* k+ F1 S$ A. O
: G& a7 R3 e# I' u0 ] 前言: D4 ^: {; \" v9 l. u) D' b
* F4 B; F6 v! T0 ^) Y* b
8 R% ]- q3 Z4 V: p2 N; C- h3 H 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
# ^3 [6 j7 Q8 m" u" Y% K$ R # L7 g! ~, K" P8 T/ o
0 k- F0 Z7 B, H2 ?8 d, _
8 X, f5 m |0 S6 O- ]8 [
: p7 {! S8 o+ W. x: t
% Q+ o; y) {+ q& N4 v# {
漏洞分析1 P3 Z K5 A& t$ ?- t) }5 i8 D
' m/ w$ R% U/ o& b, L3 u% ]$ _) r, K/ q9 x' I* Y4 T# u
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
$ x8 o* [0 ~. | D U6 v3 m
& U" Z- D, k5 `, j1 i
! ?/ W3 ~5 N5 f/ D( F$ @* e5 t 4 l; }% r7 S! T: z3 e, {0 H/ w
7 _' d. _7 `7 G: J
/ P u% R1 q q' ~: `$ j$ u 对应着avatar.inc.php代码如下:
- u* b1 ~ n" k! O7 ^* L, p7 [
! c$ b) H9 r- r; a- W
) w: E& J; N0 e <?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) {. w3 O+ e+ S' l; D8 Q$ q
$ p5 X, s! o* e* r. H
U4 D l' ?) w/ ?( w. \% p
case 'upload':* e* t2 ` @& {5 n7 u0 f$ |4 i+ b
; k& x7 E( }+ |: Z& ^8 i* C" A
* g2 x! S# O) y) a3 I0 s- D1 T9 F if(!$_FILES['file']['size']) {
) _* w* Z! F/ P) |3 U9 r$ X : _* }, u' @* B, O$ k2 l
2 l @$ P2 [% x! J3 Q if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
) i; ]4 K g& c0 R, ^7 M
: o- B1 X& U' V' `- V% K; _3 V; M+ }2 y; y" H( R$ }/ W
exit('{"error":1,"message":"Error FILE"}');7 ^8 }2 R% S7 @/ K
( |5 F, h' S- M0 M
" P: a1 E6 f- U, T0 T0 D( p4 q2 r }
3 Z8 D5 Z; a Z& R2 p * C' u5 l. |1 O# m3 X( o2 L1 d
! D6 Q$ Q& a2 X: F
require DT_ROOT.'/include/upload.class.php';4 `1 b- w) x6 z9 P7 y+ T3 f+ [
9 ]# U) J8 q3 `$ w7 }3 c* d
/ M- q$ u" s5 O' m8 r1 I* T* F
- p; K. g( Y( P( J% U
0 k: K% R+ m6 m$ l; b( T `( |0 R. w) l
" _& L! P& @: V $ext = file_ext($_FILES['file']['name']);
8 V( r1 T2 H) V* P; h3 g8 h9 U2 K
8 Y; |" T5 y: H" k2 ^' @. E% X& K5 N# B' H, g% ]8 l
$name = 'avatar'.$_userid.'.'.$ext;3 I5 a2 _( s6 T- p) b' {9 R" \
4 J1 m7 b/ T$ `) k0 L+ z
/ |8 t F" f; E2 `0 \
$file = DT_ROOT.'/file/temp/'.$name; f2 O+ d% N A. _+ [- c5 G" m
: N- O' q- ], j/ M! S- }1 E; @! H$ x/ y: e8 r& k* J! n: B3 b
# J. O2 `, N* _4 r0 o+ Q9 ] 4 j! X3 h4 {! S' C1 `" W
+ T. ?" O" w+ X. G4 ^* N1 _
if(is_file($file)) file_del($file);4 g6 S+ p4 `1 b# A0 ^6 t. H
& X8 x# `" R4 ^2 k( X/ N3 h
8 a' O$ @$ f5 q& g $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');8 |- @8 p& z" i" ^6 g" d2 ]8 D
8 X+ w) |; X V- b
8 U4 v. q, O7 ]% G
9 F" S% d! x# q# b' J( o, K& q0 k2 K
9 B$ g8 Q' s* [* B u& E9 y
7 R9 |7 S- Y! |+ N7 i x; O8 e; W $upload->adduserid = false;
9 n& @# d5 |$ A% o, M* c/ J- Z" K ( v: M7 D: F! w2 R T& A
& I6 L5 {0 N/ W) `7 G7 e
6 d9 _& K- p7 C& ~. R5 L) r7 T3 O
0 W. X. @+ Z- @" {
' _9 \6 ^' E; E! H% x) A if($upload->save()) {
8 \/ n, ?, I( L9 G1 |' ]* T# a: Y 4 p. S0 e, Y* d3 P6 c
4 a- X7 D2 j' b# u$ g$ e
...
' b+ `! L/ J& p- v: ?' H2 Q 5 ?, }5 q' l; T& C# u! u
C% w3 t" b' X* G4 I } else {! `6 K1 A- J1 J' j% ?
) ]7 C3 Q* V4 A2 X4 g3 p9 ~, z9 N, O% D
...- I( B/ u' ~4 m* g5 R7 U
- Y# V6 H3 H) N* N0 s0 F0 l
/ ?1 Z) h+ H6 p0 o }
; f# ^1 G/ s2 J) x9 a ' v" G# s' r/ y! p M& t
0 H4 ], @. i1 K
break;2 R V5 I0 ]& V) [3 T) s5 p
6 q' q) e( s e# F) g& ^+ X, j+ {5 l5 s$ I
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。8 {2 c' ]$ Z# x [5 C5 C: n/ B
; F! E0 N! }! d" S
d. d8 d+ }2 m. t7 ?* O' @1 N U upload对象构造函数如下,include/upload.class.php:25:; B0 l7 Y$ d3 E D Z. n
; D6 Q! b* y5 p/ r6 h' `4 ]. X4 m
<?phpclass upload {; L) r! i0 e* ]2 M
5 U, N, s4 P% n8 ?; W F/ c3 ~
f9 q8 v- j0 ] function __construct($_file, $savepath, $savename = '', $fileformat = '') {/ n* u9 m4 J5 @
+ Z# Y- B2 C8 M: m# Q4 t
7 s8 i& X( q& T' u% n+ l
global $DT, $_userid;- k ]! T9 D, u0 b6 ^# E, N" j
S! g* j r" a) y, u& E3 C7 o6 _! y; q4 ]* w
foreach($_file as $file) {
' v) J1 N$ L. @9 J0 K
$ G# c' y" N# G$ U, ~0 }5 v- c7 h( Y# p
$this->file = $file['tmp_name'];4 ^3 i- g; _, J1 n1 H. w
- y+ V0 X; x5 P. W
" C2 {, @8 S+ k $this->file_name = $file['name']; ^" S/ F0 P' x3 D+ Q8 j* J6 Z6 f
/ r" c s. f' O- i% @
$ D9 X+ ?. z3 u $this->file_size = $file['size'];8 a1 Y, v& j& [8 \5 d9 q, k
! f% a& \7 w- G
5 M" h+ l" N' U1 Y; \ $this->file_type = $file['type'];
" |9 v* ]5 Z, `- Z; z$ o/ E$ A
2 x, G( Z; i+ v: Q2 H# u: z- H
# d) Z4 s4 C& `1 ? $this->file_error = $file['error'];
: ^. U: s* c# P5 u m" _& R : A m/ k7 c' y; G& E
4 C# D6 p S$ [2 r; ?9 [. ]1 A # W- I4 b4 k) [8 n) _1 _$ X4 F; X
% ]5 I8 B$ |- l
' _- u. }) l9 U' A5 L' d }
' K, {$ Z" c" p) M0 d 7 o$ E" ^" g) a5 M3 r: o9 [, u
# d0 l2 q9 g/ ~ H
$this->userid = $_userid;) F; h! s* E. F' C
% q: l$ `! P$ a8 F6 n1 y$ Y4 R' R7 f H( Q- y3 B' Q0 o
$this->ext = file_ext($this->file_name);
4 m# h: \4 ~# {( h3 ^ - j% {* O8 z, ~+ Y
" R) }0 }, W5 I, h t* N- U3 K6 s $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];1 o; I' g- T8 t2 y9 c: e
3 e6 ^! j2 F- P8 x/ X: E* q. e* E4 c% y
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;5 p8 x0 N6 q+ D4 ~
* T, G4 b) @$ X% }4 L3 Z) W
3 W" u& f: V) f- x$ }. H
$this->savepath = $savepath;
3 E7 l; b$ k( N9 c ) l6 A- [* m$ r n& U3 x" |( ~! e% L
, j' l9 U5 i7 `6 d l
$this->savename = $savename;
( _& \! @8 p5 p) K; U7 i" Z / R& _) Y# ] W. m* h
5 M# M3 }. ]* p7 D }}
6 O- A$ A. X" S! z C) `
8 L# ^6 z" F' A R* {* _
! v ?0 p3 ^% j. k9 X3 c4 c; ^" k$ p 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
' W' J3 `" w8 W
# F, R( ~% I/ ?8 ^2 M5 I5 q- r
0 D% L% O: S2 X b/ y/ _& [: m 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
6 A2 E7 A7 O( h3 T4 @ # G6 d$ U- G0 S
; ~3 Q$ S9 z$ r/ X: |- z/ L8 J; h
$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.php4 I& F, y8 l' r9 y- N# Q) k# S
8 Q! ~7 }: Q; Q% g$ b# \- p$ o/ V! I! a" y. \
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:4 O: f: h) G( T: s, Y7 c, w
: K# O2 W: |! ]* c6 s8 F
* |% C! V ?: B5 B8 l % W% T, P, G, C
Y: y. z5 o; d" |! ^5 z" n& t( X& ]
2 i! M0 M0 ^; _8 _! d 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:5 ]% Q; O1 h& w% x5 C6 f
: j% @' i4 k9 `
" l+ R& p% E4 X5 [" o! }
<?phpclass upload {) n4 V7 ]! [+ H& ^* ~1 B0 J7 k7 B- s/ z
8 {+ c( Z7 }6 q$ D9 B' x
h* P; r4 h9 c! f7 P9 n function save() {
$ f" z. T3 g' L3 Z
0 x' Z$ G" w- O& y# A1 H, X. [# }' I. Q8 I. e" f* j! b
include load('include.lang');3 t$ N( a# ]: W
# `2 A8 V2 ~( p- x
2 U- W$ y# ] H2 G/ w0 \
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' v$ E; X, ^' |9 ]- r
. E" X. @. D7 V3 |* u/ q
+ A1 g- Q" N9 m$ p- J' k+ l 3 }) ]# X T. n4 k- c
/ D, a0 q: I3 T
, r" d" Z5 ~) _6 Q1 [& D! i if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 {$ ^3 n- d! M4 O: m" l& r' ^
3 o5 P+ g/ p, Q( t* |: K/ U
, g$ j+ G. m; U
+ F; f1 A& y6 a
( m; s; J3 J2 W0 ]/ E
7 T7 y, m1 f. b" v- {2 F if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
) }* H5 U) B! Z: ~ b2 D " |% m. e7 e+ d' f7 `
* u- ~1 l7 w5 o' M
+ ?# L' b; s, |) D) k
Q# G) {- e. F7 r
/ e3 v: y* Z9 b/ _* f* m $this->set_savepath($this->savepath);% ^- h5 f& A! c& w) E8 T# d
1 L9 n; V e. ^- e) l
8 I u7 W( u; ?6 I! X% U $this->set_savename($this->savename);
2 {$ n0 F% W9 b' i& W - w2 X& e0 c0 U7 s) K4 \
/ ~; v5 ~/ f1 ^& y
! S$ K& ^. `8 P B, {
7 ^5 V: e: Q2 n- k) F
# \7 M9 |2 e; Y! f9 `4 a( O# g, }* P( I
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
% q7 |& [0 [1 _( j/ {9 ~ . h- P3 e( k/ N/ R% p% @
?+ k+ [3 Z$ U' ?" U m$ ^9 _2 }$ J if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);7 U' _/ C; i. e5 [5 _6 e
* s' H' O4 g* g$ c
6 |7 z, m3 ~4 w3 U/ Z if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
, M( i8 C* X% M$ c 6 M) d \" c. t3 s6 B
) P+ V3 |, N: M! F: H5 X& c3 D
! ^4 Q% Q- r4 g$ Q8 N9 K9 } 1 B! ~4 d0 e5 ]+ X8 ]
! b: i$ C1 |7 E( d4 L0 y+ T4 x1 k $this->image = $this->is_image();# {# f) O( ? z; m. G. }, _: Y
8 Q& y. @0 H8 ?) O0 e5 `
. A8 }2 f& N- P if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
- [" H0 n ~; j! M+ w- m ; u( [2 J4 E5 m$ [* [3 {# s, X; z
- ^7 l% v5 j ]7 p; l0 D& k
return true;, d. h* y9 Y' P4 v
6 k* G4 q9 U- U7 s/ t
4 w/ S; m+ Q4 ]: b }}& p1 F7 h) P2 E7 U: q
/ [# Y: A6 C3 G" [ R% u/ H2 W
$ ~4 k6 `" P, O) r0 t4 _, A5 ~) f
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
& K' w a& X, A3 C5 D
: {- u/ d, X. X. A) U+ K
5 P! n0 i% y/ [% J <?php& y- ~4 J. g) a W# { n2 Q+ T
" w$ Z: |/ M* b) W f. I- O% D+ t0 \$ p& ?
function is_allow() {
) G- ^/ |. O$ V4 Q
2 ~7 _3 [! K7 ?& `) O9 f1 u p- ]6 D) K/ p! ], G) D
if(!$this->fileformat) return false;
M9 z& Y h& S/ c* Z: T9 q5 g, Z
0 y) T* C' \+ y: W4 U7 q0 `& R1 _2 `* V4 j. U# f' [
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
+ r$ e" v" ^3 }9 e7 x3 q " o e( a3 K5 K% y* ^
N, u2 X6 V8 A. D U: ?4 d
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;2 ]' B: v/ a j/ @
2 h# d3 p' G8 T% m
( J% ~4 X9 [' r7 P5 I1 P L
return true;
# a% V* T/ e) e 5 `( x# y4 U0 ?. E5 O9 O
0 f0 ^+ ^8 Z3 u" S
}$ p# Q) F% V i1 U+ S, I" J
4 T8 b1 F; T" |- }
8 I$ c; L, U( r# H4 s7 C 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
; }! y5 }2 C: b/ m9 E8 H
( [. r4 _ v3 ~) V
) r9 M* a. V% d1 Y. q% {1 u: Y$ ~ 接着会进行真正的保存。通过$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文件。
: |% O1 M2 o i# h# d ' e- i+ ?# [* b$ L3 y
2 Z2 {$ A2 O% D8 T2 n. N# n
漏洞利用
( H& |1 s& a5 v) f* c1 Z: H; m5 m: y' x9 i3 `
) V' ~! N C$ K2 l1 i
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
8 ]; f! J M# h9 ~" ^1 Z0 ]
1 F$ L9 _: s- {& g7 w% S8 ?
: b) Q8 w& F1 @1 }0 m; F2 ^ & K6 W- q* d2 P
9 d# p& g+ F/ M- H
6 ]/ m3 v) N; w) F. O( Q
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
! T$ L/ K+ ]. M5 x * Y& }# j3 p f2 K% A% I
; d( V. p- J- d: ], b: @6 c
不过实际利用上会有一定的限制。4 t# f# a$ e' W, B9 r. J+ e
' ?8 S# l' |6 J% Y; x0 k) z0 m" E, u" Q
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
8 a; l2 ]5 G. W' y$ A0 v+ \% p 2 W" j6 {: {5 L* @2 [
2 Q& ~- B" f1 }+ s 2 c( P. g) j" L- y; V
5 B4 z8 q) m& f/ d
0 n* H& a4 b3 |) m9 e; S" j 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:$ M+ C9 h! ]# M5 ?
) v4 N: ?7 j7 l# ~ ~
, P6 ~$ R' i( g4 F' a" L2 f 省略...$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]);省略...1 W+ r s* |5 H, ?5 l$ o" g
, m" g% v$ R3 ]; k5 H
g1 j2 @+ i$ Y f$ m
因此要利用成功就需要条件竞争了。, Y6 s# n) E% a" }7 k9 _
2 j, l5 e2 u1 \! _, N1 `- r. Y7 e6 D# t# ?0 o( J) Q4 i
补丁分析" q4 E( C7 u/ B6 f# z* z6 X
5 V( f$ V6 C: S0 @% ~
( _' [1 A( L3 b
% e, \. E6 n% M5 T ! B4 j9 b& M+ c0 }9 v t# ]. Y. b7 C! X
3 n) E. ?2 k: K/ b$ s 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
- e& ^, ?1 a4 g5 H8 F$ X/ ]/ V; N* \ T4 @9 f' o+ g- C0 S# u8 [3 S
/ T% |( ^; a5 A function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}- ?; s( v C0 v; t
4 o4 h5 r* x$ g- x; O5 O: O
9 s2 U$ E5 u+ z # p; x) o1 q& w( s" B
6 h9 N1 {" n, l7 z- M
) Y* i0 W7 l$ _! }6 t) m4 n
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。, z: [$ b8 s! b7 ]3 y0 T
3 r5 Q! o `' Y8 `2 e" j9 C8 N
" e. N( k/ G9 B) Q4 `! ` 在is_allow()中增加对$this->savename的二次检查。
5 _+ H- c) ~% H8 z1 z9 [
+ u7 ^9 u! ^5 J, J3 t, v3 o+ R/ h6 y. ]" y) i
最后& t9 H- P ~1 j( y" X
0 x3 c! {3 Y/ v5 [7 h. c7 F
( h( l" f: a0 e% X" r- S
嘛,祝各位大师傅中秋快乐!) j e. i0 W0 n( x' t/ _4 h
: k5 }" g$ O) M; t
$ V9 S( F% ]1 w+ A y
! r( v0 X, ?% g* |
- [( `0 V! b( g8 f7 n
|