' o/ ], \" I! G5 X6 }- s* I1 _5 h& i. ^% m0 B" o! S
1 h. }& j( U* W
; x7 Z9 o6 j, ?* a& r1 Z 前言
' s: L! Z% j m0 Z. G; w. k; K) _9 u% a) @5 h( P
7 K+ a" n0 s* k0 W" g( c 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
: ?4 |/ k0 l/ {: R, p! m . t4 n. T, B- {/ z. L N
# x: r1 h; ^6 o4 ~' C# \3 `1 p/ m ( t! A! X$ a2 |( q
4 K2 c+ G4 N1 F) w0 H" `6 |
; @1 d* n7 J) I0 `. c* f6 f 漏洞分析
2 o8 P$ b1 y/ D R: w" F1 F* r$ ^5 d* D h+ K, L! ?& ~! f' {# L
- I/ ?- b W( y" t
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:6 x) K/ L9 _! I( a7 ~8 a4 u
# K3 b7 c% t! |/ ~$ M/ S0 E. j5 u- U. n5 K3 C
, ]5 v$ G' D% z8 r( e# ] $ X I$ [' q) l/ y" \& M
' ], K+ E' I& J) X
对应着avatar.inc.php代码如下:9 J% m. `! i+ Q$ v; H/ h) _
& v" k2 ]( m- E M2 l, X5 e5 a
! G9 c2 ?4 o0 T2 I: e/ C
<?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) { t! R. c h+ _* a9 \0 G8 W
5 k% e! z1 S9 \. B9 {' o6 C$ ?. [/ h! ?
case 'upload':& E; ?3 h, ]' |9 ~
+ I" Q1 R0 x0 f8 y2 B5 {% a' [# {+ {1 W/ S- P s
if(!$_FILES['file']['size']) {( [& z0 E o9 L0 b# b& o1 Y
9 K! a. s6 f# R) c! A' Z1 F! Y3 U1 L5 @, h% F( g
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
u- u) W8 I& _3 P
; |4 D" C% m) y5 m7 h& ?/ r i" D4 O6 }( k& N7 ^ w
exit('{"error":1,"message":"Error FILE"}');
) W$ ~" G+ w5 @; {+ p+ |
4 Q2 i. H8 R7 u, O# [- S; n8 x! U% v1 q5 r5 L& @) U; M
}
8 y+ P* h0 ?3 \) K+ l" M
% k; ~) n3 Y. g7 C- E5 `
7 V) Y3 z" ~" Y8 i6 Q8 _ require DT_ROOT.'/include/upload.class.php';
4 A. I* A. l+ R8 V% w& Q ! Q6 W, l2 o; i! y0 [0 ]+ q2 _
6 q1 ^/ ]$ \; e" e. D E 7 N7 I: F5 Q- _% j" C* x' l
/ @7 w0 Y+ b4 O2 U7 }
. t3 K( T% \* T! o# O8 z $ext = file_ext($_FILES['file']['name']);
% m% R' c, \2 W E% h, |. a$ ? 5 r" \4 @0 |( b u3 }
: [! s8 h0 c+ i7 H1 |2 |9 G $name = 'avatar'.$_userid.'.'.$ext;
% X# a6 d3 s3 U" i6 d, s
' a6 J3 ~. d( T+ t
- {. m& B+ S) B' A' J $file = DT_ROOT.'/file/temp/'.$name;
; m8 c T' G) e% Q5 ~! J/ {
: Y8 q& r9 c, {: ^
8 p/ j& @- h! x& G+ g. F
. s6 ~ Z9 q0 Q; v" }" G 4 Z& u0 I N" h# B8 W; g2 E
8 ~/ e2 g# q2 G/ t( T! { if(is_file($file)) file_del($file);
$ h7 U8 i, t" g; a) k$ F4 P% v1 y) n
4 Y& G; e+ h- c5 p
! H' }5 W5 t& a, Y; f) i5 R $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
& M* j' m* c/ E: X' r " c3 @. Q$ l) F: L' P0 L
* K+ `% a! ^! q: H. t
$ n7 M" ?% |$ l0 d
, O7 y2 @; k# W6 V" r1 [9 t/ {0 _, G' s9 ?+ \+ h
$upload->adduserid = false;
$ {& W$ W# M$ X& K; o0 u- R7 o, i9 y
) A, q" { r, h% M' U. c5 i, w: K- d! [( q
, _8 n' V) _, y" y l4 K$ \- Z- ^ 5 ^1 T9 H/ |- u. h/ ~6 B5 O3 A
* D4 g1 Q7 w4 X; L
if($upload->save()) {! i2 a- x* A! v; \9 W) B
7 E* v& X+ V! c* Q5 G3 n' c2 D! ^( S: x6 N/ h: \( |& d
...! d- d6 a: k; t w/ n5 G( R* l0 C/ v" d
* F. H/ z& s8 X( d! [8 x' w, v2 N0 T6 K& D# C& T
} else {
7 q+ ^' l K. J" Z+ Y' ] 9 c+ Z8 s( V7 F( L# ~( j
. g" y/ C& ~1 ]6 `: R' c1 u/ O3 I
...' ~1 a! ?- r! @- _. `* ~2 G
) a9 u" t2 ]1 Y1 X, f7 p7 k
. u# @5 e: ~3 p% A* |5 @& n3 r/ j3 n5 K
}
8 d9 l9 P" D4 Z9 K, j $ ?# h0 T2 z$ d% y, [
* r4 D3 a2 }; S4 e
break;
" t4 w: O& T F+ v
" `5 V* j b" s5 |. y% @6 t9 P! ?* B$ s# ~! v
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
+ Y9 `6 ?) ?; o; L a( H
# x. y0 p! v; P9 a6 L$ [1 q; f' |6 L5 Q. i8 d9 m
upload对象构造函数如下,include/upload.class.php:25:4 r9 W0 {* @: y, u
4 r0 {' O* u* z4 G0 T& {! A% l% z7 K6 t, Z% c4 q4 x) s( R# m
<?phpclass upload {
2 g" Y8 h+ q+ h' n
; |! t: d |* k9 O! }# Z6 }/ w! i; ^, m; N
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
; q. `0 |# m$ j6 ]! u+ ]+ z 0 }+ n& N. t% b% ]: w
5 Y; Y' S' @9 |" a global $DT, $_userid;, z2 |( E8 F) ]; Q, P. g
$ Y% o1 y) I# r$ q" f" c3 h7 Q1 A# ]5 w. c
foreach($_file as $file) {
, i# m( Z5 r& D3 q7 G- L 2 r3 y+ z g/ }+ g( k+ q
* ]+ ?7 H e& c$ c
$this->file = $file['tmp_name'];. @; @) |" Y" ]6 i3 d; M( Y
x1 I: E) r3 V. z. Q& S
2 S7 u+ V) C5 k9 G- n' ?' y, [
$this->file_name = $file['name'];, P6 A t+ V' j& T! F0 k
- g, f% h6 \6 D* q
m2 E& @% @% f Q $this->file_size = $file['size'];7 i* T/ s- D3 G
6 Z! r `6 {. R' q4 ?
- \0 S+ z2 R$ A( Y( J B5 z* Q N' { $this->file_type = $file['type'];
. d( p n! A: j6 H! |0 ? ' T& a, c$ t: ~: @
) o8 `0 V. K) q5 c. `4 e
$this->file_error = $file['error'];8 h/ Z# t9 z7 {" M* w8 T
/ Q" w" H+ t6 ^. o$ X& E. H% W4 I0 g! N- u* Y
e+ k; n) A$ q
# p% h2 ~ j4 Q+ ~. p, ], R0 r; h3 Z1 r. p5 q q1 z
}
$ O. I$ b) N' ^2 Z' E( G8 v' E
1 j0 ?$ Z% m+ R5 F! X0 b' q+ ]6 K! x
$this->userid = $_userid;
) g: } I( J7 T0 \9 Y3 O 8 R$ q+ D# R: e& n5 a8 e6 Q2 F
, R6 L' {; [( d- d* o; ^
$this->ext = file_ext($this->file_name);
" c, N- n# ~+ v" B3 b) t- y' R
6 ?! I) R6 q4 N9 g0 s/ W5 c1 r, W) m
9 @% N" t0 G' }+ y5 u' X $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
5 F$ q- \ P' W5 V ' i5 D" V& R) M4 W. u
0 s, `$ t# B2 q5 d. v& Z* m
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;: t( ~. r4 | u& a, v; Z4 Z
- [0 P# v) S, F4 n3 a" z; }7 C8 O! n1 b
$this->savepath = $savepath;
/ L: B5 ?& B. ]: U; ^% w+ ]) `; W/ H t7 a/ Q+ C# Z9 T0 f. Y* P3 ]
' g' v, A L) z/ Y6 ?
$this->savename = $savename;
7 k0 T9 U+ H5 D- V/ v2 h3 Q 4 _& u8 w3 l6 G% z+ h2 U
5 f$ |& z ?# Z: v _+ h
}}
3 L4 X. R9 }- `4 p7 K 6 x W* V4 R) g! t) q% S$ s
- g6 w$ M, @0 e7 Z6 p 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
1 s/ g8 l6 R% ?- V
) ]9 w1 [+ g3 g/ F( R/ v& O, h
7 e6 y0 Z) J4 F& g0 v 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 L+ f& S1 K1 Q1 {/ {
7 \5 {8 R, ]0 j
B) i3 @. l& x/ 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
4 [( Z. L; a0 |6 ?
) O9 Z1 v% D6 n/ H$ S" h
* ]+ i/ a T! M 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
2 {! m3 [4 u6 s5 `; m0 i 4 K+ u! x; ?* e
" F% g6 n0 v. w: E$ e' O% c
% n1 Q$ C2 o/ m1 e+ D, f 2 N6 D8 u# X& x" Y9 z9 p% T
9 T- N# o/ e4 r) h
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:2 h8 p! N# l2 i6 s* Z, e
5 y. L8 c! z" o* O
: ?9 ~6 d7 ] ^5 p' }/ L
<?phpclass upload {
1 \# Z# f* ~% b4 ^3 a; B+ w! A
6 E5 k3 W( \# C$ e: c) a2 @6 J- H# o0 ]) G9 x- y
function save() {
' p+ I( K* S2 H: N" ?, E3 b; r
5 @. \$ U t( ^+ W/ c; `0 a) y* @3 x( y: T! n! U: K
include load('include.lang');
! o0 k1 h! B$ ]
' A/ F( [+ s& X0 ]) Q& P C }9 [8 ?0 k# B/ b5 a
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' Y3 R. {3 x7 {4 g6 Q+ O) g; c
- y7 a5 E% |: G- ~3 _" U; n8 i' ~& { {; N3 i
' ^$ h, I( R `. w- _+ |* h
* ? k& }/ i1 e7 X; O# I# G; O4 G4 j# {
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');. X- w2 n0 E5 K7 I
6 b3 \2 b! M% U; H' x
2 x, M2 ]6 y- W1 M/ j% Z ^4 v
( m# i# R6 W) N' V$ J# q
D6 F, s8 e$ x4 A, q
A; ?. ], L9 ]! o, R if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);: p' I. c& r" Y
; u3 y7 P% C, Z- t: a8 [; c1 y/ K1 d* J
7 N- p1 T t4 S
' f+ ]. T7 y6 P8 ^' X3 L' ?$ s
6 f& Z9 G. u* B m/ N* E! G
$this->set_savepath($this->savepath);9 X% Q- f \; T
2 I6 [2 E K/ f, R" E! g4 q
5 x, |$ E* _6 g
$this->set_savename($this->savename);
1 @0 @4 |3 g' K, ?( b, q/ E
8 q' a8 Q2 `$ h9 w2 ~+ `1 z
" d7 N: ~3 Y. k$ |; T% h ; Y6 Z5 t. u! F5 X" e( v
. W, v* O* u9 m3 [! Q7 y9 x
6 }) q# G @+ K8 N) j* `* n if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
8 B3 j! z# `# ~0 \ , p& _6 e" T& O; I% P0 s1 c
- P5 N" P5 c6 J* ?
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
' K* s \4 Y. B' G$ G: G1 ` ( }+ T' c1 c0 Q9 |: f) q
; G: P' C! h7 U; }- \& ~ if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);" J* D4 F1 M% b- X7 @: Z! u4 `+ D
* B9 Y* |- f( S2 G, G
9 J% D8 Y) a, ^" s* K , k8 F% g. V4 a. x
, P/ a8 y: Y, c% k) z( V
, y l+ a8 Z: Z& e2 A0 B $this->image = $this->is_image();9 V# y+ L# _& v$ O) {# {% m
+ t* \- y2 f* f6 @ {+ y
1 b2 Z5 a. `$ o
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
+ O: {2 x l( `4 {
1 O5 q; k6 f0 w
! N/ e1 _0 C* f% S return true; ^8 k* E# P' T& G5 a
" J2 j4 h: }7 z# z9 g% ~
/ \4 d d, v$ h S }}( {2 J& b' q; |
$ p6 `6 |" W& h: _
, x/ t" T* F s( f b2 A- p( Y) a, A0 N 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:) s4 ^. M0 F9 n3 X& m+ K) {3 ?1 `
+ s4 F, e4 J0 m! h) t
+ }1 B9 R! Z- S& R/ v4 X! C4 h/ b# h2 o <?php
. p$ W& P. g; O/ Y& m - r: C; u! C, }/ [
" f4 U+ n f# k3 r) w
function is_allow() {- m% H7 U4 Y2 \0 a
( M3 i. U r7 P. }6 ?
/ A+ j& |# I% H8 i; u8 v if(!$this->fileformat) return false;
0 W+ S- W/ j! q U$ d- K 5 I0 ]# ?# J+ z* p, ^ w, H4 v0 X) Y
) k( L# z+ I8 i( b& ? if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;/ W' @( o5 R2 J. U- I/ _
# c4 ?$ l, S; B. Y' Y& o# G4 u
7 c+ b% H( E" [* `# Z) ], ]
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;
! V+ _( ~4 |2 b g
# O! ~5 f4 M8 C& @
0 h0 r; G! E" E( C1 ? return true;6 D5 |. \6 F$ q! y3 G
4 S9 z% U& e* W7 x/ O8 Y
6 f9 Y, g- l+ r9 D+ v" c2 e }2 ?! f$ C/ n( q4 E2 E
% g/ ~5 c6 d; ^' t# M) w8 R7 r$ X& p1 l
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。9 S1 K% t) e: R" }4 x Y
$ n4 H8 S$ q7 r' F, v$ n; `% `; M+ o: }3 @+ l/ G4 Z
接着会进行真正的保存。通过$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文件。% ^* ^! X( Z2 D$ o* V: O4 O
, R4 \, D( m3 |
: t& d5 g5 v) S% Y* R, |. x- f 漏洞利用
2 y9 [: p1 F) t0 b# ?! M- X4 o0 I2 A$ O. F% V" V
. T4 t3 \! e- Y4 y9 B ?6 `
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。1 `4 S- |' d* O% ^$ ~3 I
2 ?& s3 c0 x2 @% l/ U) f
; O. z, X) {: Y( D6 n# L, [4 g* a: r
+ w) \9 j4 T6 d) |8 o& E* C
/ A+ ]' S, K- `3 k
6 m+ C+ v- `6 ]+ O9 u 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid' A3 s% G0 Q( D
/ C7 W4 J3 v. d9 z: O6 i
" J; T! w1 Y: u: P# ^! V
不过实际利用上会有一定的限制。2 n' i+ q6 O% U- T, ^
$ c; p0 @; |5 f+ n0 w" V& O8 v; C0 u w3 r: t0 c
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
3 z s0 `! l/ V# O# b+ ?. Z1 {% K" t
8 c* s/ ?# y+ O, C$ V* Z
% P" p |2 i. L& T! V: n. M+ x; ? 8 p- i1 n) W, [& L5 z/ K% r6 Y2 R
8 l! m2 B! }. V
- m4 `6 x! E4 b7 x! e 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:! l4 g, R) @3 x2 p) E
3 W2 g2 C, T# G9 c1 O+ m0 z
& v9 W4 n: I8 n# F$ m! e( d, \7 h
省略...$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 l9 U/ Y4 p( f2 ` & Z- n" h, v* I4 B2 A
6 o- c1 u" q5 _0 W6 G
因此要利用成功就需要条件竞争了。
* D& }, B5 z3 o : J6 s( l3 V/ i: q8 f K. c
: f/ i7 |' Q/ O9 f" ]" I
补丁分析* s) L6 F: b; c# F
) _' _1 r4 Y! x
- v& i6 c1 G" D9 }' O0 ^) D" u% ]
1 q# ^, g7 Y9 j3 l' q& T
: F0 v4 M- \3 c _) J3 w" j7 j0 E0 Y) Y
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:) Q2 i" c! @+ x
, t0 P" [' B) Q7 O( O5 e6 P
9 O; l4 s4 Z* A) D; H' Q
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}9 R6 \* m3 n' j
, X9 I" E' j7 `! n( P
) B, T( Y- q$ {" m: F
6 {( Q9 X0 q8 Y
, k# }) v* a$ \5 F- ~3 S+ v
5 @- d8 A9 W) A. {4 G$ k8 ~
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
9 {* r2 M( c4 {7 o % ~9 z- D$ t+ v
I. [7 P! S, q/ L t5 C4 `6 J! c' \ 在is_allow()中增加对$this->savename的二次检查。' f( @4 f5 O; U9 v) c1 w
% i* w" k! r# B6 F# g
" j8 x* v' o/ |1 j, b& H 最后
1 F! s0 J" y# L+ O* _# G7 ^' i0 ^4 t A
/ Y/ z) r/ ?/ w% O6 ~* _ 嘛,祝各位大师傅中秋快乐! @4 m' K' K, H: N2 x
$ w$ r2 K9 i; `
+ S! T. t1 l. K) l( b+ Q- n : a- b2 q0 O0 S
% q/ X8 {/ M: {* _4 m9 E8 P
|