: i! Q0 `6 J% W7 @3 n
D3 z6 U7 d0 x' |$ `$ L% i
9 n* A i: }+ V+ ?# @' f: }" k& O$ ?$ P, D, w# j6 d6 B
前言
6 q/ i1 t7 d% r' d3 n: S ~9 L9 I$ O; a& I
" V" E: e* X& I' U
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
+ t1 A1 G% C X6 u! @( \
[4 g3 ]1 ]4 d( ]( J: X6 `- U
: d+ z2 }$ Q; x1 {
* z. J1 I8 R# `' U _, e9 C2 [# S . h$ T& ?5 j! |# Y# \8 r! K* \: v
" `* |# {+ S7 E9 p* P! d; h4 m
漏洞分析
; ] v$ R) ~3 n: W/ i3 l t5 S
5 Q8 K" C2 l7 M2 {8 I% F
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:2 p* K B4 t2 t, i4 c
1 D% v* N% h2 Q" Y: M9 ], ?' {7 w. _' u3 P' i6 M
; {" }3 {, E* e$ Z* c8 T9 x
2 p9 P) y: j p3 M$ \2 p! M; }7 Y3 R# x: a' B |9 K
对应着avatar.inc.php代码如下:3 ]6 E- a- a) T) @
* t9 ^) B. v* K9 I% B0 |" s; @3 k" t0 ]2 ]
<?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) {
) p- I% S2 L9 p$ @6 S" u
& L! b9 @; w2 E1 S) F! b4 E0 I, y+ S. r; u6 i/ M' I' b, e
case 'upload':4 T9 f5 F! T- z) p1 h
9 \+ Q+ ]: ?# h8 b0 Z @) U8 t. `
; l* a4 U: ^( z8 W9 M' t' p# a+ S if(!$_FILES['file']['size']) {
! H4 N' t4 c: O2 a. Y& T h+ ]7 e( U% w/ t& X$ g Z X
; n5 A( @1 K/ `1 ]: g' H
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);' p6 }- l r- q8 U: @
( f( g* h7 }3 T) _2 s, O. Z( m* m$ U: m3 N8 B
exit('{"error":1,"message":"Error FILE"}');7 h! q) E$ f& [' n( h, p" p* s$ h
3 O& r8 D- ]9 c7 m- T
5 q* O( L5 p* ] }
9 C8 K; M a( j( q# o7 X" q 1 j. M) m6 v6 S- n: A
# V) m5 E1 ?& V require DT_ROOT.'/include/upload.class.php';( [* b' b3 e4 b+ m1 L4 u9 U
5 c- C6 I( G& K" D0 ]5 D+ \/ n
+ G7 m/ b: J" [& F
/ u( R6 x k6 t9 P' e, p
w4 {0 C# M2 S) T
5 n" m Q& O& Y- _- C $ext = file_ext($_FILES['file']['name']);
+ I# H: Q ?) c5 P, z
' R: O) Q# N- j# }
3 r" q) m) i& T$ Z: G i( s" @ $name = 'avatar'.$_userid.'.'.$ext;
* {; d8 C9 X6 W" Y4 S. ~ % P9 L; a O2 f; ], {* O
" E! g0 h7 B4 Y! R+ Z0 [. k) t" u $file = DT_ROOT.'/file/temp/'.$name;
; e& i. F+ n5 H # a9 \8 E$ C6 Z4 Z) ]' @9 V8 b
2 P u/ [/ d3 J
' r6 C$ {4 c9 t! \6 n. d# d7 c
1 W; |4 K8 [9 b
* p9 {2 v, h4 z if(is_file($file)) file_del($file);
. A# c3 o. _% Z. \ * [6 y8 e* F e1 Z M- G
( d9 j# g6 D$ F. I
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
+ `2 X. u# z' J + z6 O, B5 z( B0 j/ q( R! ^
: G! C( x- F. s6 K5 i! Z5 E+ Q
1 v' u0 @2 Z2 }& v" y - w3 v6 V/ [+ y9 K
4 L$ h9 @: y: W: f! r $upload->adduserid = false;
$ K/ Q+ }$ G2 l1 t) v5 w! E1 J
! g% X* t6 v$ Z" |% s
0 n- X4 u5 J6 T+ B2 _ * P$ F' A* a. K, F
, n9 o& b' f' O* Q1 [
: O( b, s0 ^! X8 n2 H/ | if($upload->save()) {' M# W/ X# q P% ]* R% t
2 S) h" P2 X7 D. q1 T5 R6 k9 h5 x6 k, ]: b$ v0 l6 `! v! }
...; V5 q/ E( Y, q
3 M4 p0 X, W9 k) M
V& V6 @6 `2 n
} else {
5 H- f7 ~, j) k0 y* Z( Y+ d% }* [
5 q! e4 v4 a1 k5 m; T+ t2 X2 n( g1 h [) B4 l) g5 l
...7 q" J: u& u7 F3 X4 [
* k7 T0 r% E C% {9 o
3 w; C K; K! r! B) l: I }
$ \/ o3 K$ ?# e/ r y/ R 4 ]! f0 D( s' C7 ~0 D: X- E& T2 _
* \/ X( D I- u: U x( h( k' l
break;
3 ^2 G6 H# U$ t) v' s2 ] ' r% j8 |0 F) N
, ~- X7 M+ l G! }" d6 @ 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。+ r, f, R' [7 N# ?( Y' x
' u& I [* }, Q. u1 I7 C
3 c, p- n: a* V: J0 v1 _' [3 u upload对象构造函数如下,include/upload.class.php:25:
8 @" d3 y& Q7 i @ 5 ~, m% z& B/ X$ \! B3 C- [
8 H z9 M+ F* l, a <?phpclass upload {4 F" ?3 I" r& F$ b4 ]+ D7 Q
- V+ K7 w- f: _! z& [& L
& }' A! } S7 E# `8 e function __construct($_file, $savepath, $savename = '', $fileformat = '') {3 T W8 c2 n& R d6 E; U
4 {, T7 C# V0 k" |- L: a
0 m# n# K( ?0 |0 \% L" h global $DT, $_userid;$ X/ ~* d2 c3 K5 F- z9 V5 P
0 r* z; |5 `: K
0 \. E0 V# F P* r/ t foreach($_file as $file) {
8 z) r2 w$ d$ h+ _0 c6 U
E. R) { p" S8 ~: z& ^
$ x* z- e0 A9 Q" v g5 a; z' O5 ]2 R $this->file = $file['tmp_name'];
* A% O: L v4 g" k. E8 G% I F! \, O 7 H/ v0 C3 l1 G/ n5 z
A" J1 i; g' t) w2 p
$this->file_name = $file['name'];. M5 z/ o' K/ G4 t; Z, C6 e/ Z7 _ B
5 v1 M" L1 k: V) C- {0 `5 N9 R( X4 y
: Y5 [& D) Z# j $this->file_size = $file['size'];& t3 g# }- P' B! o4 L5 E
0 e, R& l% D# K% Y$ \
, ~0 i7 g4 [( Q% g& d $this->file_type = $file['type'];
+ W: c& j, O. C& ~& M# t% t9 M - q: x/ ?: ?! b2 w
; ~( l. d2 X" A ~- d: N $this->file_error = $file['error'];
3 c4 ?4 H. w+ o) t , r+ E6 `7 c( R$ S& s- q
$ V' `5 n4 D+ g# C( n& m' v3 g " Z# B" H0 y x" F
' b, S# Y4 A( \+ B
4 W) A1 N9 ]" [7 C& y$ R! a
}0 [& H. u, N; c' A3 T) H- E4 _
7 a: Q9 B: c7 q* |
4 ^! ]. X! _# j3 p $this->userid = $_userid;+ K% m( ]* e# m- }
7 z) m" @4 Q$ O4 _7 y! ?
, V. }4 m+ t# G3 ?$ I9 ~6 C" s $this->ext = file_ext($this->file_name);
9 d6 k A) n4 z: k' Z# }2 d" ~ 9 D# m4 [* O% y# K% G
, }6 x: [% \# x( {: S0 n( q% ?
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
9 o( z" C) I& X+ J' ? ' a$ Z& V0 P K7 I
# j1 ?6 P8 ]! w* X4 [% T8 {6 T
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
& u1 |% a' ^- C8 e4 X5 Y" u
6 @' o+ E; L" L* [/ P
9 J5 |+ O" T& Q: R/ T9 K $this->savepath = $savepath;# x. ^3 c) d/ f; e# C
# ~3 ^1 [! }( e" Y$ W0 q6 k! W) o
% p6 i9 F W1 e$ H7 Z $this->savename = $savename;8 a* o6 Z* o/ W! L. m4 D( E) g
7 |6 H: Y) V. o% x6 X: H8 I0 T% U
' _& @9 ?1 a" ^ }}) g% k9 a" k6 Z0 \9 r2 a" H, c, }; \
; J1 }9 a- ]* A4 k4 t+ } l: ]. ^
2 N4 G5 m/ r; r5 @2 G 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。' |. L( o+ s9 k8 O
* w0 B$ B3 |; V! E1 ?7 l$ L
( a7 E% j# q/ s4 j7 o9 j) G3 j 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 2 R: d3 T# N% _/ V
% Z2 L& d# R& h/ s( G. r: n
5 t8 ~* H: v5 Z
$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
) s7 I( ^" t% v6 Y 1 [- k+ b: ]4 C% a" e
) K& o- i1 Y' o7 z- r2 N
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
! A* S9 K8 x. |/ a C ; \5 K4 F7 e* `5 S; h/ c
' c/ Z3 t! ]# \8 }; H+ z" W
* c9 c# X- S1 F; H) g% S- E7 {
( J; \1 p+ q% `7 i1 c* X& j; D
* L) R) u5 W4 X) C; ] 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:. [# M' Q) x, \0 ]5 M# y9 u
! s6 l7 X f) z0 P0 |- o3 {9 o
- b/ I. s) @( E; r" ~- Q
<?phpclass upload {
7 d. T9 g6 Z+ j. }. i( m
6 {2 F9 j8 P+ d( I1 t0 B# i2 e" C7 [' G
function save() {. y$ T0 u: N- O% A
3 P* j1 l+ \4 S
- k+ f* ~1 R1 q8 ^
include load('include.lang');: i; c7 Z$ `$ N. s: ~
1 L) r1 X5 D0 j# r. O$ M
' E+ L& C, i/ u% ]2 {9 k if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); ` q: @1 l5 r; V$ E
/ s: e1 u+ V/ q7 z! s$ J" e
" [+ D. V& e% u 7 j+ D h7 z; U6 L4 h7 j
W1 |( m6 K% }4 \
+ k2 b7 c3 I5 H1 a" P0 H9 M6 z
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
2 q" t9 R) z+ @3 o7 p! T / ]8 u7 U! F% N
3 V0 m( ?! I3 ~7 d! J) `
3 A/ P* O; g2 C* v0 u
/ F. f) r' v4 E9 x0 [3 `$ X
) v( y% J) ^& d! \
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);% _8 D1 p" x* G5 E) k
- l: ~; F5 \# a( G& C9 q% @' f+ e5 V* `. i/ R0 e! \( e- v
( k6 \( x3 O( b- q6 {( k. Z1 ~8 v" q
0 C; c; B. F' N& V9 n! V8 L$ E9 C6 c$ ~
$this->set_savepath($this->savepath);5 U0 t B$ c# w# |* s. |3 H1 }
" P6 a6 y5 o2 E/ ~4 v8 U3 z r
0 W6 t% _* P- N: J& y- q
$this->set_savename($this->savename);
3 [! l% R% A) B& Q: J7 }5 y- P 5 V$ V+ F4 d' `, j: P5 G% ~
! r$ D3 ^7 a+ v5 {2 _& h5 d
: [9 _- c8 c% p- D
3 N/ O0 P# V* Y) {( b
8 Q& F/ _- z0 X if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
K- |: Q: l. e- n( f7 }* I4 |6 Z% @ + p3 Q; [! D! L$ n+ `9 O# F
* [3 O! b+ _0 b' H$ G
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);6 f; ?, @# r! v+ w% U" r
( M( H2 l, s& F
$ \* L: K3 P" v6 W# ?) F if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
L i( C5 A' ~5 z
! h" f+ ]. }% l- A: W; t* A/ k+ B4 s, W2 k! @1 d0 f# d) \
0 n) m3 F* o O' I8 G* O& y& B3 P
. K5 [& f4 ?& R: f; x6 }
" q6 [+ I, g, |# K $this->image = $this->is_image();
3 E) }" t7 M6 k% T2 i
' F# A! O- C t3 E# o2 H0 N9 r) s, B6 M a
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
0 y, N: S: k! y) l* _% ? % U1 z5 P& H; y7 q3 I
( O5 K3 d- f, X4 y$ e3 q+ y return true;+ h* l& H0 C9 _! {+ E
! D8 b4 G1 U5 b6 s% d( G% n' m
7 b! C, E+ v7 R }}2 A W" g$ `8 I7 o0 }$ M( w5 a
- I2 S2 z" h# q- Z
" i0 @. p- s- n. a, m 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:. a) m2 N) @( F
& i' @/ Y! l7 b- G- H Y$ y0 |1 R" i2 { r0 h2 K4 L" z
<?php Q+ m# `; n# t8 i8 R, ~
- o1 j$ i. Z; |! o( n5 m
: x, m# Z, O2 q) e
function is_allow() {0 r. J& `- d& s u
- A/ y. ]9 i9 o7 c7 g- P' x2 Y
$ P/ ]( r" ^) w if(!$this->fileformat) return false;
! {6 @) e4 E# Y9 S# i7 _/ b) `0 w
2 m+ y# R- {$ X
, d' ?7 x! ^( { if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
* S; }7 ~' i& a# N 9 ^+ y2 V) a6 V4 h
- N! O8 M6 P; @ ^& I; |& Z! n4 S 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 v& r" y1 n# v4 {
. ~ a' n/ a$ Q9 \4 m
4 n7 X1 U* a: L4 D! q2 _ return true;
- y" D2 g1 o9 F- K( o / T# {. N$ h: I
& z7 F. E0 ^6 Q' j }! i1 S( \' X5 t8 S+ p! {, d
6 @0 J5 z$ D5 t/ I* h' ]; K/ Y0 l; _! d: v4 K
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
) r* A: o5 z8 H2 l% f- Y 6 C$ ^* N7 e# q4 l
- M: o+ a( w2 |6 ~
接着会进行真正的保存。通过$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 C' g6 R% b2 J" ] Z ( S0 A( y/ H- @+ s' {" k2 H! W
- f% k4 {" z1 c* R
漏洞利用
2 Y0 K0 G% x1 X: I
1 J) j- k/ B' |6 `' [( R) k3 J2 ] t; \* V$ e# c6 I
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
6 S( w4 Z- e1 e1 S- N+ Y" u2 G . j; _8 P2 y& v9 Z1 R) x7 _6 L
* l$ m8 k+ X) a* Y' j$ |
( }) g/ j* h5 K0 g$ A8 D7 b
" x. {/ }; F- D6 }8 _3 H
9 T8 ~) Z! m/ y5 ?) H8 E 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid X, l- _* b5 \- b% }3 x z$ g. `
* ]# l5 n9 [# J' n6 z2 I6 w k0 v! }- Q. g5 h7 Y( {3 Q1 m
不过实际利用上会有一定的限制。
+ c! }9 ~1 e; g
: ~) u7 [' |, i4 }1 `8 ~* q
% B8 F0 |) B& A( f0 E 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
8 f L7 y3 u. L1 |$ i
: Y. u/ R, C; j# ?" ]% N/ @6 G6 U: ?! d) ~, O u
# ^3 l+ G1 n& H: i
. t2 l+ `" E! o* K9 Y
" F1 d L1 Q4 z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
3 |: e" C/ w7 s: {7 F+ c
$ u5 R! c. a8 ~
$ P2 v9 {& T. s1 I4 z 省略...$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]);省略.... o: S- r5 ^1 }
+ C8 ^: j7 ?3 j/ u3 y3 w+ J( m+ W" X- \8 n5 m
因此要利用成功就需要条件竞争了。
3 U0 N) Z* g6 r; H
( Q2 X# o) I6 n) J( c5 o8 J8 E) Z/ j# Y+ S _8 x, i
补丁分析$ m, `/ T1 j& R* u
+ t, {- W) X3 F0 Y J3 w4 l7 L- e( i( g
+ P# n, N: M3 Y1 ?
* P0 t# P- Q" }& B! W5 W. T
8 }- {; D& Q m- n8 W6 u 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
' `, H2 u, S6 ?9 g G # N* @; F6 V8 x) X# E4 A( h {
% U% {; e1 R8 Y, x
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
' {- ?- B, Z0 F5 B: F: j5 {$ H8 J p) ^4 a4 ~7 l. i7 x3 m5 q% r, V, d
% `) V8 ]! O' U" F! w: l 6 \7 S) L0 t1 e* ~9 B
) r$ T0 r; W: s+ D
9 T5 b; Q) B3 U! L# M 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
4 ~. w2 h5 ~3 K2 Z. T3 Y- ^ ( q0 \9 C7 O- r2 u1 q, M. D+ f
% F3 r+ o9 o2 c: ~
在is_allow()中增加对$this->savename的二次检查。
) Y: j6 w9 P( E" f3 R2 _" L8 } : T0 r( @" t/ _& A( ?/ w
. N( n# N. B% a2 c/ Z
最后7 ]/ T$ _7 [5 Q" x
Y2 z2 i9 ~8 L) g n, z3 c) L* ?4 k
嘛,祝各位大师傅中秋快乐!, ~/ E$ S4 p: m2 T* {. D6 V
+ m5 O/ A7 L6 Z
* A( r j; |" g3 |
. m0 a |, O9 ~4 M: | ' j) n1 B) q& |* v
|