2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。
9 o3 u" j2 ?+ Q' t
. W9 N3 ?" o. z1 t3 } F2 U9 M0 B' _ 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
( M% V1 @1 @; s3 G/ R" X
6 }5 L2 B g: e& A6 x) `6 L
对应着avatar.inc.php代码如下:. ?3 _' ]8 c* T: {, |
! i( N K) e5 E% _7 ~7 N u2 z2 W! p* i7 I4 ]6 P. u U; H <?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) {
case 'upload':
if(!$_FILES['file']['size']) {
4 `* I2 F8 W0 y/ | if($DT_PC) dheader('?action=html&reload='.$DT_TIME);. `0 B. L+ |3 Q! T! e
' l: G6 C* n- B0 ?1 C7 F& N5 S' }/ G) z, t* E5 \( E% s% T exit('{"error":1,"message":"Error FILE"}');
4 P, D7 d, |) Z+ \2 w0 u- J+ U8 a( ?3 K z" i; H k% w }3 d7 v- L1 |( O( z1 M/ L
' c4 N4 E( V9 ?# E2 e require DT_ROOT.'/include/upload.class.php';2 Z4 a3 e [/ M* f1 ?. `( A
3 ]' l2 b& k2 e6 @$ G3 b f, C2 l: E6 k) K- Y6 b ' [ B. B3 R' F# [4 m. n; ]3 y; i
% \% _: r# u r$ z. _0 ~( f, c, o+ `* D- s $ext = file_ext($_FILES['file']['name']);
2 l& `% O5 I, N% C $name = 'avatar'.$_userid.'.'.$ext;
$file = DT_ROOT.'/file/temp/'.$name;) Z& s! u9 c7 C
/ j* X) x# D! X8 ^1 @/ e4 l7 O6 w4 z0 U! T# y
if(is_file($file)) file_del($file);
8 a' ?7 C4 p/ _4 y9 Z% w$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');4 U2 R/ P3 _4 J5 q T$ [1 _
- o; t( h2 C/ I/ h' s7 X
- T7 K( ?! Y7 T x $upload->adduserid = false;6 c4 @% S7 ]. ?& {/ L0 C! N1 N
# U! x; F$ h. j) o 8 i' N! q5 c* n
1 G' ~0 X/ f S, r3 i3 eif($upload->save()) {3 r! k$ o8 x6 g) j [5 N
1 ~ ]% O' F* C8 } ...
} else {
8 Z+ T$ _7 a: h9 @; L, Z' S) x2 o ...
' \) B) U) u) L/ Z1 a2 a4 T" D: w( N. ? }" e0 k! g( t9 D4 Z. v, Q
break;
. P4 m1 N8 u3 ~( @# `! ~9 D这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。; c" p5 U' v$ j& |3 e* B) a
, B" K3 _; k5 N' w' J1 i1 a* Q. {8 h/ w8 r/ W1 ? upload对象构造函数如下,include/upload.class.php:25:
5 C4 g8 t+ V# k u+ I<?phpclass upload {5 Q9 g, l+ K4 Z4 D1 y! A/ ? ?5 V! W0 O
0 s* i3 X; N! z1 g1 m) ~9 t function __construct($_file, $savepath, $savename = '', $fileformat = '') {8 N, K9 }8 }7 X
a2 {( W% G9 B0 P' C' |global $DT, $_userid;5 v: D! l5 d, b2 V3 c6 J
foreach($_file as $file) {
$this->file = $file['tmp_name'];0 k5 h4 t' ?3 ]3 \' C. R& h; H M$ j
$this->file_name = $file['name'];
6 K/ Q% q* F" n: d' ?8 Q1 q3 m* t7 K: W( X7 M $this->file_size = $file['size'];
8 f: j E u& u/ t$this->file_type = $file['type'];' @0 `5 i0 U+ ]' @9 D. C+ b+ J, g
" |( z1 q5 q& K) b$this->file_error = $file['error'];9 F5 M4 m9 I7 E
# R1 W2 S1 R7 K- Y! P 7 c! L3 t9 b( b
5 o3 U& \$ H- r, ^; y0 Y }
& i8 m5 @7 U' _- D $this->userid = $_userid;
2 {* O. c6 C7 v; Y$this->ext = file_ext($this->file_name);5 o& s+ ~9 h- p& _3 f# @
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
$this->savepath = $savepath;& g( @2 [1 E6 W4 A
$this->savename = $savename;. i& o$ z8 d9 b" A" ]
3 H* ]4 h7 l# y4 x8 ?4 Z* \6 q}}4 s$ U2 v1 A6 z0 {# u
/ R# M. r9 k0 {1 h/ E$ w+ S: M; ^4 E' N- n4 j( @) h 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。9 K! W0 Q& B) g. b
E2 ~6 p2 N' R9 ^ 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ( e+ ~9 W' e5 @2 J
$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.php0 I3 Q5 {$ l4 f: g( p# M
4 ~+ _0 b5 t( Y8 j v& o- \而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:; F8 W3 ]/ A! A! S. ?! p
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:* O+ j; a! a1 k, z
" ~5 ]- M6 t: t8 N- O! z8 y; C8 I# A! M$ f+ Z3 w2 I& N <?phpclass upload {- V1 A" n6 T7 G7 g9 R' J$ b
2 W! [7 S; Y4 s0 g function save() {
) ^# D3 X5 I/ \) F) Y include load('include.lang');
& e& I4 u! _' K4 X' M; O9 ? if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');& D& Q9 X5 \3 [
" d3 ]" |; E7 O) [7 |
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');; Q0 T. F, n& @9 i8 I" f
0 |( W* e* I$ p* q8 { ) u4 z& _, C: i3 _7 [
: H8 R+ P* q" X7 {4 \) L4 L H9 ^3 v8 P" a4 H" K if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
8 u( _7 ]/ {+ Z$ o/ V0 r- Q5 w# R G4 `! L! n, k: }
4 d7 x" x) r# Y1 T& y $this->set_savepath($this->savepath);
$this->set_savename($this->savename);
6 G6 Q, {. E- \% j' G" G8 @* k5 r. ] Y J : u' @* Z( q; G, J4 J! t! f# j& R
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
$ }" r0 p5 l- g7 ], c if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
: r M, j( H# e9 l( k if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
% s; w1 c3 N5 V2 m: t7 q6 |4 Z- d1 B- A l1 i
$this->image = $this->is_image();9 C2 y% X- R1 t: D
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);. [+ j& \( E: q- j( f4 M
return true;/ h7 Q) M& K. d- q2 Y
}}
) [- i& r- y0 S% t" c% H' `2 c/ t先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:3 s. i4 C( G: f. `
9 ]2 Z' H; {1 ?' ^# E<?php
1 o- \/ j M$ t) d function is_allow() {
7 N0 V; Y3 `0 r4 l/ d* y$ c. J$ n! @ if(!$this->fileformat) return false;/ y+ _% U0 u4 ]8 }1 R7 x. w
# v2 |2 }' R/ f: }% c f C/ Q' Xif(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;& S+ U. k6 l7 a. U
) |& ]1 S( D8 W+ w( a9 a9 L9 n& q8 E5 v% U- u+ c* T4 P5 j 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;, ~) _. |8 j6 F5 G9 ~! S* a
( q; I' b5 S* X1 ?. M0 _0 j; S% ~7 ^+ O: A1 ]. N return true;7 ]+ X) Y; [+ j3 p7 Z
}
. x9 f% O- q( X: j' P1 P1 D可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
- x4 a4 ^2 T3 p! n/ l3 u1 x g7 D接着会进行真正的保存。通过$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文件。
0 \/ F+ q: Z( A% h1 r. m$ k9 P0 M! e% } 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。5 L5 W0 y2 ~$ _4 Q9 D! a% t7 i* w2 s% ?
+ k( f. g* U! W% \/ e% g
1 z' r }" M/ ?' J 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
不过实际利用上会有一定的限制。
# i: ^1 H; @. N 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。+ Y* n+ H C" x; [
`: ]& O# o7 M0 H2 o+ E: X6 G
' z8 h' r- v6 u
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
省略...$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]);省略...
因此要利用成功就需要条件竞争了。' B5 _4 v& W: n% J {
7 `% ?" Y- t0 G5 n% x$ c; F
" D( `% J8 K( g4 W9 h
; t+ [9 D) s ^" k# C S* s 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:3 I' C5 I/ k8 _4 [7 L1 i9 [
; y+ Q/ [, E% T# x/ w3 t8 D W2 A" B6 d4 N L) e/ [- e, g0 Z function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
( E, K0 Z b* j) X' n" q. Y% ~$ l; E7 S' Y' X& q1 y
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。! i# l' K- Z9 p7 c- f+ d; L1 [
8 f0 t" M( P" ?1 T Z% s 在is_allow()中增加对$this->savename的二次检查。1 S" s! H% t5 _$ d7 g: {
嘛,祝各位大师傅中秋快乐!# x: \6 G0 E! j n& W! f
6 H% y; P3 ]( ]7 H# L x C2 x. e h$ ^, ^6 o& f0 D
% a" P2 z+ e. g3 ~| 欢迎光临 中国网络渗透测试联盟 (https://cobjon.com/) | Powered by Discuz! X3.2 |