2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
; B1 j6 H4 z0 [* P7 t& w$ a
7 G5 Z/ V) L6 h9 c ]% I9 X5 J% ]
$ I. D' k- h' J, K 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:2 X* T7 ^8 R1 e4 C9 ]
# P( \: h) C: s, c; r0 p) t& e* G 对应着avatar.inc.php代码如下:& u& K" ?7 R3 A' M7 g
<?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) {
6 G& q- d+ M7 d. n3 kcase 'upload':
if(!$_FILES['file']['size']) {- [0 }5 w3 R# x
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);/ X& t5 G' j7 a4 E# a% d1 W
. k' h0 k3 } ?5 ]& P8 }) Nexit('{"error":1,"message":"Error FILE"}');
}
require DT_ROOT.'/include/upload.class.php';2 R2 A* R. B8 ?. c1 A& O' F2 k0 i
$ext = file_ext($_FILES['file']['name']);
$name = 'avatar'.$_userid.'.'.$ext;. ]% w5 H0 B! S# X3 x1 v
4 l P5 J3 H: b4 m' \( g) C# ]; M0 A* k$ p; i $file = DT_ROOT.'/file/temp/'.$name;
' T/ K% q5 W+ D' G& X9 g9 P: u$ b6 L, E! g2 C
if(is_file($file)) file_del($file);
) K$ j$ P4 k$ g9 h) ]$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
5 _) r9 h/ u h1 c7 p, W1 v u( _: @/ ~ U
' B* T# O" O! U. A0 ?- y $upload->adduserid = false;, p, K) y) {, c2 u' ^) h, _
" o7 z0 l- r" o
" l( n2 j; p: m2 J. P) X; v if($upload->save()) {
( W" d' n( R% D& c; E. Y$ i* z& b2 x2 R( [% e4 u4 P' O. O5 y1 c ...
9 _+ ?: a6 K: x+ a/ r$ `9 V9 M$ j, _- K- |2 S } else {! j, p& I, V; U+ B3 M4 b
8 s& s$ W3 T" d" e2 C: U( Y...2 k- \+ x6 v+ m1 C1 w
}
% C" [. n. r v# ?break; u0 R( ?' r+ U5 S$ ^/ `& C
6 p( ]8 M8 U: h: C. }# X0 Z8 x( w# a# `2 \! W* @' E 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
) b7 h9 b1 W( W" v w! ~1 U1 l* zupload对象构造函数如下,include/upload.class.php:25:6 ^2 X* \$ E8 U: `) K% q+ q
+ S% u9 J" [2 W# c. ]3 X5 x# D1 u3 t7 s ?! l) H( a" Z d0 | <?phpclass upload {
- D. J) Q3 s3 E$ r" S/ _& I8 e function __construct($_file, $savepath, $savename = '', $fileformat = '') {4 w# `. M0 ?0 P1 \5 P/ G0 }; ]
global $DT, $_userid;0 [, A" B9 x! y, t
- a1 Q' u/ Z! k% `# a8 W0 ~ A( g! I K8 A2 L) d9 U foreach($_file as $file) {
' w/ n* [. ~' H O% x$this->file = $file['tmp_name'];
* f* X+ K& `. S7 o% H' {$this->file_name = $file['name'];
, U! R! n$ H# T- d $this->file_size = $file['size'];
5 f: o& M$ U! P( Y" v* Y$this->file_type = $file['type'];
. t8 `7 ^5 G5 y# p" |; [& W$this->file_error = $file['error'];
$ ?7 b- A& |+ ]0 Z2 h4 _7 M1 D/ X+ g2 J, v) s1 K, c. J
" F* a# m. t' I. Q }
& L7 H+ y6 b" E" Z; S. t1 `! ]$this->userid = $_userid;. Q5 I9 v; [ @& o
$this->ext = file_ext($this->file_name);+ }* L$ n# u* Z3 e/ b% M4 z
+ r- P! d. v* g7 s+ z $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
; W# E }" {9 `$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
% F- P" b; c7 `& S5 _- }8 s' c $this->savepath = $savepath;) r% X# c2 M {
* @& N; S0 i3 w. n- M% ]+ L4 D4 ]7 U6 p: o. _! A1 D" ^ $this->savename = $savename;
}}
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
: M2 t0 d! p9 w2 ~0 B% L/ b7 t $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 `; ~0 N/ W2 L. i e9 I" e! f+ O1 s5 M 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:3 m8 ^( y2 E3 F- {, h1 p* o6 M
8 J# B' n) t/ e, v$ [3 ]- F6 R! O: @. P6 ^7 ]: d7 U
8 ] x) ^$ _7 u
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:& q% ^! B. _- A) P m- ~. O3 q
4 P1 g. b% ~. O' K T, d <?phpclass upload {
( X9 w4 Y* F$ W: L* l# P: m' Z6 o1 _3 k function save() {8 ?, f* F( _ t) A
' m `5 ]6 d8 W' i2 r9 Q0 uinclude load('include.lang');
3 t; A+ b7 X4 Q1 c4 S4 Bif($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
9 L4 J; I4 y8 Q4 M ~5 x
/ Z# _1 ~6 K# n/ w* ^* f" R if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');- R. E% h- X/ H! @' f- c1 {% p
, ~5 w# ^& e( r, O8 i9 i4 q
; H7 T3 I' c* V" R8 m if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);$ ~% ^2 X& f- W( @& u: v
: |" j3 D, M! }" X9 S8 |. k6 K+ q5 ~' X+ r5 r* }
$this->set_savepath($this->savepath);6 s3 H3 W: \+ d6 ]: g5 o
) l: K1 C7 n5 X( L* @; Z8 C _( t( h) i $this->set_savename($this->savename);
$ w; U; R5 {1 X- m1 c( j0 ?) C5 y% k0 ?: s2 ^5 a' H/ D* g , _ L& i3 Y, O6 V9 T/ l- G' M
3 F7 D& g) H; \: a1 l2 T8 K7 sif(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
7 Y) T7 N9 r3 \* iif(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
" [. x; H7 `( c9 b- O6 f5 y5 J5 s7 ]; g/ i% N if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);9 K. S; L+ F) q
% {. k7 W- r* r$ b1 y
; e- Z% [% S/ z ]3 m8 A* n0 v $this->image = $this->is_image();
4 m- \( l9 |2 K- ~. E+ r6 \6 b7 ]0 k4 x" d1 W9 f# k if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);% g) x' C1 y! ~6 Z( A* \" Y. [
7 `" F- `5 i' R1 E% B) s. u; Y- {* E' D8 ] return true;
}}, m; D. k) {! |. A& @
9 C8 n( T$ V; z* H( V# v; M- X$ @8 [7 n- i0 ~3 Z 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:, _ Q+ ?* D* G
4 u' J9 y X8 y <?php
; E% t: [6 z( m, x0 G! T# bfunction is_allow() {
; V9 P- m& l6 M3 W, ?if(!$this->fileformat) return false;8 O3 S: C F2 Y: a& Y8 v2 N
# Z' f9 I5 @9 Qif(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
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;
4 c |6 b1 N, }; l# Freturn true;
2 e; m) z* G6 O1 S2 C }
; f' L' A5 z) B. H0 ^5 ~3 v$ n! y0 k3 ?1 e* k; [; y 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。( c4 ^. s' @$ n- B1 z( y! r( G
" n4 \) i3 k4 o0 p 接着会进行真正的保存。通过$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文件。6 |/ w( ?$ }- I1 @" {. w
2 A2 l J0 s+ V$ e$ r+ e0 e# v; K! f$ m1 W, ^ p 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。
~ K7 z& r0 }. G, c" d% _4 z7 T& D# ?; ]2 J2 J* w; d
9 a o0 K- {6 a" J
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid% t' k4 n0 Y2 e# C u5 Q
% S( b! u! ~ M* Z不过实际利用上会有一定的限制。0 J7 n1 I0 u+ M) k2 a) F7 e
第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。. h5 z" T# A/ {; o6 p- Q
# C+ w. |/ A/ b; j8 M4 `- _
z3 s1 h5 M7 T- ~- `0 f# @
# J' A( K. f" Z1 | 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:9 a0 H' }1 ^8 Z5 V8 y: l
省略...$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' |: P# a( k4 e- U7 u6 {) ?9 j& B
9 g5 B8 J! D L) z2 \! p9 U 因此要利用成功就需要条件竞争了。$ W% ~, Q5 E( ] L' y) A+ z
1 m8 y. T! x' n7 e( @5 L5 ~) N3 ?( }1 q5 a" D% G5 `# l3 g- | q
8 }3 \" Y- K) h, C9 O" T1 e
在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
/ X. E3 p# \, S0 Afunction is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
- g! S0 P- F" j! o
: J; n" W9 g" l8 ^" F, I7 Z" v, l
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
" N+ y- y" U1 b2 m5 P6 w- u0 a- P @' v( g6 Z2 P t/ b 在is_allow()中增加对$this->savename的二次检查。9 @9 f4 J6 S' j
% e1 {1 x2 Z& n+ J嘛,祝各位大师傅中秋快乐!
1 ^) g& C% f6 Y
% @; @# z& Z, R0 p" j| 欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |