2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
* M( ?( d* ^! |1 a
% v1 M2 x5 I5 |+ y; A* d+ ~
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:$ M% p& \ u3 s: B1 @4 z3 a
2 R7 c2 ]: G8 |3 p! C! P h 对应着avatar.inc.php代码如下:
+ K) V( u7 h5 p: @9 P <?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) {. @) k. a- u H1 H
7 ~9 Z7 S& l. h0 T: f {% s* t4 L case 'upload':$ G& A# k3 Z! i$ X1 _+ c4 w
8 q, E0 w# t5 X; ~5 N' q! a% v( \& j' G: N9 ]/ L if(!$_FILES['file']['size']) {! v; M3 f( A+ p: d# A& [. e
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
; J8 L, ^5 j% s1 Z1 X7 d& `" k exit('{"error":1,"message":"Error FILE"}');7 v" ~/ w" Y8 c# {
4 i7 O! j- q2 Q* u, C) t1 L9 r; c }/ v" S; j3 l" h
! e* _* u# g, b% ^require DT_ROOT.'/include/upload.class.php';
* ]/ p* j, l7 `0 A: b 8 f" U" D: H( s ^/ `, |; B
1 h! Z1 t! E/ X6 b' @4 w1 D6 {6 H( B' M! k $ext = file_ext($_FILES['file']['name']);0 ?* T9 F( c6 a+ Z4 W! ~
5 A$ s, y; p3 U3 N; r5 q $name = 'avatar'.$_userid.'.'.$ext;
$ A2 g! L5 l3 h) A7 [$file = DT_ROOT.'/file/temp/'.$name;0 Q6 j. x- ?: A& N
0 e- d5 g) g( R/ K$ q7 N
/ D4 \$ l" I! }: B if(is_file($file)) file_del($file);4 i7 F' o, M- F2 V$ P0 w+ t6 |
6 o! Q" X6 q2 i1 k: ^! N/ F2 Q6 S8 A2 T7 t" Q6 K9 E) ? $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png'); R" g$ v8 L8 W1 d J
& r. i5 \& v$ e) v' \. i X+ @' I! A
$upload->adduserid = false;4 F' E1 \" Y7 S3 z5 k# X! `
3 o+ Z# g9 m; U. E( ]3 F if($upload->save()) {
. }6 T( A! `4 k$ W: n3 |% l: [# l0 h& c" ~1 h1 K ...% c- c7 M1 c6 ^1 Q7 m
, ~& z8 B" U$ L8 w1 x; _ } else {% }% L8 p& ~7 s& M
y1 ^# }+ ^8 G...1 a( v5 e+ I; C& X& p# N
& [7 D9 K! G7 @# ?( { }: M# ]. t1 ?& B U$ `; z% @
break;7 F" p: f5 e9 d) F5 q4 o2 Z) C% s
" W5 ^* J3 v0 \ c" S+ p" J/ B- V% { 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
N) I/ m# N% wR8 ~: }$ f6 ^ f" o/ }) { upload对象构造函数如下,include/upload.class.php:25:$ I; v' v) Q# L4 P6 C
& v2 g0 N0 C- D <?phpclass upload {( }8 X, h( T d. T
function __construct($_file, $savepath, $savename = '', $fileformat = '') { p5 M' V3 n+ x% s8 C- F' K
5 G% C8 M* j3 S% w- |) f$ c global $DT, $_userid;
foreach($_file as $file) {
% g! s, s& j! }% J3 c( u8 O $this->file = $file['tmp_name'];2 T7 v$ [6 P" {, G8 H% r9 Y
$this->file_name = $file['name'];
$this->file_size = $file['size'];( E3 q' n9 R5 U3 n% H. }1 e9 g2 L
$this->file_type = $file['type'];4 |( Q5 q W; K0 {' |3 O
3 q' a0 ]! v( i( l1 @2 J $this->file_error = $file['error'];
% v+ p7 D. \3 s5 x1 d
7 ~# x: \$ W# A4 Q }( h8 o' C, P8 D9 `
4 G. j; M% [( @1 `2 ?$this->userid = $_userid;5 O" Q* n8 i8 F! F Y t
; u; j! g X7 t $this->ext = file_ext($this->file_name);
8 ]$ K# y0 Q# i( [& O $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
& d& S c1 N& E$ X9 F0 o4 U $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
0 D. n0 z8 y) q; ]) }3 t& z1 k) ^- j- {' Z $this->savepath = $savepath;6 f' ~6 O' t" d9 Z+ ^6 _4 ~/ T
& O3 r* |+ ?: R# j( g- J$this->savename = $savename;
}}
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
( r9 ^0 u& h, C6 u$ q" i 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:) h% e" c9 S8 o8 P) I% A) 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& n3 o- {5 g1 P" o* N- F; o
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
; _5 m4 z) t" l8 m5 K4 ]
7 [: e3 B: [+ J/ `5 d* o 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
: g& | N8 _1 \# i% G/ m- w$ }& t" ^ <?phpclass upload {
function save() {
2 _6 L9 J$ ~( v& f- Z$ y4 E! E include load('include.lang');
0 F/ @: h F7 d% r' Y" p5 S if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
1 m, G6 J- t( w6 H5 U0 a x, s. M: [
& B/ b+ H+ h* t& G% P1 C if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');4 W1 u( m4 v$ c9 F
d2 c- _% |. H/ g( Y * P1 r( l) V% K$ b; S6 H
9 w4 V# F! S! [4 f if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
1 O6 ~, |) _1 F7 p: |% {3 W8 c
( T& k) N6 _# n ?" ]9 M6 M2 e $this->set_savepath($this->savepath);8 \& I+ i' k* }: l y
/ E* [1 H$ `& Q0 p2 i$this->set_savename($this->savename);
/ q' x h; d% s1 u/ g5 U
- E, r) l& H, d3 D# r! g7 O) M" K if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
9 i/ t R% x0 v: o$ A1 D) ?1 z9 Kif(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
. C2 z5 s& _- J2 c2 W2 C: ]' o# n , v% y) a2 S& A4 j- O
2 b0 y3 P7 u* ?: V0 ^, s $this->image = $this->is_image();
# s7 R2 z, N7 |/ F& L if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);7 {# r" L2 R7 I7 a7 K
# f. G Q* w9 S" u1 n- j3 w9 @' n7 x. x" f2 y/ [- b' U1 c return true;( s0 f' V9 r5 |2 c
g! [) t3 ^8 _( y7 p) M1 H0 V, i* D/ s" }. X$ T }}# o! R/ X& M, V& V
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:9 o, A6 }$ @; P$ K6 s8 N
<?php
3 `7 U' t& {9 {% I8 a N function is_allow() {
3 }6 u d1 w. y6 D0 Q6 }0 e$ Y6 |' f+ |- I if(!$this->fileformat) return false;
, ~2 Z7 @# X0 ~# w if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;6 p$ s7 T8 I7 h9 J1 d
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;3 g' g1 V* N$ [, {
6 `% T t: M! Z- b return true;
. P2 C5 z; N' X* a4 C }+ [1 k+ M0 l! V9 z% b! r6 z: X
- v) y, w6 j( a3 E, Q 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。2 ]8 b ?* E E
+ R/ m% ?+ N! H2 w' n: A# E7 x" n: o3 L% P! u. X 接着会进行真正的保存。通过$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文件。, m! V- _& E7 P2 a3 u) C# [' V
% |$ d$ i7 b' {7 f 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
, u+ q% |' g. q/ Y+ q
5 c8 z r" | y0 u$ s- D4 { 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
# m* [- { z* K& l8 W 不过实际利用上会有一定的限制。
; \$ i/ [, S. G( y' G: j% Z# e8 ] 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
) U, a9 k, C! F: @( }- X" h" [
5 @$ ~5 O7 ^! e' | 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
( J( f* j& b5 x- c+ D0 s3 V 省略...$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]);省略...* Y# i& H: j2 |
) r; x8 m8 F7 J8 A1 ], n2 ~- A% D5 I% U# I3 l 因此要利用成功就需要条件竞争了。: C5 F% l/ {: R* L: E- Q
: S3 f* |- _& f# _' N6 R/ E
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
: s8 p/ I/ r+ C: P- w7 K function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
1 ~1 Y: S9 w8 ?0 i2 f( ~9 w' V
7 [. d- e7 T: j0 X% f2 i3 M- Q 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
2 z% L9 ~- S9 }( ]3 O' y7 R 在is_allow()中增加对$this->savename的二次检查。
嘛,祝各位大师傅中秋快乐!
6 K$ n% E% Y& p w7 }0 G9 p + R, J, A3 n5 x
欢迎光临 中国网络渗透测试联盟 (https://cobjon.com/) | Powered by Discuz! X3.2 |