/ W, H$ z* ~! ^
. c/ B! J& [6 N! j, _6 L
% ` w* X# h3 [. l g
7 W$ F j! \% {; B 前言
+ n: ] X% ~% z5 e5 v7 t
7 |. Q8 p: z; H
c0 b+ f" Z2 E 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。* a$ `9 n; Y# Y* _: X' E8 K
% G- I Q4 d6 ]3 l
4 N0 [5 |2 B- l' N0 g
! O) F) `1 Q/ l4 ^ ' L5 r4 `3 a. G/ x
3 @ d, p) d0 O; u, Z/ A8 m2 C V. p
漏洞分析
: n: r6 \$ c6 u& [; P, ~: t3 i G+ |5 x
% ?/ l% s7 A3 x- k6 a0 O
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:3 Z8 f4 g" z: Y
( ]* P( A/ \# v& W- } \2 e) O2 t8 V9 W& q. E) J
3 e6 _; V- s! ]' Z! s3 S
- f m6 r8 H, i' \% g
0 G. Q+ m3 ^, M6 j. O
对应着avatar.inc.php代码如下:7 o% A1 E% r* Z+ f
2 m! \9 f2 E5 X d5 }3 m1 ]( N$ k
8 t+ M% U0 d1 T <?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) {) k3 ~! E0 e: B
' e: c' ` z! H6 @5 S0 e! D2 ]
& L Z6 |3 P$ v, s- |7 J
case 'upload':) Y3 }- I; G) i' S. d! ]7 _! h
j+ j+ a6 A" O: V
- f! D) l5 z) G
if(!$_FILES['file']['size']) {6 ~" `. ]8 L! d- N9 f
4 U4 ^1 r6 n# t& o% J, |
1 D" [2 X% n5 s! i- \; |6 j if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
* V) A& Q. k9 {9 y" b6 R 4 X5 a6 j7 w: |7 ?' q
5 R6 g1 i I; l/ b5 S4 O/ h exit('{"error":1,"message":"Error FILE"}');; g$ V, o( u& R; V
9 e- i2 u2 Y1 o. S/ M
: O) k+ n/ d/ D' _ }
' F! m) [* _: T' e2 \6 s3 o- b2 }# |
; [2 e X7 |7 o. a
* z0 s- j7 f0 G require DT_ROOT.'/include/upload.class.php';& S6 M) [/ H+ |7 ?, ?# Z
- N0 {4 X4 M+ {' A9 w; }
0 y; \; H6 x# c3 U0 A4 ]8 Z
( \9 t6 m- W! ~& ?+ I# j7 Y( g
# s5 u$ d6 r8 S, V' y3 y8 a# B
; Q+ O7 H' _3 c' e( g $ext = file_ext($_FILES['file']['name']);9 ]1 S4 r0 H& ]6 E* ]3 {3 z
6 b p- {9 t. Q% x$ M4 A
0 e' w* K4 Q- y2 A8 q+ d( X2 R $name = 'avatar'.$_userid.'.'.$ext; E# H% n3 J$ o& P" P9 J
9 h* B9 l- ^) t2 c7 z: z
+ }9 }* N3 _! X2 z$ J4 b $file = DT_ROOT.'/file/temp/'.$name;
% T7 |2 b$ e# q; i' `! z$ M
+ k& y1 F$ D6 T8 R/ f2 \5 n; H& u$ I4 c- H+ {) @
Y/ S( Z) D h G9 q" Q( H
9 Q" j- `8 d* e
# k( j2 M, I" k, Z3 q3 i- w if(is_file($file)) file_del($file);# M3 B- w# v6 n( c0 ^
" F- @2 h& H; |- M, Q# @4 f7 }) w5 D
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
- i# Q( k' V f* n! n5 ]2 S
1 q5 A3 S9 [) o" P7 r7 o$ _' K ^' Y5 ]8 N. N
* @ \ a. U# V - W* l0 c0 ]2 F% ]/ u6 F
4 q+ f* U& a6 Z# K9 e+ A, ~
$upload->adduserid = false;
4 M2 t0 I% N3 Q& p3 I
& |3 I& ~, j) j; O, k+ t. k
; t) N6 w/ M, X8 y* W3 A2 U
1 W, O2 Y" w1 Z+ k4 K7 }
% W( l# J4 z$ ` e2 F. N
8 s* `. b' U9 [: Y+ j% W if($upload->save()) {
% I$ t+ G: [: W& t " ]5 {) u K# c( s! Y
3 p9 o) O6 w& [ i0 Z& C& G3 E ...
: ?9 s7 q8 a" D8 I: C X) B2 H * t( o' _% P( L9 _6 A
) R. u0 c3 o4 G# X* h# J
} else {6 k9 Z' m9 }+ I) c# C
0 T0 k6 n2 d% h* L, `* |: a% L2 f/ p, ]; d. W
...9 b! i; ]# U3 M/ @ R
9 X0 A B5 ~& u' J4 }- D
B( i+ }% q& T: q+ O+ y: } }8 B O5 p- |2 T, @- x
* I+ m. u2 {' C; V
1 }) [, ?4 |) W% t1 P1 ?- T break; M! u0 k) ~$ N9 `( M0 j0 y A
% ?: m: |! w Y. n" F% N( y$ J1 C) x: c/ @, d7 t
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
. \ x' U. }+ G; E0 b+ b ! {4 W4 b8 K1 `, V% F+ Y
% T3 ~ f1 o; c+ r$ k9 Y2 |
upload对象构造函数如下,include/upload.class.php:25:
, q+ N: S' }: x; ^) W* |
7 _) t B# W% p
8 X; C5 Y" ~. ^( l <?phpclass upload {. z2 C) g, y6 W: T9 x2 @; ]
; u3 {8 Q7 A- l0 W5 a
3 C: S# ^, l5 r: Y* G w4 ~0 v" l% F
function __construct($_file, $savepath, $savename = '', $fileformat = '') { M3 C% r. q2 O1 V9 Y
# A0 ^/ y4 p+ J( t
2 E1 V8 Y P" c
global $DT, $_userid;7 E0 L3 y- q5 M' m9 ?* i2 H. d$ n6 ^: z; j
+ ~+ w2 F5 j7 ~' @+ M% M
8 B& U8 C" S% W; ]
foreach($_file as $file) {* [4 M. V. w' ~! ?/ `: A. L
; _8 t3 M# P- A# q
7 B! f4 b* O" P $this->file = $file['tmp_name'];1 r/ u4 W. |7 Y
7 c1 |/ J2 _' v# w* s+ j$ k. Z" h' Q0 p
$this->file_name = $file['name'];
6 g2 T+ E2 W. w. y- q4 H
- d# E( M) Q" D) m- z$ E* U; J" c0 y% ~8 X A& f
$this->file_size = $file['size'];
9 {) f( e5 r1 U. d* q( _4 f8 M; v( v - r! Z! D- C' s& o) ]" Q3 x; ~
0 a7 S; X1 g [ $this->file_type = $file['type'];
; f5 ^6 T: B" d7 p
2 m. _/ B0 |; G( B' a& U0 N! H! g0 h* V1 e5 i0 n9 }8 ^! b/ J
$this->file_error = $file['error'];# F6 V) A) k' C$ K1 ?, p6 h
' w* q+ \8 f) t0 v; S% ~
9 V N) k2 ?" n: I. |
* I% J- Q( V+ k) j& X
, _1 S1 j+ i( d; u- W
8 o( b, b2 X7 H$ ~1 o }" B+ f( W4 y- u4 @& Y. N9 |# b
/ t) B% Y! }% r9 A
; O& F0 u9 _7 \/ A $this->userid = $_userid; c1 S" V8 B, f- w2 m
& y0 b% m& N! K; B2 N: y% ?" K2 \
" ?7 T8 K8 j. \9 }6 N, }: p $this->ext = file_ext($this->file_name);
. {9 t8 B2 ?$ T / P9 L! }$ n. \1 m: f6 d
2 @6 ]7 ^" Q- g u, Z! @& @: ^ $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
. N# A5 V, V4 c* ?9 L
; \# ~. T+ z. u( {( T& B3 A
9 _8 @/ G( W; t- K$ v$ U8 y1 j @ $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;! C6 B% j" k/ z
8 l: S! u' {6 M0 b0 V2 k7 b2 c9 ]
* `* n, G* G2 F6 k $this->savepath = $savepath;8 a/ B' \$ b5 q
# i6 ~- \& t* l
, x, S7 K0 @0 Y L& \8 i, { $this->savename = $savename;1 M7 _) O! x7 y& B# G
. {* x# j0 s9 l4 c" D
# `* @2 j7 y4 W7 \ Z$ |$ h: Z
}} G0 U7 s! z6 ]) p
3 w8 V$ Z+ d" \: H. s; k
q/ @4 D u: {, S/ J7 L' C# J 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
2 u9 W4 l& k2 B7 C5 y 9 [! `/ R; G r
+ c4 U! N/ U6 n( w1 e
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
3 a: r- }, r* P/ V) v; g! f: _6 S4 E
& S; a) f/ j# I @
# Y, w3 D- A3 A2 B) [ $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
6 ~: P7 ]4 J& a: B: g; o3 ]
, Q1 Z( h& G! C3 j6 r8 \; w
' f& }4 `4 S+ a; g Q 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
& w* O$ h! g* I2 Z# e w/ L$ {+ M) o4 y
! k+ ]- }% u% K* G5 o; d8 a! o
9 ^3 T7 k' R2 \: @ ; `- `( Y! a9 U3 b) V( X9 p
0 Q) D! k+ {4 p* T& H5 H 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
: E; P$ E. f$ Z/ Z" X3 Q, O0 P% b
! F) g3 C& X/ C* q3 C7 f' G5 K
. V, Y- u7 T% J+ C0 `. W1 u <?phpclass upload {6 d+ p% B2 q4 [2 ^0 q# |
. v" y1 B5 B6 z5 B3 w
+ G" n' V4 Z; |+ K( Q/ _) L9 K5 m function save() {
; {9 w: U- P* q
2 N8 z; X, M5 W& o0 Q5 V6 t4 F* W: S# a. W1 m9 r1 @0 a" C
include load('include.lang');
0 l) J' `' Z2 {& q) C, T
7 @' P5 ~* o u) W, j% R+ l
& W. x3 b; g" t, K0 l8 n/ A if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');- i0 }. ]( l4 w4 F
]8 x$ l$ J z7 h8 d+ v9 e4 v4 h
$ _3 l1 y; _' [: z8 J 7 y3 [3 H$ c7 u; [5 B: H! a
- {6 G9 j7 D/ |% ?/ g. g" V$ K1 g
: r6 i/ R1 r! m- ^$ e6 b4 K0 n
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');0 j/ j: \% E2 P( s
2 n' j. |' t' U" S
8 a u9 P9 g+ A& L
8 Q& F' F/ G, h3 N: j- k# { " ~) k. V n( K( i. q
- b& I. ^8 C b- D/ I) D3 C if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);, |1 K- @4 h' D0 z6 \" Z
: M7 @5 o3 n/ R# i# M& ~( ]
# s' Z/ s+ ]+ m2 {' K. g! c! Y ) O. p I' G/ Q) U7 _
5 B$ T$ u# B/ I. B, M4 u
* O5 L/ r* S- l% u0 P' x $this->set_savepath($this->savepath);2 m# j) m/ A/ y6 C% a* k
l- R& B, |1 U4 n& E
5 J0 r6 X$ f& p4 m% \ q+ _' Q
$this->set_savename($this->savename);( v8 Y8 B" u7 q2 s6 n5 w/ Y
/ J, D. M2 t0 c+ [0 D4 d: U6 F2 U; C
+ X7 P! w5 c( @ 7 g( X0 T' g5 l- u; R- O
% h& H/ z5 T1 `! x2 E2 a
l3 O) Y2 j; u% Y# }+ o0 M1 B if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
% v' J3 V$ N; {) S4 ]& Z $ U1 n6 i3 [) O' C& ]6 l
7 `/ l' z9 P; h& t, ] if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);! z6 w X8 ]" m) A
1 v d& S, Z% k. ^& m+ ^- X% s @; m) e% `- \" d: ?
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); ^: j) ]' Y+ v+ R0 x) \. T" N
: C1 G, s0 U1 S$ w( t, B% m' S8 Z' J
3 F/ r9 a: A( Y% K. v
0 y. `; {7 X- m' c) E: j7 ?! j# {2 a3 ]
$this->image = $this->is_image();
! u/ o! ]' Z6 W9 B' U- J
4 F- H. ?. R2 O% G1 G0 h7 @
7 y; t b7 ^' ~3 Q4 ~2 M3 J+ F if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
# f7 }: V/ k' R6 p' e) G) _* N ! ~6 V7 l. O( @+ \3 U5 _! T
& R( d$ v: s+ P- A1 X S
return true;; m& C. U( J9 H& A: ] D) u5 E$ H
: a) D% R5 Q8 D" G6 @ C6 g6 L+ f; ?9 c' ~: {
}}4 D6 I: Y; l1 L1 y7 [/ _, k
. D6 ]" w( t/ E+ Z, T6 [+ a6 L6 n& |- [
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
, h) Q& x0 x M# m
8 y$ t6 t- ]+ Z6 L, a* g* q0 [
' _2 u( U) }+ c <?php6 e, Q2 f: X4 F, ^
$ v. i4 e {' d$ k
1 s6 G' }. W" S' {" t2 k function is_allow() {
! C9 W& \$ h2 t( Y; ^4 T. w % E: ]' u' l& E
& X- v- C) Z) N% f8 A; V& y; _; t- N7 m
if(!$this->fileformat) return false;
7 p7 T' y0 c; e2 H, _: J
7 }4 y0 N7 P/ r5 w' O1 z8 `( h% o% ]) a
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;* T% v. Q+ y; {% \) s
1 x0 [: D4 [% q4 r1 G
. Y$ E7 X5 X+ W7 `
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 V% `% X+ `( c; O
5 v2 b" E& k o/ m4 A7 _/ q. D- d6 u. U9 {. O" D; j
return true;
9 H2 ~! U6 p: l* B+ P& o - p2 _- j# I& A( m' N, m$ n3 |
9 \2 o) N+ K, B8 `9 b
}
5 b( j( S5 i/ ]! w5 b4 N
: j! h, i! ^# h2 D& Z
, K6 ]" {5 E! I$ n) ]2 ^( { 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
0 M5 O# F B: `: N2 ?1 C# d" O 5 W V& _( u( M5 t% w ]6 i' {
+ H( _7 ~ k+ ~; ] 接着会进行真正的保存。通过$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文件。
" {& L1 u/ Z9 _5 D
K4 `, w5 S. A$ N! y' L1 d/ h! f6 I+ j
漏洞利用) O6 P5 l! [/ F+ ]' e# E& U
: E7 m7 t/ o+ g9 ^% P3 @; e& u' m4 S2 x/ w9 o
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。( N' \& S; ?+ `0 z* |0 [
% \4 h* }2 X% R/ @: i% P
+ c' l! l( P: E5 Z6 O
5 Q2 Q2 `0 P. f: [! Y0 m. S
; M- }2 Y. \+ u* [ a( q6 W" w, O, w& P+ V9 k' a6 p9 l2 `5 v. I+ K
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
9 O/ H) ~: D x( [$ X3 B
4 \" \' a& t% v8 S* c- M1 q" U1 P; W8 i6 h# U
不过实际利用上会有一定的限制。- I& H4 u2 k- m* \# \
2 m/ {9 r U. \: d
* e: e& V. `- ]8 X3 ]; u: B
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
5 M5 j$ n; s" Z8 S g
0 m4 n, k7 I4 B& g) V: W$ `
+ ^$ n8 _1 u9 o! {
; ]7 q$ X! Y0 |$ ?1 b, E, M3 P0 v! ] 0 C" Y+ I) l6 f N/ K
* g0 D2 t+ |3 z/ @9 k6 @+ k 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
" {7 u8 a4 p7 i+ w/ x
3 y8 f* y: \0 z7 O! d2 C0 V8 i, h
/ K9 M. T- q: e 省略...$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]);省略...8 U4 n8 u+ {8 C1 E- s% |
0 O2 O, q0 i' }2 W1 Q# K# z
. @- W" s8 C. @0 V 因此要利用成功就需要条件竞争了。. T. O5 |9 C. ~% x
3 I6 M' a/ P4 m# e8 i4 _
3 U# o+ l+ ^5 [& T0 n9 N 补丁分析% G/ ^; l! e" L2 g; x/ I1 l1 E, A
0 w4 B1 }9 D& o, S g( C) c, a. P2 x' G+ R# k+ d; O( J1 j
5 ]" }; _7 G7 A+ U# L & E5 \0 J0 `8 M7 M# }3 U+ V3 P
3 e$ `0 y& C4 D# B 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:1 S& l. v) R! D6 Z- Z) m
1 q4 a1 Z0 S( [' }$ |' G
) |2 Q9 Z2 F, y: d/ r- a# v9 a function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}8 v4 _2 n8 h5 s. P; A$ a
( Z2 p# W: ]2 d( R, E' N1 a, Y) F# }
$ c/ O) w2 D2 p4 s1 z+ p * |& }# |) k% G! d$ |9 b" v
2 o5 R6 `# F% p# W4 ?6 z, \
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。) @' a$ a0 x# W, s
8 j6 z' O" E3 K I2 ^8 Y! `
+ k7 f9 k+ o3 i" o8 x* A; H: r3 Y 在is_allow()中增加对$this->savename的二次检查。0 ~6 `) ~5 v% _. d
. ~# V Y+ Y+ e% E0 B' v1 M$ D
4 t7 w- v6 V4 R4 N( c& z* ?) M1 Y 最后
8 x; [( k8 ^/ w) T; g' g- @& }. v7 N' Z% A/ }0 {) I2 M
! Y9 H! T( s5 R; ]; ?4 p, C
嘛,祝各位大师傅中秋快乐!
) Q' Q9 m) B. O; N
2 e5 V' V! }, e! y" t8 S z& S- O) R& R( g* p
) {) f% w, c1 M. G4 R* H 2 P* C% E* L& f( {7 \- x# I6 B) ]
|