- b& I/ V+ Y9 l( O
W# P' F: _* P6 T0 V1 p3 E( \! U$ C8 L6 P: Z. Q/ ~& E
* n9 v/ o1 } b F' A. _
前言$ M+ d: Q, A$ Q% u9 m0 E, q
8 y4 f/ @2 \9 u) w7 r
$ \: |5 l2 l/ ]) ]$ b. | 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
) r8 Y; \) C4 o
% v4 |2 D" H8 k0 G" u; I3 P5 j5 f! ^8 Z2 c, A6 n' S
( {4 ] [% k2 g- Y, T
! j9 M1 \9 r, ~9 Y+ R0 b9 A0 w: a
0 q- R. z% V$ ?( H; c 漏洞分析
9 F. l) x3 X; X9 t
1 ]" o9 G5 t2 H7 }3 t7 H! H3 X, b" j K" E! a7 Z
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
F" K A, e* o( n: N
5 w- L7 b4 d# c# f5 N5 B( i/ c' G
# e1 }* Z4 V& @( u2 Z+ ]7 i
, u6 M: `- `& `$ Q# J2 y7 A/ _" V
对应着avatar.inc.php代码如下:: K8 A4 O* j6 c; [
6 E3 }) p8 \1 p. Z+ k! ^
) m3 X( }) T0 ]0 V; d <?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) {
- g! J0 c/ l: F* f$ n1 u8 S6 J8 b
# E$ K. `2 E; k( t* b+ w8 H, `. q! J; L) l4 u; J6 M
case 'upload':
/ g5 `" f+ b. W3 M 8 m( ?# ?2 H8 s# y' |: n2 x' A
" Q- ~5 H6 q4 D' h& j
if(!$_FILES['file']['size']) {& F! `; x! _! s F' K
1 X: U/ v1 B; M* P3 c/ K' |+ B/ m
+ G3 u$ T, |4 q2 F if($DT_PC) dheader('?action=html&reload='.$DT_TIME);+ e1 U* |/ x0 w) Y) c: `/ x$ J
1 P, Y7 u3 b/ V. J' l0 W8 u2 r
, @! I+ ~8 T& b f# K
exit('{"error":1,"message":"Error FILE"}');5 i; i. {/ X4 x1 T4 f& a( w
* E" A# F# G) l5 A, m6 `) n
* x e* u+ [7 S% `" f }2 O# u: K# D( A( X- M
8 o) _$ r- Q# j2 o
# e+ C2 w+ E1 Z2 b }$ v
require DT_ROOT.'/include/upload.class.php';1 E9 ^/ ~7 Z$ W8 j7 {7 y
" {6 f# p4 B3 w% Q0 ^: T0 e
3 z* T1 }. I- j) o- q j : C$ F6 D$ `, k' P6 f
. Y/ ^, H7 P; X6 D3 j9 k
K7 H( E' @1 p; _' B* y5 o $ext = file_ext($_FILES['file']['name']);
* G1 i; D6 B w J
& S' ?4 i9 }3 d$ e( Y1 X; u7 M, r; M; J5 S; @+ a
$name = 'avatar'.$_userid.'.'.$ext;
; M+ a" y |- t2 P
9 W, y2 b! u( g, M- x" T8 @# z* F+ }4 D
$file = DT_ROOT.'/file/temp/'.$name;, u# F& N6 J# P) Q# R% U8 V% v" ~1 s
9 Z2 \$ ~& H6 {7 |: Y' M& u2 }0 F( m5 \
. V5 D, ?* C6 z. m% O
' d1 |5 l0 C# O5 |
% l1 C& x8 }# Y# ?
if(is_file($file)) file_del($file);
' @* d$ e; m2 D4 c, }. N 1 i& g1 o' M2 Z v9 e) ]* _! N
( C- o. d, Y( `6 K) v9 \, s
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
: I" j9 S( X/ ?* g
6 e" D& ^. D+ m; R8 b' {2 ?( D1 L% O1 C7 k
; e% P: V h- f# t7 r
# ]* c' V$ u6 o$ x1 a! I2 B; D
9 ^2 B, R" o- ^" } $upload->adduserid = false;
4 U2 p+ q; b, M8 \- Y * n* v" b1 k2 }) X. [
% ~9 \* h9 X' j8 Y; ^- k5 j3 k
* ]1 W; r8 i( D/ ^
1 T: R# m8 ?; J- q
5 C: x$ J! p0 x" d* ^ if($upload->save()) {
9 h8 ]. ]- z5 K2 r. Q
4 h4 p+ o$ H9 T) v8 Q: }3 ?* i- @
...1 u. l8 ]. _/ q m+ a3 T
# D& j' o- O+ u/ Q3 ]
" Y. F! _2 S; N! }. ?
} else {$ p1 u& Z2 p7 { P2 ]
# W$ o$ d% y6 }
2 {: v9 n8 u: |* q$ i ...1 ~% | Y% n0 F! c7 J
. \7 B0 b. G( B! v6 W; k& G# i8 F+ D
}
o1 j, i8 `, |( d: T f- D, K' s- A8 [
$ z+ Z% r) t- y4 q, h% x( a% k/ g Q3 Z5 i
break;# t1 C0 ]- j; n6 V* H4 K
& e2 N7 \7 K' Z& j( V
& U* `/ J; x' M4 j2 j. R 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。7 R, B; L0 t% z8 F+ B+ [
/ T+ L* Q4 x2 ~) Q# C
' ^+ [& [& H. C9 r9 i! Z4 M upload对象构造函数如下,include/upload.class.php:25:
" F* D8 i! ~* F0 d
0 y ?8 R6 f V z* e2 F5 D9 M3 O; h& e+ N$ w9 S% }5 N
<?phpclass upload {
0 Z. S" \9 G8 J& [) @ 3 t6 a T5 @8 Z+ R7 M' G
/ M- X5 I* |/ u* X7 m, [, ? function __construct($_file, $savepath, $savename = '', $fileformat = '') {
; |( ]( s; U' G: B + Q3 t4 S8 N6 O5 p: ~- L
' T K# u3 C' n2 c+ Q( k4 g: G7 ]$ v
global $DT, $_userid;6 ?- \) x; M2 m/ j: {2 q6 o' l
3 N2 t+ a) \ A! ]$ r9 G! N: Q! g4 x) K0 z/ g: p0 t
foreach($_file as $file) {
7 Q* |, K+ g0 V5 P; m+ \* h. Y: J
1 u0 {" r8 h2 u! }& ^! L7 O
$ Z8 V2 N: h5 Z' N6 u. D6 _0 z3 V $this->file = $file['tmp_name'];
% k, R! P0 w( ^" d
9 l+ }9 R, I- V3 L( e: y. c! i
+ l7 }8 B( S5 g9 | u $this->file_name = $file['name'];1 A( v" q# N& M" U& Q3 |% \
8 e3 ^/ T- h- P. G, [0 ^" A
6 p0 q. f8 u/ [$ B5 M $this->file_size = $file['size'];
$ M- C6 w+ @, C5 C ! }2 B6 H' C( P2 ?' o
" Y4 F6 R2 H; q $this->file_type = $file['type'];
5 M3 h j3 O5 }9 m5 Z0 {
4 b, L& n0 t; x! |
: e5 j% N5 w8 l( l" Z9 R# [# l $this->file_error = $file['error'];
5 S( `$ T+ c N* K* L5 t# H
- V q* M, M6 d* d/ Y9 A
4 J. ]2 M& O; y
# |6 j: X* v' t5 W$ x9 D
; Y5 h2 ^& E5 u1 Q! W" @0 B
" `+ m% z8 a, H! ] }
" v& A9 z+ M8 q ( C* @' X" o( u: e7 b( f }$ x" u
2 ^' O8 l+ p3 }: y! i6 z2 N- Q $this->userid = $_userid;- H8 Y/ }3 @& d, f: H3 q
& o Q; }- t0 C p( W
/ r$ ?2 {! {0 n5 \; B $this->ext = file_ext($this->file_name);8 N) b3 g0 g( x' J5 i" |4 O9 Q
5 w8 }* j8 y1 _9 ?/ P4 V
c2 D& T2 ?" @
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];4 v* n6 I) y, s f* _ V: X0 m/ ^
: c0 x6 o" a$ y
6 @' D2 f9 |( r" V: e& N# j5 B& Y) [ $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;* m7 g( k* s/ H
) E' @8 D8 ]5 `8 m9 x3 X3 ]9 @, |; `
$this->savepath = $savepath;- q5 r x7 {: K! ~) H
5 f2 a( c- w/ x s; g7 [5 A7 H- y
+ n* g5 A: J) n& M- r- Z0 U
$this->savename = $savename;
0 b, g* ~+ t2 I( {+ B 1 i" N6 e7 V- L% [
! |8 E; D8 f6 |
}}
}& b: a2 A+ s8 {( i2 Q X% t! m$ v) X, W! {
3 F' _: O* Z0 w8 P2 {: a! A1 W 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
! n! H$ q4 c) | o! N& l/ K2 Z9 B
% ]/ C7 l8 E3 L/ @4 F6 K8 ?" J' f2 Z0 Z& k
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
$ o" N3 z6 H' ~. D9 T0 x
7 U0 \/ w1 `: l/ G4 n0 p$ B6 ]2 g) O" H0 v, I, I. v f
$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 K7 f% x' a p ! d1 ]8 p+ r. b+ O8 }! {
" c+ B+ |& A! |" B 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:/ B' u3 n" D" }. Z( U3 h
" M) f5 V1 n4 L5 S b" v' x3 W
8 V% K3 z2 P" t7 y' @ , j& \8 [* [3 F0 {
" y. u8 B0 i- {* U: f! b
, `) n# P8 g( i6 N! b- K 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:2 D4 y) M1 D$ H% }! ]) j
, f8 Q0 i, o& T3 O! T
7 }! B# c& d4 j$ V3 ?- Q0 _ <?phpclass upload {8 [ z& x6 u" m) `& k
) a, F! o: x6 O ?6 ]0 k
* l1 p! }& e4 g5 `4 F% d6 k function save() {0 W4 j: S( Z, R" G
6 K* n2 x' ?' J* w+ R. s! A+ b& m8 K5 u
include load('include.lang');6 ~- w3 {: Z$ J* s/ ~+ q
8 u! y( O b$ L6 R
$ V5 S4 u* J4 K, k% o
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
! C$ j4 z! O: c! i" |/ b. X5 x
" Z+ g+ m8 Z$ d. f, `" \1 Y! ^
; }1 Y2 U: _0 f& J5 T6 y
: y: v4 R1 C- G% i( p
" ~% J1 `, Q# a! q% [9 Y; y+ h! M4 V7 ]7 l
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');( z1 @3 e' F4 a6 ]0 k
7 p% }$ y! R& ~2 c# f
7 Z! c5 [. _% R" j6 Y' N. | + x- D9 Q7 T$ @6 ?
" L& o. b- j N6 x3 X
- X' ]7 R; K) L+ F if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);2 X3 _7 m+ |8 T. L5 v$ X
# d' k) P. E1 W7 Z( y* f, c5 T; q& E7 r! l3 M: C# Q, c
3 ~+ q Z9 L- `" B, u6 z F* G
6 r! I3 c, j: I& O3 e3 M0 r7 H
& q t5 N$ g$ \# S $this->set_savepath($this->savepath);
# s5 @; P4 R% \4 @. K# v8 U: k
& l+ V7 S% [* w1 {
8 I6 V5 C: [4 e7 o8 e/ d $this->set_savename($this->savename);
, @# Y, o( l. [! Z & u1 {% e4 O6 ~2 v H- H# a
* q9 m% O- H* m, B
o' X$ O+ A1 v c8 U5 o) c1 W
* I6 q3 M! Q! s
" j b/ C# s8 t9 K. i6 ?2 u6 I if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
/ ]9 I. p1 B5 m+ i! l( i
2 J x. f( T8 c2 G
1 h0 n% _7 R5 y' L) J; h$ i: x if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
9 D; P# p. H5 s' J
' B8 E3 i' Y/ E* Z" t3 @
% w6 [( `) F8 f# g! ?7 O, \ if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
/ V9 X- D3 d- Z5 v% e 9 |2 o6 H3 M {/ Z; s4 A1 k
7 M3 r2 V2 P+ u% }4 ?9 r
" t) N1 d8 y- H) x" S ) y- c. @! g. Y
6 E( n" G5 T2 y: _: e* i5 B $this->image = $this->is_image();- q1 f' F+ b& B' s" w" b6 k' c9 J
* k2 h# }* o1 l8 m* ~
- v7 [2 e' {' m6 o+ p+ j if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
/ V6 D, z l9 x- L ! y) i9 E6 U% P t3 K7 j5 l/ G5 u
1 y1 T) G: E+ ]$ q
return true;
7 B, W* {: l; g0 w
1 K' i' s5 N* H8 y" d! u. M h# k4 d) Z0 o; j* j1 {
}}, |! B( y+ R) B% k0 }$ C4 W2 w9 g
& U- d0 Y. h/ ~: H& f4 ?) K
& d$ J+ A& a* ]# w/ _ 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:7 ^- `3 f- ]7 ?0 N2 i" m
" }' G2 q( l6 G
! ?- {5 k% W: p. n <?php
" H& [ v4 S) W+ | + A8 J5 m+ R9 f. G. X
7 H6 ]* z) P7 ?2 L1 Y! V* M, e function is_allow() {; T. g2 v+ R, @. L4 N9 {
7 k( i8 J& B# {/ A; s& b
6 a% [; N$ h9 v/ {( w% O
if(!$this->fileformat) return false;
3 L7 e- G1 g: R! X- U
# R2 z- x2 X7 D" I7 Q2 d8 g) k+ g( u8 v+ v# n: w6 u
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
0 _) x: J1 S2 `( y) t
7 ^5 Y: e" T! p! C" x/ W1 k
0 P0 g. _3 R7 O% p: i 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;
X9 J9 l ]0 p8 Q' {' ^2 g3 J- O
! l% M$ U4 b3 q5 x/ [2 p1 R6 O2 v0 i7 ^5 a. F1 s" Q
return true;
g, a, k2 v. i0 s3 B8 Z- X) n 8 k8 R) D- {# o' P7 ^: I; \; m
! X( j& `% X8 ~2 s7 k, |* |
}
, x- S7 v1 x+ J% x 2 R3 }* U- P+ O" Q* q
0 T- Q. [* d! G+ U% N& ^1 X0 e2 S) \ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
0 G+ M6 P) `+ b1 c: \/ G
$ i% g- \5 @+ ]6 D# k
! _ S2 W! P0 L, o: a% ] 接着会进行真正的保存。通过$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文件。; n3 \- F( V( r5 i9 w
. n; O# Q& W ?9 k8 k/ _9 i1 D G9 M. E# c i2 Q( M
漏洞利用
2 w6 C+ U3 P8 e7 i# J% a* ?8 h1 @) G& ^4 e# M t" r; x
; D" k+ x/ r' n$ T' u3 K$ F
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
5 W/ h! i7 u4 F9 i$ A 3 n% o; ?, E/ O
" t D0 h4 e/ M# m5 _# s ! [" o. }( l8 h( {" l' z
3 J' K& `. D" a1 B
j- w( G; X# w 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid N: }6 T3 j3 `2 S2 i/ Y( ^
t# Q$ J4 B- m; [" k
2 N8 {! P, l8 x" W 不过实际利用上会有一定的限制。
8 {; O! l, a, Z4 H
0 u# m; j, j7 l1 s3 V/ [4 {8 ]7 X: f, \) s* F+ ~# w
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
' T$ s ~& j1 q4 s 6 _9 @; c8 W; J6 s" I4 W
4 ?7 ^" p4 Z2 e* \9 P4 e
$ X8 H, I+ Y5 n3 b; | ~# d
0 {/ |' K2 P f
, u+ K# h4 A! Y! K, b
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:& ~& J4 U0 S3 x* p- y5 [+ m4 T
/ k! }" n& c0 Y v- Q
/ @2 H1 \) x, l" J4 ]2 J 省略...$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]);省略...* H/ \, V( p6 m; P! D. G
% h. f- c! ^0 c& b4 O/ S2 Y, T0 F4 y/ P2 T) W+ o8 f+ _+ q9 z' {
因此要利用成功就需要条件竞争了。3 V$ f& s) ^: |2 ?
: s. ^/ {1 S9 T" Q' [- t+ x
- m/ ^; [$ f$ t$ [8 U0 e+ c
补丁分析
, {- E x G; E V' ?, [1 |% c: F0 l- q
" y6 ], R3 B0 g% r8 o * y7 }) q3 A$ ~9 u8 ?- H
7 n; G: @; ~6 |0 s6 K- a8 f
, G5 n' t6 v4 c" B: u4 r
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:& W7 U3 @ e+ J+ W
" v) h8 |5 Q7 H! ]; C3 A. y
7 ]: p- `+ [' T4 z- M/ `( T function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}# C2 p {& c+ _4 g9 g8 z* h( b
6 \9 P" F; w. Z: V( W4 z% j4 I2 c: b
4 r7 Q" i2 p9 i, a) H6 R, o , N. g& v; o+ |5 `: t$ i
! k; \2 E2 ]$ R% x- S, n9 y8 E; a7 ], ~" j- s
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
2 m- v7 l4 ?( @* D- [4 S6 s0 ~: o
% A! X( b) y7 N1 Q, a) K
* [' |/ ^2 [3 j) @% ^6 s5 \ 在is_allow()中增加对$this->savename的二次检查。
6 j: t/ X9 v& `. [6 z$ W/ y
, K% E+ u0 m: q- @; M5 }, O$ S3 b# ~
最后" p/ q' r3 \1 x
- ?; o8 S8 P& Q5 L. |
Y: A: z: e: l) x' f0 _7 s. d 嘛,祝各位大师傅中秋快乐!, x7 ^9 q; Z$ h% ]
/ P. ]3 r! r ~0 _7 G \
2 A, l8 c" u5 {4 ]2 x+ ]& V9 H
" a! D$ d2 I7 x O
7 o/ D H3 n4 G7 y6 Y6 V2 ^
|