0 ?2 J$ k0 y! b' e' {+ l% V3 K+ i/ J4 ^: r' t
+ O6 F' C) i1 Q
W& C$ l7 R* H 前言& E$ W* @, P/ L; ?$ J3 G
" q$ H$ i2 e9 }% G3 x. i" A E; A: z9 L* q! u- J) x$ e1 J
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。- q8 g4 b+ k" E# J! R
3 Y/ c6 n$ d1 S0 B
7 E5 o' W" Q7 i1 i' @
2 A- h" P& q: F7 z9 I/ q 7 U3 o' X/ { m0 O7 [4 u
+ ^6 r. W' L# d
漏洞分析
/ {1 R% V4 P3 N8 {& S
$ a! N$ [. c a4 \
o) _5 a. J5 Z 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:- d2 f* Z* l( W) C& w3 Q! t4 }1 L T7 {
/ T& Y9 {# O: U$ I5 Q
# z( m) n, v" S' F
# x9 Q) V6 j2 Q& l; t. D
7 C6 V% i0 F+ X& C+ W
" g {0 D7 Z# e; }. f3 C 对应着avatar.inc.php代码如下:
7 w }! R% t+ G/ D( G: [
, s, m7 K% w1 ?* m
( C, |! f/ j5 f. M# u <?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) {1 R ]5 ?) J& A( d/ X
" `0 D* c2 J8 E, y& J' J) b/ X" e
+ F5 z. K( w# r2 J$ Q
case 'upload':
! o' p1 f, Q7 n2 b9 ^, I, | + f0 m2 e) t9 o! n) p e
y$ k. `3 ]# t$ [! e
if(!$_FILES['file']['size']) {
8 }) a$ n; K0 \/ i, b
% J8 n! `* a; b& R8 e g& o9 S- n5 G- c% @* J8 R" [* T% L( U
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);4 l( W- n8 b4 F# Y! a9 v. P8 j# q9 v( P
/ i7 k+ \, D$ t, f: Z8 o7 Q$ Z7 J( z
+ `7 L# z. z" f; o4 C4 G" s exit('{"error":1,"message":"Error FILE"}');% b1 i B+ ?7 O
4 d2 @- D; i2 Q0 Q( X
# a) u, O! e1 X5 x1 V( c" A8 Q }+ E) H+ H1 {/ C( O9 g& z ]
6 G) W0 F8 J1 G+ H& t2 a, |( \9 k
* D4 ]. G1 H" X% D( S( @6 E/ w) C2 ~7 r require DT_ROOT.'/include/upload.class.php';2 r/ ~4 W$ E2 A5 i3 s
4 Y5 _% H' H' w, {1 n( M/ j8 N( c; M) a; {
8 M6 ~* e8 [% k# N
' [& v+ q# ?" v
* d9 J8 B$ m# k8 Y& s
$ext = file_ext($_FILES['file']['name']);
1 w3 G0 _0 a5 {8 G8 c' E / b F8 n/ [; T4 c
2 d9 j4 ~: |) d( o7 |' Z& ?7 z
$name = 'avatar'.$_userid.'.'.$ext;
n: N: ]" h) o" r* X . Y8 ^* f- B: J# ^9 F7 e
8 ~% C% M' Q4 O5 P- }9 B
$file = DT_ROOT.'/file/temp/'.$name;
( {9 n$ a# [5 a ( z- l0 G! H3 H3 R! I$ t
+ j5 x, }3 Q; y) J
T$ M9 t3 V( e- j" u : A* Q8 I: y S0 c3 | o
6 A% b$ y! T& h' v! Z: N
if(is_file($file)) file_del($file);, L/ N+ U6 @! V' ~ q$ O6 i. `7 t' s$ l
( @ |" v0 n9 V2 q0 m/ \6 i
& O- i: ]2 N, D $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');' X7 l% G% o0 f4 [! p+ c4 e
8 r6 D6 K% Y* ~
( W9 @, Q" M/ `
! F( U, E% `" ?7 ~% h* g$ ?7 v5 ] / k' w5 [2 i% P0 m% @
/ M2 ~6 i; L! i; `6 y* b, v8 {
$upload->adduserid = false;7 C1 p7 u1 X' q' w0 N2 a
9 [) K$ x' A2 ^3 w. Z' \
# i, g1 W+ |& \4 ? v
; K, n6 c; K3 S/ H0 r3 v' [0 n
2 M' E( p, C3 q& j
' l2 r, L! X& {! d if($upload->save()) {
9 W1 i5 y3 f, n# O1 m* r
! e; Y% v$ @5 v9 `6 d
2 w$ i2 H! s. l4 F; n ... v4 E$ E6 r$ B1 s7 R1 ~
5 h5 ?3 r2 A/ S+ |5 l- e0 p' q' ?* Z3 r5 G5 D# Y o. ?7 ]; g. J& {
} else {
( }9 C$ s6 v d4 a. d- H) n
0 D; H, k" M8 c2 e
( O# R# P; Y+ W ...( {% k7 ?( H' T" I# J5 ^: `0 n
) H; U. X( H8 H3 a5 y
* ?! p, j# [3 C3 J4 T. S2 H }5 m$ S# I0 t; N6 H* C
: `3 t; a0 W3 `' c
/ G: P6 e# K4 I break;+ t& g( j3 K7 S3 j$ }, a
% [& U% w3 u' v& L+ V2 X0 G1 l
4 ^! R3 R6 @( r) m3 K3 S/ {
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
% E9 G1 z( f4 }6 _1 c8 S 4 J9 F% S0 s% _/ l
' T- d# o9 q/ ], p4 j* x upload对象构造函数如下,include/upload.class.php:25:
. X |7 b. d* a1 _' n% L X# S% [% q9 X+ B% O9 i N
7 q ]# R4 o( {+ U+ \& t' I+ v <?phpclass upload {4 e, @% Y5 P4 x; c
) ^) t2 |1 S- T" s- L+ \
& w: C% R( Z' x3 ~8 w4 F( ]4 t4 {7 P function __construct($_file, $savepath, $savename = '', $fileformat = '') {
" ~ k( z: D+ w
+ C3 x1 ~2 A+ D8 {. j
% @6 F* f. y( E+ n0 X* ] global $DT, $_userid;
M6 n( k2 F0 R* X4 G6 l5 J `5 Z( K$ }" U- j5 i
5 Z1 r) Q+ }1 m
foreach($_file as $file) {* T2 D$ R, B4 [% M0 E+ | z
4 |& A/ j9 g5 Q8 W
3 h7 U0 z H% e) }6 {
$this->file = $file['tmp_name'];
@! |0 L/ s" J9 k, v/ j3 N . f$ F( Z5 Z3 D7 [+ |
" K% [$ e v0 m. q
$this->file_name = $file['name'];% C9 W7 i9 o9 _' e
& F! S* v/ v" q8 q0 D' _
& e0 p& a Q% m* Q
$this->file_size = $file['size'];
: Q$ x3 j) M( f9 C& k 7 V" e3 f: h4 b! J3 x7 W/ W8 a
: T* Y# W' o4 V8 j; N/ M$ g6 F $this->file_type = $file['type'];2 C6 k, i F, k F6 j- `
- E; n8 R* w* v7 e% g# \' |" g+ y! p
5 c) i# | x- l8 G. E+ R $this->file_error = $file['error'];
9 ?( P0 S6 n0 g- t% \, O
- z- J; ]' S1 y; C+ G2 `
) _! Q5 P& O! c
$ ~6 W' ?6 O/ t( D/ R+ a) @. [
8 l& m$ k" K" U* a, j/ x' r/ N# k: _: b) w0 T L
}0 N/ K5 ]; U+ W
+ u7 \/ s @0 ]
& F6 }; \4 a6 N& s# j- v2 ]* O3 Z $this->userid = $_userid;2 h0 r4 v# N4 K) V& j c
2 l3 l2 T C. I; E5 U
6 a W/ W9 v8 h2 V $this->ext = file_ext($this->file_name);
* I G0 V) b1 d- l4 i7 _
# V3 A$ |2 p _/ `( A7 t) e# h! U1 N( \5 B a9 B
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];) Y* m1 }$ R+ q( c" N' z% R) j: o
2 E4 t% d# N- ^' t8 G9 O. e1 I4 B
' L; x9 J! _3 U: Y( p1 e3 b $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
$ S/ c4 X2 f. a1 U: L/ e% }3 z) G ! o' B1 F# X! Q7 t _
5 \6 r$ b' _7 j/ d, Z6 g/ [) ^
$this->savepath = $savepath;
7 `' V: f/ [0 d: H' \/ G
. c+ \; f+ m4 C8 _+ P) `' o5 x; c4 G/ T
$this->savename = $savename;
+ d3 I3 z, I2 K( s - o4 i) k+ o2 \0 V
4 d* {6 L1 `; B }}& I8 V4 H+ x, U
1 ]4 r7 ?) d' b
7 c; Q1 J6 I' j0 m" {7 E( h$ f$ r 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
7 l) ~% b* f( N" G; r( } j" { 5 \$ y/ T- W* o4 s) |4 H
; [7 v5 N% O8 x& ^; k5 s& |
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 , O0 W2 h* ^$ |; k
# |% U( _1 c3 F
6 Q' O6 Q$ ]+ W8 k8 k $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 k% X+ d0 G) h4 K D
+ Z. Z$ f1 f! ~5 n. ?
9 D' p) F1 {: c+ P0 h& n/ U0 I( ? 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
# m3 F7 e; f/ [; R0 D2 B* {
& ^ y, e5 c4 `/ w- E2 r8 {# d; }. G/ a: i9 t: [
" K! a- S! }5 s) U- c
& o2 B+ Z/ O* m) B2 l9 A. L
7 z7 Z/ ]& q! d7 z% N, p5 b# o 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:' p% X3 F' t5 [, u! W2 k
" B; X7 L7 U8 \# }1 K/ l* e7 X2 r- W8 M
<?phpclass upload {
; O- l& ?- D9 w
8 B: l4 h; i, ~* ~5 y
% B1 O2 Z) \# x. {3 P function save() {& Y2 P+ r% Z$ u, P7 a: Z# Z
6 n+ Q f+ M ?; {$ M, r
) Q. p7 u8 J$ _9 ]' B4 j0 [1 U include load('include.lang');
`& `* C. H- t 6 b2 N- L5 g# ]7 |5 @. ]
+ d. y/ u8 y- b% r3 M! v if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
) E$ t; M/ n5 z: q
( \( W6 [2 ^6 t* d# t
5 e. l( k* @) h4 u* N2 N- x2 |
! X" j l* i. L * M& F. K) g" l
7 Y+ r( L6 `8 ~2 ^+ b( |8 y, X
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
$ v9 F9 \7 o4 }# w0 o% {+ K
& o. t- {. P! S6 T w
6 d+ S1 Q7 u" u8 ]/ h( ] 8 Y. @- _6 g. W/ K- _* ?- V
, k6 s9 ]9 a3 B" ` J7 q# u- d2 S5 ]& m4 ?+ x) K2 ?/ H7 N# u
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);" b9 s$ C5 @ Y, \: x( c( W- j' x
/ ^4 S {7 l) _9 \ K( K& q* f5 v# k+ ~" \* l
+ J+ M3 T& w- s5 `6 s7 l7 L L 7 j; @8 W4 M8 O; W9 X
: P7 k% g0 i' E) X5 D, X
$this->set_savepath($this->savepath);- f7 z& }: S, w% B) ~' L$ ~" x
5 ?+ A- |1 q' s6 o
~2 i( L# i0 P# n# u6 b" b; Y
$this->set_savename($this->savename);1 G8 p( W" L0 Q) c$ N0 w
4 s4 o7 g1 p0 o
4 F8 V' o- h, I3 t4 C1 @ - @; f3 K2 G! T( D2 }# K7 O& ~' C" G) a) y
. {0 p! F, l4 { {/ g2 V. S
9 M2 h2 v3 g: t" A& W8 F if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);3 K; M E8 M" V* I3 r) q2 J" Y4 o
+ p: n; r2 u5 b* V8 H% Y
F' w' j+ h0 K& G3 P: `* d+ Q
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
3 Q' a/ e" ?- L: N3 y
: s' M4 F# K2 U9 w0 S* s
4 E, R& j! u" g' n7 Q6 @1 N( }5 h' u if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
6 R! n4 N3 [8 [$ {
! k- V; S8 ^, @( E+ P2 V. b2 C
6 J2 R8 [, }+ p: J* P. e
- ~' F% B+ f7 o- ^3 t3 l5 ` ' D- P8 K0 d+ p7 M5 l
' m( I9 `( H% d( [ $this->image = $this->is_image();
6 E% F5 a9 i- {+ k. k" B 5 g1 x9 C/ k o2 n( N9 Z
6 f- v- D+ g7 @- Y: i if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
7 U& I0 I9 b9 J3 Q" A " T, s& f- D% w1 Z
! @6 r3 B) s( E, K9 r6 U( T
return true;. T" W Y3 t7 j" x7 e# N8 Y- `$ ~
' S% Z R; w. b' Z" e6 l
* j. b/ _# R* x }}1 L( L6 a n) Z& G
0 e$ Q- i9 h1 x
+ d H' f' C1 A1 ~+ P1 T9 U, { 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
3 J+ w3 o5 y9 C& k/ G" o
* e7 }" m9 p3 Q) J
/ u6 O) u' I6 f& R( G, E! o) x <?php. e* D$ P# Z7 r8 r% i7 p% b+ G
# @0 \# n- m6 ~- C' o
: X1 }% L$ A/ [+ Q, h' B5 F9 q function is_allow() {2 \, a+ E( k9 l+ e4 R3 [$ _
9 p: T8 v3 _: p* X" O7 L
- _+ {2 H/ D! P( V" i! M5 k, i
if(!$this->fileformat) return false;# f! w( s- z$ {% G; J# z
( y; |$ F! I& _* i
9 g" i- v$ @" a
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
- T$ N; y& L+ @, |5 p8 g% d4 W
% w( w6 D5 Z' o6 L) i$ q0 f5 v# r9 L# f9 V1 h! X d1 S; t4 h
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;: K I2 v5 ?5 K+ Y, e% ~
- E9 p5 n: c+ R( k ?) O% w: s- K, K& P' J( }$ M
return true;2 T( Y4 I- h) k5 E
2 u( A& I3 t. [' D$ P
, C8 d3 U! |" n5 |
}) \$ [' G* n* f" M* i
7 }5 ?# K% g! K6 R9 w+ Q0 J
4 E/ |6 S u& x6 {0 H) U 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
: _# K! j0 Y- `2 m ) {6 s9 ?; O% _3 m- r, K
" [( ?$ |! L8 u9 e, ?
接着会进行真正的保存。通过$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文件。
3 L' Q8 u+ N! ?" j/ c
" w" ~% O* [3 |5 J6 A" g- \7 M3 |
- [) g9 B: C. o7 n; _ 漏洞利用, Q: y- H6 p8 o& n; N0 Q1 F
6 i* m1 R- ?; {- A
( g2 \7 c) K; M) x2 d 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。" f- s' _0 o: y1 E4 l: m5 Z
, B% Y& P9 p. j4 f, {; L3 v
, G, }0 G2 \+ q9 B [# a7 V1 q9 b- j& T3 v/ v
% [* b0 A; L( J* ?/ v
( o4 Z2 d& `# b' C
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
/ l( M2 J2 h' l: l% z 3 f# G$ r1 y2 a$ R/ g2 f, n
8 g/ n; C! K( P 不过实际利用上会有一定的限制。# Q2 E v% z, q" `0 @/ H0 |; L
' C; ~+ L/ u2 I: \' o8 I6 W$ O: M6 W3 y6 J% a
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
! b& C/ Y& D, i' ]
1 u( w. v8 b, `5 m# P [/ S* d' I3 D5 O% B
( }% B* x3 D8 f$ l+ L k; R6 A; x& A$ c" S, n
$ u7 h' N, P' T- F1 N
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
7 S' C9 j, R3 u4 B5 G0 k 7 l# b. @# ^& M+ S& k0 e$ s- [
l2 | ~/ p: u5 V# Y1 s
省略...$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]);省略...
7 d8 q* O# L& I/ L5 [ ! i0 Q9 | D5 x$ z( u6 _# ?
! f) q' z( m G
因此要利用成功就需要条件竞争了。4 j( a2 M! h7 O' L }
. Z/ Q; T4 b" u1 d* {, O6 i' S
补丁分析; E4 z% A/ l7 c* x0 r0 d; i5 [
8 ]+ Y( q, R' q4 A+ Y! h( c4 N
2 y( J8 P& z0 c 9 }: i7 p9 X. ?4 W
& @! C& e! }4 C+ c
0 r5 H, ]$ E* Z1 h- m9 F7 |6 a
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:7 r5 }4 \1 w1 u& D; X" p+ S
* n4 C: n& i5 V
; u. j# V# V' [7 H9 A function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}" W: g* i' k$ \9 S6 S0 t
7 b; Z- `/ F! K- @* J5 S- q
2 T3 a- C7 q, H
0 _, v) n7 R' i7 M: A0 C% {+ u3 M( @
( C" J3 U2 f, v x. m2 ^
0 h+ }' @6 A+ t: X0 P% E- L9 E
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。! O8 G- {7 Y5 K( a* h8 ^8 v
( `0 E8 }; L. Z) O, @4 C5 d
6 k* Q$ m1 ^5 ? J, n1 {" O3 I
在is_allow()中增加对$this->savename的二次检查。* \+ N: O1 [- N4 W
* P2 i4 j( g$ a6 X5 J
. d' r0 P8 D0 c
最后
3 r* Y8 C+ \$ [7 g5 l' j. ^5 r+ t8 Z
n( [/ w1 u j( |# ?' X+ L
嘛,祝各位大师傅中秋快乐!
9 ], @1 j9 C4 B . m: J0 F, U. c4 z/ r# i& N
' F# l; g& J/ F ~) ~
% n, A" _3 q* J ; c) q. b$ m3 ]2 j- h5 v& o+ ?4 G
|