2 R, ?1 f9 y1 X5 a3 Z
% K% s) V9 R. e" S( ?
3 U7 r2 ?6 ^) Z# L
# R7 d |$ z( l6 I7 F 前言" B# x2 V2 F' O8 `3 U0 T$ w4 v
& Y+ [7 C5 |1 n9 `5 ^5 C/ K( J
0 y' b# w; w0 Y X% L 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
- C- p& @ j- |. V 7 O' m$ C: o$ V2 D' Z
+ M$ |* L* w D5 P" D/ n
0 R+ Q+ B% P1 m0 B" g% Z- q+ y6 _ " ?3 ]5 ]3 |+ r" _ f9 W, y, @' y
+ h0 F6 `! j1 Z9 E& D
漏洞分析
: R u+ }) ~/ ?9 E$ Z( d4 q
, m/ ?; @; [- S ?! }. [$ C
0 N# z1 _" }: W! q5 n3 O+ _ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
& D- b! [3 @% y% Y, \- T4 h
$ r6 G( t) U/ N1 P4 m, ]& a3 W5 D4 h- |7 J9 I
6 t, A" y& |: T- H: | - n* W$ P+ q" q4 u8 L6 f
" Z+ a1 x( O6 m7 y% i& h
对应着avatar.inc.php代码如下:7 |# N' i! u1 y2 |
" K, W4 g) r c Z+ B& Z0 R8 M7 P: g- O. u+ w4 Y- 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) {
' Y7 C( `0 J' o% I- e
) B7 h6 U4 f# j5 L" X1 Z2 T# |5 j7 h0 |& @
case 'upload':, d; C7 U$ c/ |6 |" S+ C+ r
6 i7 F' E' X# O# r7 [1 O7 U; c) o* q2 b3 _
if(!$_FILES['file']['size']) {
* P4 O' Y# @+ v . e) ?: k7 C3 g7 S# Q2 m* o
- z2 u5 F3 A; c5 j if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
" ?) `3 V1 r" p: o$ e - i( M, ?1 k0 H( U
% w# L: D! e4 c7 B
exit('{"error":1,"message":"Error FILE"}');% N1 c. ?: P4 X( U; B2 |3 i) M
, V Z) J5 }+ b# n
: B b) s! P0 T0 O" T/ Q; ? }
/ a7 Y7 c* N1 i & R" C. ]' V9 \" r9 H
# _! _6 \ _3 ~4 j require DT_ROOT.'/include/upload.class.php';" k3 x1 `7 c( z9 V6 o# q
, E1 o/ x3 h1 h8 M. e" q+ x" K% u! E6 H5 Y @& R
! K7 V0 U; l) k. ^, X' B 8 M, G, a0 H* |! O
0 e7 s4 i3 W( y
$ext = file_ext($_FILES['file']['name']);& o5 Q2 A- N6 w [ H
. q7 U" r/ R2 v2 P; m% l
+ s+ T) b+ D% y; k4 h $name = 'avatar'.$_userid.'.'.$ext;( C( u4 k. O9 |, E" g9 z2 U
" Z2 z$ m. `7 N L1 S) w: n$ t
1 U4 i3 }4 q/ k
$file = DT_ROOT.'/file/temp/'.$name;4 b6 u# {0 H4 C, t5 F) u1 T! [
h E3 z8 e+ L& n6 @9 V) `, Y
p! R8 [! J% e" I( _0 W$ z1 ~' N. j" \$ \
- @6 f6 e) u4 T7 x) V ; C8 c( v8 Q7 w6 P
# F9 v) B7 {, T) r3 n if(is_file($file)) file_del($file);7 g0 U! i+ ^8 P {
+ S( w4 `( A, z
& x5 O8 `: q5 F* u( d9 D2 x $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');+ }2 g# l$ _: W# E2 S5 ^' e
3 R: ^, {/ b; i7 F3 l# u" k4 g" x3 Y
: Q, O7 U3 O9 l; c* r6 [ - @$ t5 d9 E K% F; k* `8 `
/ @ U) R, G6 E3 I
- Q7 ]. N4 `1 Q$ Z# {2 P $upload->adduserid = false;
' U) f9 I, G. }* l0 }, h( m+ e
, m$ ?) F. ?) j2 Z5 L E, v3 r: L# G+ Q8 f# k# _/ z4 D( P
' M! Y( D ~& ~' V: h$ } U 6 |3 {% [1 g& W2 q
4 i/ E( p% @( {# B* F& i
if($upload->save()) {& X; C i, I) m9 \3 ?
2 b1 g, a" G# A1 ^. u6 t' G: p" m
' l( c$ w( T7 i, r ...8 _6 p: d0 A: N( h5 I
* C5 V, \- U f& s9 d
: c! d/ E" G, y3 p5 Z& j& J } else {) H# L7 u4 e, z. @$ m
: C* v4 T$ ~: a2 e+ m$ h" Q; k& v. D: W
; o1 z* y1 ?; K7 T* v; c | ..." R7 p. E% K7 w/ u- R
* Y2 U# C7 j6 y u K. Y2 G1 o; k
/ ^0 W1 ]: G3 x5 G+ g) ?! Y
}! |& D* m: `1 v" z" t/ Y
4 O# @5 U/ n4 z( H$ C( q }, C) Q/ d! K# C6 Z4 ]
break;9 m* ]- [2 |) x" L
3 ^* P7 H/ b! Y2 a9 k
! Z1 q9 U; q5 [6 }+ G9 y2 Q
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
) \% q# l6 ?: N- t' I % D8 t( w! @; P5 x* n7 {3 q
7 K2 T& i# j( O! W9 V6 H4 }" y* H
upload对象构造函数如下,include/upload.class.php:25:4 m/ q8 H6 m1 N/ W/ K% f; q
( H' U$ Y5 s3 N/ V
. c& ? B; O7 |: F. r6 S: h9 Q <?phpclass upload {; [/ k- ~( u1 ] U2 M, [
6 z) t4 {- |8 w( \) D
# o7 E7 `. I) M
function __construct($_file, $savepath, $savename = '', $fileformat = '') {5 E# u- ~) j5 n7 {! j
4 f5 V5 E8 }' ?
1 F7 \8 ^# F8 R# ^- v
global $DT, $_userid;
8 g* C9 ?3 c7 l2 d& J/ G
, a. x5 s( u: L/ C+ D* m! ]8 o! I+ {. |1 U9 M' q/ ]
foreach($_file as $file) {4 r$ G6 P" I: [6 s
) |3 S8 v% q( u K7 Z
. x' m" z0 t" R/ X: X0 {1 ]7 R4 I
$this->file = $file['tmp_name'];7 d, g+ e, x$ e6 @4 l# F( O% o) g$ x
+ C% d% D7 S3 `7 i
( [* s% o* x' C/ W1 \
$this->file_name = $file['name'];
' w3 K- K, w+ {) m: k
1 r2 `0 }: e$ Z1 ~% k& j6 w) W" E) u0 z4 E
$this->file_size = $file['size'];# T4 U7 E: l8 N4 ]- b
2 Z, |; y: p7 c
1 O7 n4 a2 S, X4 Q5 d$ @+ V. E! x $this->file_type = $file['type'];
! v; ^& E' {- B, I, x / {: b6 ?+ P( V
3 T( D# X0 v! w5 r6 e4 _: _8 t $this->file_error = $file['error'];
8 s. I9 H$ }( p& @3 j% R6 L& n5 w) T1 j & r; w! `, q# o a
+ m" n/ z5 v" j2 D: y U% p% x% Z M. A) F% A6 j
0 Z3 p! S& `4 v& A1 ^9 a
4 i' W( g. u. k2 O" ]2 F }
6 Q4 n5 W! h. q- Z7 K& c1 n 3 }5 i& Y8 t- I: p! d* c( J* _
& q9 p8 H( u7 ], Z7 w3 P" W3 T { $this->userid = $_userid;4 Z3 V% F7 B% A& V+ T3 p
9 ~/ D! K& N E: H
2 C' S8 k; x. f7 T5 V% x. f/ E $this->ext = file_ext($this->file_name);
: @6 C* _" p) X# ~# y" n( Q
* |2 R, z; F- l8 ]# @+ L! P- _& Q5 E X" w5 V; ?; G
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];: W6 l0 }: }9 X; S4 @! b, R5 O
) @' M' ?# p: n5 z
5 |9 ]7 n( ~ k7 F% Q5 A2 P $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;/ M/ V5 K2 d4 s. k
1 {7 Q5 \* ~& M
/ _6 Z& M8 W2 T+ c0 o% \, n2 k, C $this->savepath = $savepath;! j1 Q+ }0 V L) [
" ?5 u) F. I$ y+ e' w1 n
, ^; z0 B* @5 q $this->savename = $savename;" E' R& u k/ q) U$ o' y/ l! Q% \
p+ y- ?# ]8 t
8 E) C* E- y i3 l5 |
}}/ [/ Y4 I: q0 m4 e" n! @
$ e6 y9 q# \7 E. q- @$ y
: k n# M# ]+ F8 H7 i2 a 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
* E9 ?8 c0 J' ^7 N 1 S/ L/ Q' q( [* i
' c5 H3 M( C& D+ ]8 J; J 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
, v1 j, ~- p4 o1 c5 p4 i0 _ / c5 D" d! H' O6 p5 G, F
- |# S2 d$ q3 d. Z) B' J( I) z6 n! M- M
$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
+ a- |- i1 b: \
7 J& w9 [4 a: f; M7 d3 ^# `# n. g! K' ]2 p; e) D
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:2 |5 E! s; N6 f( F* C( Q# Q0 d
+ {1 x8 F! n5 h& E; E' x" C$ ~+ c/ I6 @9 S- H/ C. d
+ F! T% ^' Q( ]% u
* T! x* H% f5 L0 f; c S4 V2 h: m% i
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
1 C: E7 Y" O3 R$ X( t0 I* {
- ]7 r& J7 y" _3 v6 S" o9 U% j1 V& N& n( j; n+ l; Z
<?phpclass upload {) m/ G; ?: v3 b4 Q
7 J$ @+ k, `) a. d1 [7 O1 V5 c7 @) R% r
function save() {# {- H/ O T F- v+ R' B5 [5 v
2 K' S2 W& W, A$ A4 z' O) n0 v$ W! m2 n6 P
include load('include.lang');9 O2 Z4 }: Z1 @$ K0 L# l: W6 A
# `, j7 I/ u) m1 i6 b7 _# H' A" @/ `( t7 Y7 b0 V/ ]
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' P. [+ b5 @8 S$ A+ j5 D
, R1 x! g2 k5 ?9 I2 V! Q2 z6 P
& e4 ?' ], H! P. y* e+ }1 Y % L& E: P/ T! J
) o/ M3 s- v5 L8 T* `9 H if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');& v$ F' }# u4 @# v; k2 ~& [' Z; u$ ?
4 Q( Q: ]* u1 M; b5 c9 S
2 l4 d7 n+ u7 |2 Y8 }) z' Y7 m
; g- R# S. E3 m
' |7 _% q; s" Q. P& [0 ]' J
7 S4 c! M1 g+ h if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);8 G# K$ E1 D0 t! e) B
: N1 L& ` M( H$ F* P
, k9 h) x9 t6 _& p8 a. j' Q5 d . M, ^& \. g. R8 H- B
) v" x6 D, r0 ?) Z
8 t, o* [+ |6 s $this->set_savepath($this->savepath);
, P8 c# p; v' D/ L w5 Q# `) x% M$ x& B2 j
) n8 k3 i& D$ P6 r# B# G) W $this->set_savename($this->savename);- E$ I5 Z6 P0 ]
' }. e# n+ J& f
: ?1 l' `8 K" j( k4 e
& l' v. J& U( g3 e+ J / x0 M: D, m* a0 B* u; X
; r O3 F" s& ]" g# d" @ if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" o+ ?9 e8 l( a B$ p. \: e3 h
$ @; |3 I _1 M: Z! e
% H! r& k, M0 W Q1 v7 e if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); c Z9 B, Y# k+ w1 K) j* O6 p
- S- W6 g; F$ v( G* T+ V
/ a/ N, c$ E. O' |( ]/ c, e if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);3 \! ~6 C$ [* I6 `0 i
5 p* |4 o! N+ _7 x, q
0 J' o0 p, k1 i# B
" e+ m. r7 o% g0 J, S. l2 f : c" k# c5 p7 @0 F
. Q) r* d8 R7 q, {$ f
$this->image = $this->is_image();
2 c4 o) ^1 X8 K0 L z
2 d+ Y7 s" q' N% t- ?! B T) T# t0 I0 V6 L4 ?0 Q
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
" N, Y \; D. j
. ]% l# b6 n! s. c
) p! x( C2 G" J8 u0 E; ?3 ? return true;
; @" e3 {" [& h
& ~8 a' K# R {* r- ?2 `
j2 z$ D; S( ?# C7 ` }}% C7 J. z. \ j8 J
2 ~. C. O& D5 E* f# q+ _" n
. }8 C4 P9 q8 H+ g/ V
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
* ]& T8 D; V1 [. o ( t* Q Z. M6 p
1 w9 c+ A8 n- ~ m1 b <?php( v, c, z! \. p3 W! n. @ m+ I; q1 d
, l( r& ]# \- X x
' {; U7 x& K8 t$ k function is_allow() {* ~3 S! D6 Z/ a5 H7 t) h2 b
H3 w/ ~1 W0 a6 f2 C* L! a1 k
% J' O: l+ U. a6 z- u& { if(!$this->fileformat) return false;
+ }5 _, m4 {: t# q! q 7 j x5 ]; N* v( n3 C: S
" Z W+ ?$ i3 `- N0 M if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;" w; B" d/ b; C) t( Q
, K2 ^9 D h; g7 p
+ R0 U$ l$ [6 @4 r- |* R. i' 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;1 Q+ Q: {* H }; H, Q- D: ~9 B
. P8 m/ m W. n+ C8 U+ _
- w! E( E4 L6 u# D return true;
; J# r3 R( `1 w/ q 6 O x% A5 t7 r9 {1 C8 @
$ ?+ j2 X$ E6 f5 c6 w: e
}
+ ^" @' l$ Y) f. W
( z% [2 { W! P( [
1 ^ l" ]6 S& b& C% R" [ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。& L$ |0 } I% N1 G3 w+ c/ C
2 V( i- u/ h; ?* Y( Y; ?/ g
0 c) C; M+ ~9 p; U; x2 O' ~" {
接着会进行真正的保存。通过$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文件。
+ {9 h" r( `! T9 s' _ 4 b; {( H$ G( }' C% L, F( U4 @
( R2 X8 ?8 T8 J/ G9 [ 漏洞利用
) A9 x2 C( t: m) Y' L
! I$ s6 |# R& w0 M5 x5 G. U0 @# t$ K( [3 ^
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。' S5 @' w0 @( O
& _6 D/ l. h3 u9 g8 T$ K) z
: E" f. V4 i* O
' R5 Q8 \& s) {" X
) O3 l+ J! b7 C2 n! _7 u1 t4 Z1 c o
2 |4 X; q6 z4 J- E 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid' F7 K% J8 h. e: K& D
/ V- C5 i7 |5 Y' j3 b K
$ l8 }# F: u3 }- e$ _" _0 b 不过实际利用上会有一定的限制。' L# ^$ A" Z& g0 M& t' X- C# ]+ x
/ E- c' S7 b+ F/ ~5 W: g
\9 L. J- J; h2 c9 K- J
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
7 Q# a6 d- M+ G9 ~1 S, s h1 ?5 c2 q; D
4 Y- _3 R& j" M$ {( A8 T/ m) O " n b" V1 r. A$ e' A) P
' e4 U( C; e7 A+ a4 C
9 W; k: I6 c3 l6 i0 E3 c7 ~ 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
c: Z, ]9 j% n
8 Z, c: o% w/ s5 x" \; |# }
% o% n# k' c; _2 I- 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]);省略...( z+ k2 e0 f" O8 b _2 @
2 G4 z" K4 n" J# P
Z8 T5 @7 e; ]/ b7 R* a0 q
因此要利用成功就需要条件竞争了。6 G' R! j l3 `$ u
. b, l3 t7 c. [ I
) R. E% i+ E% i6 I) g 补丁分析
$ p; _& ]: t. A$ z+ _
9 i* b' ]: }# `3 o0 E1 U! e0 l
" H% V" ?; B% ~3 g# h; }: ^. a $ ~$ y+ N" ^! b1 i# T7 O1 ~8 X
9 F. E* Z' N( x8 q& I" X& E, f4 \% m% u" w* \. C& M
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
8 U# h" [, I0 ^& k1 ]* t; P; F4 d
! \1 C' p, o: T1 c
! H" G% E3 g! u. X1 J' \9 ]6 n, V function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
- B* ~0 I5 G2 J6 m* \
X" @* o2 |) I4 y z4 R4 g* u) R t* W# I# h
+ |! }+ q1 m/ ~
- ]/ M2 M/ q+ D
* B0 }9 B% V& x* x7 J- y1 S# ? 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。8 v: r( a* e1 `) _3 K
. V3 L" J2 o2 I
8 e5 e L7 ^6 L( P3 b8 P
在is_allow()中增加对$this->savename的二次检查。
8 r' m1 W; r1 q- m; a, G# D
/ c# O" f2 n& ~" W% b5 D# M: s* j/ ]3 R: c$ j
最后
* l7 m6 _% {+ v1 D8 N" |0 \2 S q1 R* I/ J( Y+ Q, P: t
. v- l f% m/ g2 p. @& h
嘛,祝各位大师傅中秋快乐!
0 Z" J, C9 z A8 w* q
0 i5 [: y. }6 c& ], d$ p; E5 x$ Y* h. E
8 O+ [5 B4 c1 V: h1 Q0 | G
! S" o3 z% r( R$ m1 b$ Q7 I
|