1 y4 A. c4 o% b2 I 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
\* R* {7 M- x; U" [
根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
% t) ]0 T9 j, S. K
对应着avatar.inc.php代码如下:+ x/ O* u6 s6 r# |5 P% I* X/ }
6 V0 p0 f2 l' a" g; a# t5 X; C; ?: \3 m1 X- 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) {
: Z7 S* K8 e! P3 r" G, p9 q! B4 q6 ?* k! o' B: a case 'upload':
7 C) q0 ^5 t: S: q' b o0 {) K. }4 ]9 d2 @' h" m: q" ? if(!$_FILES['file']['size']) {8 x9 ?0 b# |$ x8 {/ V- c
7 g& ^( _; V/ O1 S$ F0 T9 J4 z- e! B2 J( ^ if($DT_PC) dheader('?action=html&reload='.$DT_TIME);3 D# z4 z7 V g4 H/ D
# I% ~. ~7 ?5 ?& o0 U( o/ sexit('{"error":1,"message":"Error FILE"}');6 Y8 Y. {4 \& D3 l- [' P
, o1 u3 ?# G8 a8 P" G }
require DT_ROOT.'/include/upload.class.php';% H, g; [$ o) L" v4 q2 L
$ L3 ?6 n! _3 k
$ext = file_ext($_FILES['file']['name']);- B* w0 p& ~2 l0 @1 i4 P6 ?% ]" w
0 Z3 X7 O% h' Z$ ?( ?. c $name = 'avatar'.$_userid.'.'.$ext; w y* D: R ?$ _% _" D# l6 }
$file = DT_ROOT.'/file/temp/'.$name;
8 w$ v( ?: c1 Z; w' m# }+ G0 G1 R1 `+ i. Y( W: c
+ a2 p8 c, u) M3 G+ ~+ D/ x: F& Z* {1 w) E if(is_file($file)) file_del($file);" o, P: _* N Z2 Z& m7 Q; J4 @
6 p- ~9 ]% _5 A) T$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');# G2 y; Y5 n! J
7 [# p0 S i- e3 @2 o# d+ b4 E C, n3 n6 x" o& U8 s( w Q$ C: k* N- M% Y/ `
$upload->adduserid = false;2 |* N& W* h0 a. y& {3 k" [2 x
; m7 i1 E) \8 q8 @
4 n$ S3 t" v+ z3 K1 O0 M s4 b/ m1 l if($upload->save()) {
$ p7 d# D- }3 w" E; U2 y- L% {...
} else {
5 p$ Q, f1 A4 w( C0 L; F, H ...
+ I& s# N: d/ j0 y* x I! h, o' R K}/ d( E J6 S' y/ e, [
break;& G' l- c, F( s" c
2 P1 @- V: R3 A0 y; f$ [0 @( `" I! |这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。* q$ c0 [# l4 m& _ V5 d7 `
+ [; u E1 A4 ?5 Q" W$ g5 p+ x7 C9 lupload对象构造函数如下,include/upload.class.php:25:
<?phpclass upload {4 V$ ]) D0 i1 Y+ ]( X
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
global $DT, $_userid;, k7 N! N# x0 x- r! m6 U
' ?4 Z' P4 i- o5 M+ s! `% Y foreach($_file as $file) {
7 H# X: k" K6 Y! W: b) Q7 P $this->file = $file['tmp_name'];& {0 {6 B8 V; C/ d: p; z0 u+ E4 ]+ u
. T6 d7 `) F7 ]8 `' e; b$this->file_name = $file['name'];
$this->file_size = $file['size'];5 U! ], z1 `$ c, A3 c+ i! {: o
$ N# @* z0 A5 c) i# D, {6 C- b' d8 S& ^& H* o- S) C $this->file_type = $file['type'];
) j, w' F6 Y* J) j" ]( S2 _* V $this->file_error = $file['error'];. ~/ y$ \! P. R$ `/ f
* s- ]7 H( L3 G5 @ 2 x% t1 i) I3 o4 D# L) S, R: g! R
. N( }4 K+ |: K5 g: B( e; U! d; L! L" L% B9 P% {# e }6 e8 a% J, _3 C, d
$this->userid = $_userid;
$this->ext = file_ext($this->file_name);
( ~& W9 ?# c1 _* @+ w2 G $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
8 N& J/ I+ H- ], Q+ [1 s; d# o% b* Z $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
3 Z' ~, ]! ^ s$ a $this->savepath = $savepath;, {% `6 f5 C3 x$ h: Y" g
7 u f( s" r6 {+ C% d4 t! c. @( U $this->savename = $savename;) V% n+ w o" N4 \
+ Q- v, k; H. V8 z/ P% e3 C! B! T }}
9 e- B$ a- t) p) C" Y# c0 u 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
% t! d# l* @6 e7 k因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 - D4 _0 v2 [: e9 t* T# F; e% 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.php3 o4 h1 ~( `9 X! u
% N. @' K$ F: S: W/ Z而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:$ T7 Q' O0 G% j% O: [
$ E6 i/ f0 z; x- ], U8 a! {* Z; i- h
& d# S, z$ X' D& I! l
( A H2 Y1 C8 U% _7 F2 H 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:! g, ^1 i) h5 \4 n
' h: A4 b, ?: P2 r0 d+ Y2 H' j* I8 H' v* Y1 [ <?phpclass upload {
8 ~2 ^5 w. ~/ [/ n9 X9 Q `9 f( k+ q" L6 o: j, F! g" ] function save() {
1 v( c/ a5 o/ }" H; @; g* vinclude load('include.lang');
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');: O) w: _- n5 \2 T( ~
# \. l3 N6 O1 x$ Z9 r. L+ W1 H; q# d7 f: q
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
' R# H! S5 \2 l( u( v9 s6 A0 }6 B- \& F1 ]8 u5 G9 O7 x/ |- n# v* z
! `( U; H( u U9 w; }2 c" n1 N! y( v8 E& i D if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);) i3 \: T$ b- ?) a& B
0 i+ R- R, v% y a, ?' p
& m# { C; m3 e9 l2 c; Z$this->set_savepath($this->savepath);5 j/ H; J- k! s' T; Z
* L9 \& c5 L5 x- a% p2 A$this->set_savename($this->savename);- I9 d8 G& [- J* }, F' W$ i
% v3 H" w% a" v4 }' q) p% z9 T# X: t( k
" S! P( F; E( C0 E& w) M1 _5 d! Q6 U: ]2 G if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);1 `! W z" H! j6 [
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
3 l; i+ ?6 x8 {5 C/ a- o+ E- J if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
" D( J" ?7 H. ~! p
! O* b# G& d: I. n. ~) ~( { $this->image = $this->is_image();9 v0 T) q, b- }
% j1 ^( ~7 k0 A2 R7 n% A, X) q; Z$ n2 y7 @ if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);& J$ ~* V9 L( f& v" P1 H( x
B+ W7 [: y5 {4 H9 C, k* c& j9 ?' ~- E% y9 J3 l+ I return true;& E' O5 u$ K/ R
}}
. y% {* j0 [- G( B2 R( f 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:3 ~# S7 B; e; n) M% h ~9 d
<?php
function is_allow() {4 i8 t6 W7 ^' o/ w- j% ~5 e
3 n$ E! i X9 ^if(!$this->fileformat) return false;
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;# x! K& D- G: i5 a4 @ A: G e
- U* l5 j$ t0 _' x$ J- rif(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;
8 ?9 h2 M0 j4 P6 i2 O! u- Y$ D. R7 p# `3 z1 f return true;
2 h6 s# c* |! s; T* K$ o( v7 j}" ^8 b" _0 h/ _6 A' m
2 V6 T9 N6 Z# e& w可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。; u3 L& ]9 t) h* |; Y
9 P6 I+ [% Q4 z) {) V1 \' | 接着会进行真正的保存。通过$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文件。
: Y2 [# U) a9 W/ N) J2 Q) [; d* s) b1 V b 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
( k* ^; B' t, {( ~8 n" A# O
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
; j9 r- L5 n- g! z# F 不过实际利用上会有一定的限制。
3 V4 D3 c4 J" V) b( H' e2 k1 z8 s5 D+ a- Y 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
" _' W2 P, _% X, |4 P( |! p- @ 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
& }4 X, w$ i; b' a' z' t! | 省略...$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]);省略...( ]% I3 j, a: h) w# w/ V
* J e. V" u; k- U7 _ O" E T7 \因此要利用成功就需要条件竞争了。" D. j( V1 i {- v0 g( n; \2 ?
: V* w0 ]; ]' X' _3 C
" A$ X- D( ?) W! k: ?6 v
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
' H3 X* }) w9 [4 D Pfunction is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}* h/ f1 b# ^' o$ c) Z: q; I/ _
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
: S! A, [, n# {, K1 A6 X在is_allow()中增加对$this->savename的二次检查。0 }2 ]( t H- [, f
# @$ N+ ~# h; ]" j$ Z3 T, g$ Z 嘛,祝各位大师傅中秋快乐!
9 @5 d. _* k0 V5 r1 `# J- s
| 欢迎光临 中国网络渗透测试联盟 (https://cobjon.com/) | Powered by Discuz! X3.2 |