. D. M8 a. ^& o2 q
: ?# d) U/ v0 Z8 m% e" _9 G
* Z. V2 k# Q2 o1 ~& F# ^$ h2 s& B& P2 f; g0 N2 h
前言
; @2 o* i& x" G* h
8 Q s# [& J6 _) g; Z) u, C' L. K5 M8 E, h, \7 {
2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。4 r- |! X0 P2 o ]
7 ^' c1 p; ]' I$ |% N% M; D
& u( V' j% Z% I5 a/ c. D C( G
# y& Z( ~5 C B8 m
8 E9 I0 k! t* A8 ^2 v
8 _/ o1 }. y9 R. ]+ n0 y ^
漏洞分析: W8 Y" N, l# `! y. K s+ G
. H) k3 U$ l7 h# l1 N6 x0 o4 Q5 k; ?. d: f
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:1 B' H. D$ P" P
; J& h( `7 n: o- @. K& Q+ X
3 b6 `, M! K0 }9 a8 S1 ?
+ l7 p" a O# o8 s; s* y- c: b
" |5 I3 }# t: S3 {4 m$ i1 \9 ^- X
4 [# e* |& ?) g% z
对应着avatar.inc.php代码如下:
0 E% N- g0 z6 K) P$ D6 H 4 h; K) u" s6 W, w6 x/ h1 ]5 [
5 e, _# ], o+ I5 I1 u5 G U% I, } <?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) {
! O8 `( n6 e- m% i5 I E1 f
. r- M6 N. P, @: c. E& l% O, b! Q8 j+ ~
case 'upload':
0 i+ r* H* ]) G" Z4 D1 ~6 o8 Q
! n/ U+ N" H B9 A! L" b- i8 g" J( \6 C N# \
if(!$_FILES['file']['size']) {5 e8 r# I2 b7 ~* _- R+ q
8 Y7 r9 ~ T7 Z/ m) x0 e% f
$ T; q3 O: L+ R, D2 a: j" p4 ` if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
& r7 g# S% H5 h/ y" x; m/ k
) }' B! h4 c% F, m1 x
: G+ ~$ l- t! h- \ exit('{"error":1,"message":"Error FILE"}');
9 u- S" U0 D4 I) y9 ~. r7 ` 0 r% j9 a4 Z1 m3 e
! c- E; x. Y( T }
) L- M, n; C# w5 C7 O' M ( X% S( f- G* f) u! f
5 j5 U' Q L- }1 X1 R require DT_ROOT.'/include/upload.class.php';
$ W3 G9 \4 W6 o9 F/ O8 C- H
- p1 Z0 j8 I" I5 G/ g ~; {, N
- @$ t+ N3 N' Y: |9 U
" Y& k* Q; Y% y5 e' n' E4 b$ Q 6 p x$ _# n _9 l' K
( }% X; \! c" z' H
$ext = file_ext($_FILES['file']['name']);
) ^& k+ L% E* \" X 5 k8 |0 Z1 s) Y" X
J: M' v4 f# L $name = 'avatar'.$_userid.'.'.$ext;5 D7 S6 v1 S3 C2 a( c4 O- [* P
+ Q4 F; _! U: B3 M
+ O5 L) ^& q/ _3 \8 I$ u; ?( L4 \ $file = DT_ROOT.'/file/temp/'.$name;
: x& b- H; J7 Y, Y! M" l: p & A2 t5 h) j }- J& ^
( j- z3 Y5 y( W t( V0 I) P8 j- p
0 S# q" t# l) A6 |. k* F8 | 4 w; U5 ~+ Z5 ~1 V& W/ v$ `- U% E
+ m/ c' O% A( T! v: j$ u if(is_file($file)) file_del($file); j; Q: H- ^5 i
! E# f( i: w/ @0 h6 i9 t. Z: U5 b
. V9 q5 d0 l+ Y Y4 N! B $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
" u, n3 d4 t) A7 t, U N" ~ K) b# o" |: [
; E9 w; j4 L3 c. k9 L8 u: d+ e
' `9 K1 {9 u: U) B
* q9 A$ ?& R, a* z
' e4 O4 H# m8 }4 l $upload->adduserid = false;
% `7 E( r$ R+ K8 J
# p. P$ h& u% k" D% l7 z! B7 |4 t# d0 Y: h; s# n
6 P. y: O* i) e
0 E8 `# H8 @8 {# f3 `) v9 p% U" k j# e. W
if($upload->save()) {: M' G& P' C* E
& d2 `! h' }' o: Y
+ M& ?) R/ |: Q' `$ Q9 t6 u% [
... r9 v( ~/ A+ I
$ A" U: |0 [/ [) v/ h% h( [) Y
4 ?7 F4 f& w* a: S7 _8 G# ~+ D6 B) T } else {
/ w: e! M+ Z, g( p
% M5 [1 a& W* c- k6 ~- X3 t' ]2 K, i. ^8 a; B) [6 |
.../ P4 v& a; ~8 h* |4 P1 {3 V: E0 i! w
. C! u: u" x( e0 ]$ r
h1 ^. W! Z: T; @ }, T7 ~5 u! u3 |7 R2 N d
) v) }' w I0 u% L! ~
5 G. O* @" ~; P* m+ d break;
$ l0 V! V: j9 f. ^ : N; z" c& _! n" B5 E8 s
& R$ Q, i2 m" q) n0 v0 E 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。3 d" v: N7 N1 n4 q$ s7 Y. ^2 z
a: V: u; P2 M. a# q* ?8 U
e, K( ~% `9 I3 P* _: Q/ v upload对象构造函数如下,include/upload.class.php:25:: z! r/ g8 f+ @7 C
% B' V9 Z* O1 Y5 @% b
9 C, @" R2 ?1 `- q9 Q9 x& { <?phpclass upload {
9 [7 r M. _* G) ^+ L/ ^) F( [
: S- h7 ^. m; ^2 Q$ n% p# K1 N
7 V x# |1 R% o: R- Z: o, } function __construct($_file, $savepath, $savename = '', $fileformat = '') {3 k0 q& m& o4 i! ^. t1 M5 `
, T) q5 K! e/ b# u4 {% W# j1 S- U
/ U: e( e% k; b- ]( ?6 W
global $DT, $_userid;
6 n6 S$ g* s- y, r, N+ O4 Y
4 w0 |9 R7 h( C, ^ k5 d- ?% X7 M: ], Z+ r) ?+ ]4 w, N" z
foreach($_file as $file) {
2 o" a9 Q r: S j7 d ) B3 h% S4 ^: O2 p( ]0 l9 R+ |
1 I, e: o3 f# h $this->file = $file['tmp_name'];, V) N1 F8 j M d
, ~8 M- _9 M/ R9 y; E& T
5 H6 i3 m1 V5 y# ~ $this->file_name = $file['name'];
- _' R' Y7 K( H V ) x( m; l9 ^- x; y- M8 G
( u1 s; M/ N3 Y $this->file_size = $file['size'];# Q3 V: A/ k9 C2 ?" ^
+ a3 w. _: f5 c0 R& ^4 G; h
U+ z5 e7 r+ j $this->file_type = $file['type'];
0 Z. O z/ j! \0 F3 T Q# r$ Q ! `1 J' s# h* }; V
$ S/ d J1 \9 N* o( H
$this->file_error = $file['error'];8 @, s* t9 m3 x: u' y2 Z
0 G3 W: O: n7 e
5 n6 J4 V- R3 c+ n7 P; K+ V
% y' F4 d: h: g* z* R
3 u/ z& w% K' \4 R/ v
7 L; s% E8 T& G }
# W! Y7 t9 m! b3 w% c A. U 8 _, S( b2 A) d! c" `0 Q9 A/ c
/ w. c. B8 R- W. ~) H/ n# H $this->userid = $_userid;4 y7 f0 N0 [* y- y
+ P9 k- r% {) k4 y2 i& f7 s
1 J( K' k6 T" p4 [9 v0 T $this->ext = file_ext($this->file_name);
. [+ e* i. P7 T" r 6 q4 c! r( Z2 D( U$ s% {( g
7 i% [( F% i7 U
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];2 I; a/ ~, v4 c: M
; B0 D# P% r7 B/ E) q: D5 [
/ ?$ t$ W7 S# ]- s $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
& E! M5 b* w9 L) i* c + E8 ~5 l$ `9 Z, L" X
6 q' E0 {& F) d- g& O d2 Q# ]7 L $this->savepath = $savepath;- O; X9 ?4 m0 D* R( @ z0 O
1 ~6 L8 y! S r3 [
3 T! I, n8 a; {! L8 D
$this->savename = $savename;% f( b- h) w0 a
6 r1 a& l& |; J6 {2 j7 J( {. ^% i, w( T8 c1 X; x# j. \! i- `5 v W
}}7 `8 Q" n/ X6 `$ L' y/ ]5 F
/ A# J4 p* I5 z A
) j4 y6 L# B4 {4 Y
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。" O, P4 Q3 c9 d0 x8 W b
; a* F* G: f# Q! C8 F
- g; l/ B1 @# b h 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 " A5 W5 }8 U+ q* c" V' n/ I
. A; g5 ~5 h; u8 P& b I1 [4 }+ A
6 j" M9 j* c1 b( d2 @5 t9 ^% b% H6 }
$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
" @7 _7 y% _7 f, x" e* o
0 z) J$ ?1 @- S# t, N! C( }4 N
; S4 x) X6 S. k' V( |! ^0 C 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
) |% \+ v( ~! v) a6 r) X, Q; | ' I' [- W/ e+ \- A; d9 ?! Q
( T; I& ]* p& ~0 s) v
8 t8 |2 |% R$ }6 h
9 h) ]( N; Z# w+ I# v$ Q7 _* f
4 i: S' r, O. K0 y Y 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:% v! I- |. }9 C4 I" e" {8 ?
, r+ \" ~- R1 E3 B4 ^# k4 L9 x% z# R0 k( @
! r8 Q+ f6 p7 r
<?phpclass upload {& y0 ^2 o8 f8 a/ R: U
2 g: Z3 g' r; y8 C0 t: @' _
' ?5 r _9 g; z! t# D6 v
function save() {+ Z3 ]4 t( B. E! y2 {
, m' O/ ] v) `) a3 i: I" x. `4 }/ _9 P& x. O) ~. d$ x0 T
include load('include.lang');' u* @" V7 \& J
0 h# ^) O2 k; {" O/ U
- j9 X( T* s3 A; e9 V4 s if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
& Z/ V# V* `9 q9 g , q' i# I" u9 v" D4 l
/ L1 j. k3 X- l( e5 ?. _2 l& O, ^
: \* f3 e1 g3 O! O$ k( i( _
$ R* O+ T) b" Q) p/ k
, g' v+ u, F) I3 \
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');" S7 n, z w' x: O) J
" W7 E8 n3 l5 B9 y
0 f1 T7 }* [6 o+ b 7 Q* N, ~( M! Z* c5 v9 p
" t# j. R, U4 X$ l0 h
# X6 q3 s! p$ m) V if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
" o& v/ n/ F, b D . A4 ~+ e+ u1 A: ?9 D
7 R0 V! W: u# a7 w- o
7 ] i8 w" p& q( a5 |, ~' a# A# E 8 ]$ f) I& n6 ~1 i8 |% Z8 M
8 J, B& M2 i2 G l$ g7 k
$this->set_savepath($this->savepath);) y9 ]4 @8 U# C/ b; }
' W- g! h/ Q$ g) C& G# g' Z' _; w: p: ^- c6 K) o8 p: i
$this->set_savename($this->savename);5 X- _) S: q' k$ X8 X# Z
) x$ |5 s9 A/ o6 K5 M4 o" t' X. B/ O# X, c# _( s
4 z+ d M& E Y8 {% P' `5 X0 u
" q" t- I* G% t- A' M1 G
w; ^9 e$ h4 P$ r+ h T0 R( ?5 e if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);$ x4 \& `: a% _& F
& Z9 ?4 n; }4 { B% D! L8 K
2 C7 S% ]8 _/ {$ o' I0 Q/ f" X if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);- t2 ? K7 I3 H8 z: i- C
* C. k7 {& O; C
8 a8 y# k' g) F: s if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);& g* h' P7 T) x( H6 ]
4 ] w; r1 G- e, f) g$ B
- T" E3 n f$ H
0 x: Y6 G# K, L6 d/ E
- ?3 k x7 B* o1 c9 `3 z9 \3 z1 X- @" P6 b$ a8 O
$this->image = $this->is_image();' r$ A/ g" `. Y. G6 X# M: }
8 H: M+ b3 t2 @( V2 P
; \+ Z) ?2 h1 ^" D: S/ W! z
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);& ` f! f3 f' m3 s& y3 C
, o" y f- {. n7 o3 z1 U' P
9 w/ v5 Z$ _: _- s return true;, w- Z- |5 E8 P5 w: G, {/ i
) B T$ \: [2 Y1 c# {# s
' I" t9 k' a$ v }}" E8 z2 r3 q, a: R e+ C W+ Z; j4 y
3 `+ U$ f4 W( t; O( U5 z/ _" Y5 v+ q2 L, |7 t
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
l9 Z6 e7 ]0 e 9 ]$ d; j. y) N" c/ M
0 U4 [8 I; I& t/ t2 z0 u
<?php
( t, p: U5 u/ W# k Y! A
# ^( _( G$ H1 v3 E9 X* @* k, v
& ]8 a$ S/ O0 o& ?& I1 u function is_allow() {1 `3 X q0 z( P/ g
9 x/ |. E8 A8 {( v# S' c3 o1 a& q2 g$ A: g7 V( |
if(!$this->fileformat) return false;
6 L, e# i: t& a! h/ k/ R$ F, \
5 E# a x( p3 m! g! y) O/ M/ Y1 N1 b1 y' |+ ]; D' @2 A
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
' ]2 m3 D# j) f6 e& a: A
& Z( m! p) A' s" J* k; `2 b
: W7 q3 X* d0 S' ]. o. r 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;
4 a+ q" Z4 S: S; E: f0 j5 X2 _ $ O! e. F/ k( `- v7 A) L
, @3 z+ ^- r7 S& \; d3 [
return true;# s) |1 t+ ?: ?
}+ E% |4 \* H3 H. r# D8 x2 {' t" b7 C
6 t* B- x4 R* q; V }2 a. ~9 N: m9 E
8 O4 o) e- }2 n# Q
, ?" ?+ r- l' \6 s/ A
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
' K+ D& u# M2 i# C. W4 H4 f
" T1 L( H8 u$ B7 ^* V
7 c9 Q4 a8 v+ 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文件。/ m* r2 e6 V" D V; j- s
8 H( T2 Z6 G1 R" O2 _
& K% m$ n" I: ~5 P3 ^ 漏洞利用
# Y& P8 K4 k9 r7 g9 k( P8 ?% w4 Q
6 x m$ F) x6 [/ e* z, U$ d9 ]5 n' g$ M, W: R3 ?5 p( Y' V. I9 q
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。9 @9 C/ A4 e% }4 d
1 |1 I. @, V! i; t# x6 g4 ^5 F5 ?( _! G6 x- A4 _3 l
& p; s* ~/ f& @) n. }) n. e 4 N4 M' u+ W. s
- C; o% p& P0 d& T 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid8 {# y3 N4 j5 z7 e: q+ S' E
2 T6 i) D) G. y% J- p- b- F* Z! d' b& R. I6 h w
不过实际利用上会有一定的限制。9 [9 g6 n! _! H
/ f! K, D! \: m! Q
& @4 f& ~; \/ j' h: Z, @1 g$ u3 s
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
$ s7 r0 B4 I5 {" n0 M; T" M8 W, y
# G$ q3 t1 E' F6 ~/ I: T( ~8 F, ^
! o% J+ k. U: R9 o ! R8 J: H4 F5 g, ?9 ^
) ~; H1 A: {/ Y! l2 w' Y& F: j7 d
! R! M. ?% z! G, `# B3 i0 a 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:2 \5 n f$ m) y1 j9 y5 Z
. G" k1 @4 o- Z
9 ~5 [9 o4 {" y$ E2 J7 D! Q+ R6 B$ B
省略...$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]);省略...
% H5 \- J3 S$ D6 s1 b - r0 V2 s6 D1 [
6 V9 X" L6 J# G' x
因此要利用成功就需要条件竞争了。; j- V! i! |5 p, ^2 z0 }: d& Y
) P/ v/ n4 U$ e
5 s% w: |3 ~# C: |8 C 补丁分析
9 f# `* z) {* o# i0 z1 @
1 \' ~( w4 I$ B1 q
/ o4 z) x7 ?; W. y. s" z0 u& m
6 d% o* ]+ J* [8 _# {7 {- z3 X Q" p' p0 b2 {& ?
! B6 u) o- Z' w8 c1 W4 c
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
, |1 }/ s2 ~' X6 _4 ~ e3 [0 A " @. D# M9 Y. Q1 W; a
3 J M- U+ W, b3 G6 e; d0 r; b1 B function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}" Z7 d0 N+ O/ \4 G% S0 K/ C/ Y
R4 e! q: B. \7 D: J% k
! s1 x$ B" z' r' |
2 l7 f5 i) B2 k6 P) o7 {! z ' t* J7 r" Z `# h1 ] \
# J$ D- B$ f: X) m) M
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。8 g0 W$ r* s5 O5 T" J5 _. g2 |% e
8 `; }$ t, N3 L# s& j
: B0 V" t8 S4 L b3 V+ w- @; I
在is_allow()中增加对$this->savename的二次检查。/ ^1 C }) j. J/ p9 g1 x
1 P5 @9 K6 |5 Z4 o: h) \- m7 W2 }
) T) F! c2 ?) s) u$ \' N p 最后
, Q2 `& ?* q$ z( ^' V; ?1 D$ q4 c$ `+ {+ X3 i4 j' C u5 s! \
' r) ~) e/ T- c/ e% B+ @8 a2 E
嘛,祝各位大师傅中秋快乐!
9 ?. b3 k5 \7 r% e, s E, K
& C; i' I. b- x6 i4 z1 w0 @% `/ K% I0 h7 b
0 E, G/ T: J8 v& q/ I* n% J
# A* ^2 U1 [+ V0 W% I. X0 Q/ ?
|