t7 I, _, }+ o
/ O' @! D% {1 t3 L! I. E) G8 ~
8 ]6 z4 v. B4 y2 Z3 V
% |8 m0 G, C5 T. f0 o 前言
" J, O: Z7 G U M
* E1 e- g+ G' ]! g* z
( t# Q( R; ?7 t# x: [ 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
/ m% a$ N& a( \9 v/ Z! B 0 v' p% O8 K2 A& ]4 k$ B! t8 X
3 ^ E8 x% |! c3 H
1 _! Z) x* u8 H9 ~- M$ i ' N% @- l1 e7 N" b! R% N/ E8 U" h
# q- _+ s/ t* v$ w. `( v1 V! d9 A
漏洞分析
7 p" S5 E# g7 l l- y
# R1 ~5 ^& p2 L4 y- P3 r1 V |0 ~0 m, @0 @- f& ^% Q
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
% {9 m1 ^) _ m, w 8 s3 b+ S. W/ C+ Y$ _9 f& ?
/ E& z/ m* P. n! S" r/ g 3 T) ^, D1 F( e
- d. ]& t$ h4 F: U
, o# i: e+ E- }8 C* m4 Z 对应着avatar.inc.php代码如下:+ ^0 L9 n, a3 u+ \
( x& d/ s' d6 K
1 v, S8 P* u4 c" L) |, M
<?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) {* [ j& R0 K' ]7 p0 h' }
y+ |4 ~/ n8 d }& G9 B* n; L5 V2 _9 W( ]1 i/ `
case 'upload':% m3 z# x+ M$ A9 J
0 S' R- Y' M6 c+ |
/ c7 }/ T# Y! H0 k8 l2 E if(!$_FILES['file']['size']) {1 U! o2 i# D0 X
; w+ N, `2 g) k2 A
- P k6 H2 e8 c! d# v if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
3 D. g7 u) m1 L) r( y
7 M4 [1 |' o3 F& _4 T2 ^6 C* t' k! d2 K" O* h) P4 w J
exit('{"error":1,"message":"Error FILE"}');
4 T7 _3 o& _9 N0 a# a
8 O% x$ e$ U" _: Y# Y* X% I2 ~- P$ B( H/ N: C4 ^2 J
}
! D4 g, u, `: E1 o) J x) G- A
, W; Q% n, Z" d5 k- R7 u' @' T% l% @2 C5 z
require DT_ROOT.'/include/upload.class.php';5 R- s+ a K1 @, k+ ^: k
% |4 H7 p: F' y& C) S3 I+ Y' }2 Z( M+ c$ J( u5 ^8 x
/ G- D& r% l' J
9 V5 k. Q! v( u# N) k. _: d
. k+ \8 d% m$ E* b
$ext = file_ext($_FILES['file']['name']);7 S9 Q1 b4 z& ]
) q( H! d' Z: }0 d2 z, s
" h0 i i$ ?" q1 }- z $name = 'avatar'.$_userid.'.'.$ext;
2 r2 V. u7 g+ c) I9 N: { * W' t4 N! t; D9 U3 }/ V( l
2 d& }, V0 G7 J. Z% z4 _
$file = DT_ROOT.'/file/temp/'.$name;- i6 S8 I+ w1 Y4 G, J
/ a N6 m9 c- i; z
0 s! N# {: S& Q$ |2 g
% |# I4 k& e% {" ~+ c
3 K3 n, V o0 ?/ R5 n* w0 E" `2 S8 s% w( f2 `/ _# R# A$ g
if(is_file($file)) file_del($file);
7 J' W7 E) G+ Q/ ^$ I' S& B/ B 1 c" J) M& Q, g- O+ F9 ^
" U# q/ K8 |6 r# F/ l1 R( N; i $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
/ v+ O3 x! i& P& u" X4 d5 J7 i
3 Z, ?7 X2 F- O* u) q+ }. N8 m# i, b/ {
3 U; v$ L$ r6 j/ @5 }
$ v. N- |# ?# v% ~3 |5 i3 }+ ?+ R& C: d4 Y' K% e, O
$upload->adduserid = false;' ?7 E4 q, {5 N6 ]
5 P- B5 d3 Y2 f0 M/ Z2 J
# S E) G7 | c# I# P& W; T , n# w6 |, B4 R3 I: f* g3 K9 [
, T6 a* F/ d' b$ E/ h
4 Y( R9 T% N/ q3 I4 d# j if($upload->save()) {
) u8 q; L; i% h) d " a; e# z `" o9 Q$ x+ M
* ^- P& \; P; {4 T; x" J0 x/ A ...7 c* c: M3 f8 l; G
' o, _ i' Z& f# d
5 ^8 Q% Q2 z% i2 U } else {
5 y* [, `0 M2 q: |0 l; ~/ d2 n , L" @6 [, O# P$ w ], ]- N) Q9 j9 r: U
5 `' L: r7 s& P3 k1 G9 j
...: u' q6 [, {* x' Z. f1 G' d6 |
7 X# }5 B* O7 S6 w3 r; U6 g' G6 |/ T3 d; g v
}
9 v% G- z6 g% A6 l! p
5 [/ L: c* a# n$ d/ s4 V9 f+ y! B! |9 y7 V+ S2 J
break;& h" K3 Z3 D5 F0 X7 ]: z
1 } U8 R' n$ C: F7 S* j
4 ~1 v- v1 ]/ f) Q4 M& }8 q1 g
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
8 D; D* K0 O% t# s: t$ x2 G
0 b3 L2 I" ~) ?% H1 j8 M3 j4 d/ c- V& I0 q3 w0 [
upload对象构造函数如下,include/upload.class.php:25:
! n: S' q, u0 } 8 Y( ~4 K# E7 v: k3 o% L1 [
; }0 ~( [$ M+ |# q. u2 g$ V
<?phpclass upload {
& o7 ~" n8 M' [" P( D
4 R1 t+ [7 y, f& g
+ R9 k, R4 V9 i4 w function __construct($_file, $savepath, $savename = '', $fileformat = '') {+ c$ @* w0 I1 s; D
# n% |1 v" C V) H6 h
" V Y( R+ c% N. z# Q global $DT, $_userid;: k& r$ h4 E# y3 @- n) E, S0 ]
/ Y+ ^" s5 @1 v8 H$ V! p
/ l# @+ B( m0 X: s5 z' P2 h1 M
foreach($_file as $file) {
" z. _ ]! C& x: L, g( | 9 i9 u. l* B* C8 e) e2 J/ ?- {
4 m) K4 z g' p" z Z! F& C9 j( ? $this->file = $file['tmp_name'];5 ^- a3 }4 n9 S* y6 `& D: o
V- ^+ q9 B% r7 |! E" I
# Z% `( W2 D" U; E+ V4 I& R2 ] $this->file_name = $file['name'];
$ t) H1 {! K9 W% ^1 H7 ]' y$ C
: P0 v* V3 \# S# z
: d/ @5 f9 D- O7 m) |: r; B- m $this->file_size = $file['size'];1 E1 {; y/ K2 Q
% Y0 o$ s' V9 C9 G
6 S" } w: I$ \( S# W
$this->file_type = $file['type'];% P% @! q# ?( j- R
/ B$ p8 B! E' X# |" O2 C
# `7 j. J, ^/ q; [% v8 w: S $this->file_error = $file['error'];) R# X, H1 }5 M) b. [. U
( d* }& G ?' n
/ e1 n R( R! A) H2 `1 x' K 3 L7 g$ C+ ?7 j1 Y F! Z9 N
0 m" c4 S, K: @1 S0 k7 v# H8 _0 {+ D
}5 n8 ~2 _% q7 {0 T$ z
! q0 P8 I0 m( u
- \$ i) D$ o6 G8 h- n $this->userid = $_userid;# t& Z! M' v2 G4 W4 _- W4 ]* [5 D& M7 l
4 v$ j, F' w- c# G" }' n$ s7 k
" O9 l8 U' S2 q0 u8 @' v $this->ext = file_ext($this->file_name);$ u% ]! e9 X- t
& ~2 K: Z* y$ u& P, ~% K4 y+ P. I' H9 H0 Q# P4 }) N
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];) ]) _6 s& Y6 c* t/ q' ^4 {0 s
* \$ P9 Q8 A- p) q
9 U" H" Q( F. R/ U
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
( y2 y% b' Y4 G $ P( z! ?; I( v% x5 ?8 X
1 m8 X, a& h5 ~6 j( N $this->savepath = $savepath;
$ C, M: I0 c4 }/ I
2 b, L6 Z! O' u4 c0 l; Z6 J8 |9 d, z" ?8 t
$this->savename = $savename;
6 Z- Z7 E A w8 [6 M- g/ J ) D8 Z3 V/ M% G, q( m/ O
: z3 [ o4 l7 D1 W9 ? D }}
9 ^, {6 d% @( E$ w' X. c% O- ? 0 b! r3 W! Q; j s; B/ v2 C
0 D' N$ D& y: A1 [ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
8 n. X3 n$ `0 P+ L 2 f) T# F. j' Z0 a3 D
# b3 t' G! ?% O2 t e 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
) @5 ]5 D/ `! D" X 5 k; R7 p0 g8 [1 g/ i) ^
+ A5 [( g2 \8 W8 i: i0 Y$ R $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 I4 A% ^ i8 A6 l8 P& V
. Z+ y/ ^: Z7 Y* d3 W7 ~" g2 s' P6 b% k3 M% U% h# I
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
. l6 t4 {3 P" |' x6 h; g6 M! w 3 N* X) {0 ~% I8 i) @9 P4 P& a
$ M R* J4 V& R% @; }
, j9 L* Q3 }" H) x8 l$ v# E
8 B6 @' I% D' q6 D& e1 B V: d6 }1 [5 p+ L* x
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
; k- O" d! o; b* @: k, m: w( U z- C0 [6 K" ^( j7 g* Y. M7 b( n
1 a# F4 f" a# B% C# N/ c0 |8 t+ j. B
<?phpclass upload {' ?" p3 P2 i7 P% P
# N F# [& x( t7 I7 H( G
4 X" U+ X2 q- e7 r function save() {
* x, u4 W1 ^0 p3 n
3 c- _ J% n: [9 `4 ?; m1 E8 P: g6 I; p! E
include load('include.lang');
; G& P2 o) j9 k+ s$ { 7 ]" d+ ]4 P$ o. \
! O4 d& K0 R& Y7 U: m1 G if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
6 k6 H$ Z+ M z9 |( }6 ~ / }# \* s9 q- Y, h5 f
6 ^# g) l7 s. S$ B
, ]& J6 \5 H. r. Y6 Z* o) i' u 3 Z3 b' t# }* R( t
6 X# }" O: C2 f; i if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
5 b! P/ ^- B% u9 B: I# z
( x0 ]4 p# y. }$ d/ O0 \7 k0 ?, w0 ~# k* H
i- v/ R9 l2 _, v
- F# r3 y Q2 c
S( c1 x4 B% f% \6 M- c7 n if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);, W P/ m) B6 v- {
/ X# O4 Y1 ^3 ~9 g5 ?
: p, s+ o& \1 k2 u: Y! d# t
* r0 B. t" v* l9 i9 }8 q% m- X ( O; I0 X# k* E
9 w4 [# S5 R y6 \ $this->set_savepath($this->savepath);( Q; H/ u- `) o, ]8 A- y* K
D. w* w9 |% G, Q( O5 N$ x
6 l7 S0 M* A* a% M4 T' C" F $this->set_savename($this->savename);' e! N& Y# g: G i% H: Q
# p/ B( I% j$ `% p& s/ F& t
! G# l2 l# t! z, K $ t- {' `+ R! P, }/ ]
5 z) I/ @7 K$ U8 H
[% m% K% g, i! t7 [ if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);( o0 e" V+ v7 V
1 v, y9 u. G& B* A
* |2 ~; z$ d: I. r" ~0 E if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);# q: p# C: I9 K: ^. [ o
( _# Z. ~: m* G; t) v) g; k4 {' z
+ S' y3 E% \4 _2 C7 m if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
$ a" g% P4 i) `! K! z4 z' R( @ % x$ k6 A/ |+ ^5 v* X# r3 a
% Z' z2 B( }" H$ z0 w$ X$ v1 y J% z5 v) M# L4 X+ k0 ~
0 ~# Z9 T3 s; ?) J* ?
0 z: S$ @" T) ? $this->image = $this->is_image();' M1 I( F) y# e# A; L/ O3 x
4 d' r: v4 x5 O5 |+ t
& Z6 n, Y$ F$ H9 R( Y, U* ^4 M
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
! V9 G _+ j0 x+ a, B* b6 |8 ^
' ]- V8 Y6 R. y5 i5 a: L& |
% Q) E4 X3 C5 ]8 U7 L" G return true;' |* x" R; M& M7 I# I" |+ a
7 x* s2 e. @5 w; M2 l a' O. k
+ ?, m* ?7 `. Z1 \% p3 ^ }}
' d) ~! o3 r! x+ ~# z+ A
8 _0 t9 Q. x+ A# ?% u6 _4 Q
! E* ~2 x; E" y: [. m2 \$ d! {: O; k 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
& w5 o: S) |: Q0 S1 x( R7 I
% f8 P T& K9 G( S2 X. w! D
& ^' r% c+ c& a2 ~' X <?php
2 H. a. @! l+ ~* c
" U: F3 y: e) N
, @6 Z* S5 p1 J: T; ~ function is_allow() {% X) I" a5 u! \# Z
9 @8 H( V# K+ z! ~' S8 b2 K6 l6 V% T
if(!$this->fileformat) return false;& H2 p0 H6 y- C& R( y" S+ D
+ Z( f, [& g n- a0 }& v6 ^% x$ K2 C: E
; B* E8 W7 J, Q. {4 y2 ~. E$ h9 v if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;' o; E Y) E- t! y
$ n! w5 |- O- @" |! b
0 G1 S4 x/ [( d8 p4 ?& E# K
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 q# o2 ?, }* L% x& q 0 b% M( D' v5 H- e! l- E
0 n* i9 ~8 w( v* ?/ @
return true;
) @6 j7 M6 h/ \+ f2 a4 j! @
3 r, Y. r9 R8 R2 Q+ k' b% x' @. [8 @1 O& F8 O" L
}
$ `" Q2 P: E# x1 e [+ i8 ?# h2 [% `9 u. ] V
# K2 Y# W" Z) S5 U# B7 F
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
- J4 ?+ ]2 e0 h. N
) h$ q+ b! b8 B8 ^5 U( Q% U5 [! O% Q7 }4 s2 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文件。+ ^+ `/ B4 m$ W' H& [" R j, K
' i3 V u' D2 g- U' J4 q) \
1 H9 }9 w. U# C! Y 漏洞利用' o% m. E3 K7 I) e9 Y3 }4 m/ A/ R
) w# T* w9 [6 t
& ~- ~& o& \- B# `7 O 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
7 E% ^5 s, V* _5 a& G* Y" K' g% U( B
: N8 y* i7 z, y0 a) C( C4 v8 s$ P
' ]! `8 W& x; X/ G; k+ D5 l
( M# {8 F9 P3 P( k" p9 ^4 \ : L, c3 z; r! W0 ]
* ?: y, |4 k& ? I) A: ?( J6 O
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
7 v3 b) z/ s5 j9 ]
8 j* |8 H* X2 N5 x- J% \5 [) t3 C9 i% e$ _9 ~: y& p' K) v2 A; F+ D4 a
不过实际利用上会有一定的限制。
- @# P8 M0 p$ P; w* {# W
* F3 h( z5 t: [0 Y x/ l
# K* b! s/ C6 R$ H9 l. @ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。% e q( J$ J6 Z" T* U7 I
Y! W; Z1 c1 ?) y7 ?: n+ w# O$ U& C
, j- x: l( ]/ c4 V: ^
3 K1 v2 e! M0 I; F5 u
! s0 f6 H U c& ?* B; o! ~5 q 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:" ]+ q1 N9 ~% n' W! Z
. s$ ^/ U8 H! y6 _* `# D! p$ R* i! s; [ N
省略...$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]);省略...
6 g ^4 D' T+ k5 B5 U7 }* K% [
3 U8 C, u- n B1 A5 Y5 V
6 Y2 V, c2 J( i. L9 j7 E 因此要利用成功就需要条件竞争了。
6 C/ y/ Z ~6 r: {" [) v* d B
3 y, }4 h7 \) [! n
% I2 v' l0 [6 @. F# d. [1 P3 K, u 补丁分析$ D$ U/ @. x1 N5 |- A. d
0 j. K* ?' I7 R% E) U
3 L7 d* s" M1 `- z4 P
$ G! u7 m8 D7 L" @5 O S 6 i2 S. I% k, s/ J2 u# {
/ z3 f9 a9 z" t' t( k
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
8 O1 G( F3 h% M8 Y' o/ B+ } 4 c1 H# Z1 D8 G7 \( N1 ~
5 N0 k& O% O6 I/ @ V B* l) Q, b
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
8 n% |) Z9 o) N ) _4 D: I3 w0 X
$ f, t2 g m* @8 {& s
0 u" Q: q* u' }$ E
3 P+ J6 F9 i' B/ a
7 K0 W' S5 d; e1 H+ N: ^5 B3 ^6 @ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
+ [& S( [: i8 ]9 Y0 p! ]5 i4 x
D% C- a" K% v9 j/ r
0 K" m1 M1 n. I+ L" W- N" o 在is_allow()中增加对$this->savename的二次检查。2 ~3 _- P l3 V, |7 T
, G, S1 O% U9 D5 b8 L; s: d
( ]5 q. X- T5 }4 w) K( j0 F 最后9 `! R3 ]0 B- F6 ~, x: p2 f" W- r
# N) ^; u! k: u( _8 y% z
" @+ E( m }# v. N
嘛,祝各位大师傅中秋快乐!
6 O# F/ @7 \2 w% q" T2 F
& k# a6 t/ |: \% B" u
( }# f3 A0 e' _ 8 I) ~( d d( y, `
5 m$ a9 K9 @+ U
|