* V3 q( g2 @% c( Y2 g* K# e
& n6 b1 ~ L: c: g
) M+ G+ X+ _, ?6 ?4 l4 n
3 T; ^/ d3 N' S
前言# _- O+ W0 m q1 ]$ c
( T( P: x2 A' @ g" `
/ v( V! b# |9 j- S 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
1 _' V+ o- S1 f8 \
( \. k- C$ p3 q9 Q
! b& W/ {# c0 K
/ f/ f5 z7 ]8 m1 d j( Z* d, t
; H& e. l8 o, p o$ q+ m
, B- c! a' o8 k9 G% Z 漏洞分析
! g" e- ~( m7 q9 |! s# E+ a- u' m+ q: D9 l U& ]: D! B) O* D0 c
& m5 o* Y; o- T& F# n' a' V 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
/ T% [( |; O; P9 u 9 r2 \1 }# Q/ K- J$ v
J: s, y* { v! }4 M2 r/ `
7 t; s" R8 p& O4 \) w% s* O# J+ E & `! Q. y' J S/ c# k n% P
$ J6 g6 l/ J. J( k
对应着avatar.inc.php代码如下:
6 b% u; I# ^! m6 g* I' Z+ F
, D! C$ z3 W/ h2 n/ S$ L: h. c
/ g) v+ O% S; j8 F* w, 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) {5 o9 [# D, @+ I0 k3 {# `! @# P2 T
( Y$ ?: Z3 H3 ]0 V/ S
/ Q4 C9 X8 ]9 }7 d" p7 { case 'upload':
6 r5 {7 k8 ?5 y; t' a , h- r; Q9 h" a2 F% b/ T
V( q0 ^& ] ~& D8 a0 q if(!$_FILES['file']['size']) {
, d+ `' y2 W" v$ k
* Z! e# Z3 @- f( C; t
+ f i6 N8 \( ~: x/ H) v$ _& i if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
8 d# \% V1 J a! K2 W, D 0 c( Z- n/ m, f1 V N
2 }+ Y/ k5 `5 A: y! E, Y) A exit('{"error":1,"message":"Error FILE"}');% d4 _: Y0 S ?
; [3 N2 p, u1 j# \- q. X
; I0 Y; }$ ^! i& Q& z! e }. l* D. M: v, x8 h3 w9 K
$ c+ T8 w0 @- X/ \3 l ]) m5 [
+ y/ F3 [3 q- q: d# @) L require DT_ROOT.'/include/upload.class.php';
x+ `9 G9 J: \: x4 \+ E
% s4 R% a W6 K" U O1 ?: `
7 G6 I h' w5 o9 P
# b. ?% g- G+ Q " V/ k: m7 }7 y$ N& l. V2 ~8 D; U& Y( c
% u2 n E4 |# N $ext = file_ext($_FILES['file']['name']);+ O' a: O0 |( `8 H# H! W
8 J# T# i: c* b* M1 M) ?& ^% _. j
: y2 M+ \, g2 X. k: y2 o7 O. Y8 g/ K0 X
$name = 'avatar'.$_userid.'.'.$ext;
5 P; P( x) p- i: {$ T$ T 5 b0 `1 f1 h2 B! E
: e9 T9 p2 e( n% @+ i
$file = DT_ROOT.'/file/temp/'.$name;
2 w% @8 b8 l8 A A% k5 Y; {* E 9 B% B+ X! H: r9 B; A/ O
: r2 q: i3 `1 z; \! m$ Y
6 [: Q2 U! e, d% [/ O
0 @+ T2 w9 }* O' b
) z3 q1 D X( O; Z if(is_file($file)) file_del($file);
+ N8 W; u2 k$ z 0 H3 x; r$ d$ J! B% \: M) J
& t7 {! V/ a7 Y, K/ N
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');6 L* U2 z$ }& O7 _" O! t
% ]; q5 _: p7 ^% D. e, H* m) A3 y' |- p Y! W! i
& ~- \5 w5 I0 C1 U" T
k2 v# w4 q5 a* u$ v
0 U _# K- ]% [+ ~- h$ z1 t; \
$upload->adduserid = false;5 g# h/ m( Z, N
7 ]! U& p( l; O' j) @
! d* n% I/ f7 G; _- Q ) n6 m! n1 h5 q% Y6 Z4 w
7 m3 \0 s8 X9 Z8 j: ^) U1 e( q7 }$ t& F* w( k
if($upload->save()) {: Q4 m R- k7 I! M# c5 z) A
8 s) _! @8 }& z& J4 i# p* s* p0 ]2 }+ O8 S7 J3 t
...* v& `. k6 Y5 E( D1 A4 }9 D
# b, m+ Z" z" c F) |, c! s
+ F3 c$ }7 g5 J) K1 K# y } else {" G' ^! \% D7 A: b; u. K% M3 B
4 d, [& x. L" A, d% X1 a9 x5 L( [( s! \' [3 V1 j$ H
...
, r3 u* y* E' ?( |. B0 w 2 i5 `4 D0 g8 k' ^
6 I6 Y0 K# L1 c" q% X: ]7 l }% v+ D7 H$ }1 v) m' U- ]: ^7 D
* Q" c! b; U' v+ P
5 z# q, b! q; D9 T break;
; n) ]( t7 k! ^8 R1 Z; E ! R' j& y N. E' q" q
( C- m, k! y" ]$ T 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。" K4 m2 r: p/ m# f
7 S8 m: l- s9 j" a) t- s9 x9 G E; w6 X' H2 J
upload对象构造函数如下,include/upload.class.php:25:
) u) j' d. W$ O z: L- t 3 R" M2 ]9 |% ~, @2 x. ~
( }3 i8 k. R1 w7 M+ h, X ^ <?phpclass upload {' d) I; X% r9 U
! a, ~' Y. o) l. o! j& J
" s" [; w0 f Z, Z [ function __construct($_file, $savepath, $savename = '', $fileformat = '') {- l5 t+ M) e1 Z/ N
6 `! Q2 D7 i0 |: H8 N- q
% E" I8 p6 B% i5 ]+ X. { global $DT, $_userid;8 d! U! c" [3 a. q( U* u0 o2 q
; |) q% C d* p) o' T" A/ I* h9 Y5 S: q( S7 u8 K" @7 @7 y
foreach($_file as $file) {1 K" J4 }! F4 k7 m D
1 _, P' L1 P! [+ ~( d# N
+ K' N$ v7 j8 G: F, [; R
$this->file = $file['tmp_name'];
( R: x1 x# u0 f
) a. [; O" C6 s: k5 T! V; P% D- t% C* i9 Q& [+ W8 j
$this->file_name = $file['name'];: ?0 R' W$ Q* R/ U
/ J5 V! m N# Z
2 f2 q4 v' T8 x0 t
$this->file_size = $file['size'];
1 \% o6 X O, K9 v/ ?
* w, T0 R* ~# y# _; i5 ?! H* i
, @; z' n2 e4 |: w $this->file_type = $file['type'];
% C+ R6 K- L N" Y0 j9 u5 u. ` 7 ]8 C8 S% R/ K. c5 I) E3 f
- @: y7 W/ h$ c* Z% p$ R9 G* |
$this->file_error = $file['error'];
2 A8 H a! d* H1 s f* L( [8 J' X1 R# ], D
7 X6 q* G- E- t: \5 N$ R
N% e& x8 Y1 D) [ 9 t; y0 J+ l" ~8 F
0 B* R" o$ o- R- H4 ?, h9 ^3 r7 a
}
' @. Y* S! A1 C; G) u + V" n/ d/ r( B% \8 ?6 {; M4 R1 S
9 u0 S3 K# `! p $this->userid = $_userid;
( w9 F4 U4 \$ \* }# o0 V 1 T/ [ z+ t4 a. M+ t: w, Q
6 e- ~1 p8 L# P' A8 C K $this->ext = file_ext($this->file_name);" g( c( R \2 ?& Y
% z- ]1 _9 b2 T! {; h1 [
# C$ o! D) |" n) E' Z% ] $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
9 m2 ^, x) I: _! j0 O0 S+ f 9 l: s0 ~# S- _- a4 S1 M
; v& J7 G. U/ c4 ~: F& @ $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;9 s* E/ F$ [2 A' d0 @+ o$ E1 f0 ]
! S" B) ]7 L7 c* v" e: s, D& a9 q+ n2 l+ N: X9 c
$this->savepath = $savepath;3 ^% I- Y3 u3 y' _9 q, C; S
6 f: m x' d, {5 o: o) `0 U: k
q5 h X8 r% H) h4 o- I
$this->savename = $savename;$ G& N' x! {1 a7 d8 p
2 v5 [1 H& L# a' Z! D- u, Q1 V; k0 b1 p
# S4 `% J$ m3 S$ L5 v }}
* J9 m/ k5 w: ?" i. l. v 4 l) r1 Y& V' A7 W/ r+ f
& y' C; `2 d: J; Y: A
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
8 z2 z- }( {( j. T; L8 T7 U
7 i1 W) ~9 c, z( p# O3 S; s& `8 I0 c. E- c& r8 C- S" M& L c
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
) L! ?) K& D; H, N k8 P6 n
# b+ q) T9 y( s, F4 F9 v" G: x- C# {4 L4 m8 d8 S! X- ?
$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
* B7 M) P5 \; f$ S 9 X! C3 i6 @$ c2 V7 z# Z$ g2 j
# c- f5 P' q' M6 z% |. g
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
. u7 h5 ^7 J6 R2 F2 G , @5 }& W$ S3 L6 n! [! \2 N
* v9 y9 ]' R$ e, s* O* y; X: h1 s
% R8 {; @; n' Z) e : |3 h* x; p0 |! K: l6 V, C
! ~. u% l7 v2 G1 W$ a+ W2 o2 b
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:/ K+ z c9 k, U2 j6 D! R
( [) U' U" J4 ~, m
9 X; a* ?' f# p+ ^% {1 z& x, ? <?phpclass upload {8 o" M2 p" H0 O+ Y. Q
1 P8 V0 r- c2 |2 d
( B. S h8 H/ u9 p
function save() {
7 }) [% [4 a' S5 V6 L
; Z3 f3 F0 v) {' B/ Z
5 }' H7 e9 r$ A2 w7 _9 Z( Q include load('include.lang');
, _; w+ ]+ f3 r
7 S0 x5 h7 q+ L
# a/ J* k j* Z" q# c8 ~) ~ if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');4 f2 v3 G# U/ L
* K; d, v( P7 H
. f. R! t) A5 ~. Y$ Q' q
) p6 R: { l( i0 X; f7 x
- d' ^3 o* B& h4 V) d) I1 |. C. @+ _ w3 T: z/ C8 N
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');+ }" W" k% [, ], q
1 j. d2 g2 b7 Z) I. F/ O }1 N
- g& R. ~/ E% q% ]+ a3 n
% i, a R/ Z' u4 s: v
8 _3 v7 y9 C# S. j7 D! K7 E6 a$ b V. E: R9 k2 C) d6 u+ f
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
; \ r4 P. E) J4 _ ; q" E: W, m7 W r; o5 j" q. q. ~
3 I9 W& u1 W$ K" K' ]3 _! [9 w
: q4 `$ [( u, c! \8 t! }: O! u ' ~# d/ Z5 j* c0 d8 Y
0 y+ n' p( c5 E9 k $this->set_savepath($this->savepath);( M( C; N. [1 K* A2 }
9 _/ I( t9 `, n+ x8 j( h
8 l J* O, Z4 P# g8 ?- [8 x
$this->set_savename($this->savename);1 X* q A+ ^; J
, [9 L* |" `( n0 b6 e6 {5 A
) q, ~6 H, t6 F6 A
! T4 Z$ I' ?% r% s; `" O
# U# H+ B" J" E% c3 E& B" q) W1 }2 @; T, y; K# t0 b2 J
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
& D% {2 R6 z; m# j' ~* A 4 D# Z0 X9 z8 z- [% M/ {* W! l
4 C, _4 D* d/ x8 y Q! o' [" P9 s
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);8 ^ m+ n! m4 K) I
0 Z5 |8 T1 y9 _0 H. c/ B
3 _) ^- M' D1 F' \0 W; g4 H
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
' S2 r) z$ T+ j) w 8 a5 p7 V; {6 n1 I
/ S6 M% n% `0 h& {- i; i, n
3 {2 C3 W. E2 c" A/ ]# j& S
6 e6 y" R+ ?6 Z$ ~9 X% O2 v1 A: ^
$this->image = $this->is_image();
3 Z1 A) m5 o+ |
z& L3 Z! \( C! R" b3 {7 ~% ]0 n6 z+ S
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);: n1 X9 `# T0 P3 N/ a
! i; A2 L+ S+ Y4 M$ [# n
! |) t9 w: B: M0 ?- i+ G return true;& V8 i0 \( X% Y" P w& S
; [9 }% D! C K8 \7 |! ?- ?" O) ~( D2 ^" P- }
}}; U* \$ [6 W' o9 C3 Y* o
7 `2 v* X2 K& I( [& t1 n. A4 [4 Q! L" O7 O* p$ W
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
; i+ T* J( p0 \3 y" S9 m2 S ' s( p0 B! }: d5 t8 N* Y. ~
7 j; Y" x9 {$ c
<?php9 p* L1 Q4 L7 o& ~! i- v9 c0 J9 I3 y
9 N r z+ a" U9 a% f/ |# T) a, W2 f
function is_allow() {: Q4 D% \! v. \, }" s9 k3 u# Y
/ A$ o" n7 i! y% O% y( W% C: P9 b N: E! X: e, L; ^
if(!$this->fileformat) return false;
3 w+ Z- b' k8 r
; z& u' i7 a2 {1 o0 e p# J2 O
3 b( g7 L8 W: C) z* h# r" d: ` if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;, b+ [ t# o! F) r! g3 }
1 T0 u! c: [" @# e ~6 R) H( n
z. t2 ^0 W5 q1 a 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;( \: a6 l B) S4 j; ]
% [. f( V; _. b- u/ _
8 X! c5 W0 z) l! E: A5 S return true;
- R6 g7 c) w6 P6 m 3 V, o0 O& }4 P% I* J9 l5 M
; j+ F1 K+ T" W# M
}
, e' T, }8 _+ ]" p
o; l9 ~+ A( u* Y2 R8 m
- ~; D/ \# j! D3 j# y, b4 s 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。& J8 |* c2 l3 K) k3 ?7 Q
5 x A/ z& j+ o' J8 ^! C
: |3 i% T1 B8 x; w$ G7 _7 k7 \! { 接着会进行真正的保存。通过$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文件。% y" `9 t. Z3 v2 }& L# Y
0 O+ x6 o& O# p6 }- k) x
* j' v: U. C" m" g- m2 M# N$ ? 漏洞利用% B! w8 w# y l7 n, `7 D8 {* T N
9 _% x) J8 \. t8 P
2 J7 f- V' T' C 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
7 p0 j6 x+ R' @* w$ ~# ~
* n1 b. i( W* b% y
$ i) q2 r, A. n- x 7 E! H% m ^: B
, K5 i, e( e! [$ L* A; @: ?& [, n8 D
3 u. r/ o( b0 f& {% U% [) Z
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid9 |6 d u( M, f% t
^" \* h7 _8 J' C j, L4 N3 u
不过实际利用上会有一定的限制。
+ D/ [4 S+ A+ K# n: F; z% _* q
: [. b; z8 T' `4 h0 P, ?7 X ~! d# O; W* r& P: F, S: f6 j- l- |
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。6 W* | \- S4 a+ X# q, I6 p: l
, F7 M. a9 A7 s! C9 i( v! l
' n, s8 W( t0 F6 w( h) _- ~/ Q! F8 w
2 L2 ^! P# u9 {8 A# }% \/ _1 x
" \ Y% W' O8 t$ R
' n9 @. n9 B/ t/ ]/ l3 w+ j 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
3 f0 i$ s9 E5 M 9 V+ s& J1 x$ R- A: q7 s9 g
5 s5 G L" u) ` g 省略...$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 e3 u: A& I( q" D5 }
* e' S+ h8 O6 b/ a+ Y
" B- `+ m$ X' F$ I( K
因此要利用成功就需要条件竞争了。
* G( |: H+ O' @# O& ^3 H6 D / d3 ^& X6 |/ s3 h! Y' t, G
1 A% U6 i- H+ }6 z7 K 补丁分析
: ~+ P2 F" h# N8 |$ Z8 v+ p" x6 v% H* r/ j7 e2 D$ r+ i6 O+ C9 O
; N% c4 Q3 }6 x' i+ K4 f
# g, s# N$ b5 L6 N' S! k ' V1 y) Q6 A( u4 ]& E
1 I+ y8 J3 N; W0 @+ z+ b/ q& } 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
: N' c) \0 u- S* f2 C2 {+ E + y7 \0 Z9 t5 ?5 X3 Q3 y( C
7 M$ n* a" P5 V: N1 c/ E5 [
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
) |+ y' X A6 j& `
* X: \! c: V/ n* {* k
, ~4 B* Z) m# x# X2 U o! p t! m* N4 Y" X; @5 b
& y, [$ [. L2 H3 b% V4 O4 J' Q' Q! P
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
2 Q: a4 Z" I2 R! n; Q * }! d6 t6 l# W, A6 I+ W. _
' a( }, H( w6 L 在is_allow()中增加对$this->savename的二次检查。
9 V, j2 Z2 T' Q4 z1 ] Z" c+ J# X, N% \7 V
5 H# s9 _& i0 j3 u
最后 `- V6 P! V' K- b7 c( K
d. ?" I% v5 V. K2 Q/ L; t
' k4 ]0 c5 |+ l0 x 嘛,祝各位大师傅中秋快乐!
$ ~7 f7 f: |' R6 g6 d. o T& p% W , Q; s5 ^& S7 G8 l
, s; @$ e7 s% K0 x3 m 5 x# S( `/ Q5 Q3 P% A; c
4 R& @8 `9 w/ y& Z3 y! @
|