. Q' z1 D* ~" m4 b$ a3 P4 b7 w 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。% }$ D! `: C I9 n- M9 ]% G
% Z. A" I/ J8 A Y6 `# Z9 a( k0 N: E- q) G
2 o y: ?' {/ A+ v 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:" s/ t% ^, i& n
% o3 \$ c: Q& N t' B + Z8 [( Y6 M2 n( I
5 t0 r: M$ {' x9 P对应着avatar.inc.php代码如下:: Y3 o' u# _, N: ~9 G
" Q* B9 i3 n/ O& p- x <?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) {
4 U3 [5 k2 F# ~9 S; |+ k case 'upload':
F6 |3 N1 U4 V) E if(!$_FILES['file']['size']) {4 C' ^0 a j* C" o; j; g0 J
0 y" _# g* H0 Z5 L5 L. @! P if($DT_PC) dheader('?action=html&reload='.$DT_TIME);) |; s6 E& d" k, b6 A8 f- W
7 N& s8 i$ `9 F' v$ b) [6 P& D5 ?8 ~2 K; j exit('{"error":1,"message":"Error FILE"}');
, J' }$ Z w% [+ k7 Q, d% |) h6 u& l6 u! I0 w$ R }" L- ?+ A% Z5 N
) g: |- k% [8 j9 h5 S( f: G, t% ^0 W, ^2 A! n _0 p( a require DT_ROOT.'/include/upload.class.php';
2 D( g& M, G; o9 s" n! X; J! s* J2 ?. u. _ 5 [# b/ r1 m$ |! e d2 H
, A/ |$ g. X* Y& p: G( f3 ~! j $ext = file_ext($_FILES['file']['name']);- M$ a9 \3 k, G* } C# C
$name = 'avatar'.$_userid.'.'.$ext;
& \' X: K$ W" a/ p3 S0 V $file = DT_ROOT.'/file/temp/'.$name;+ f) b# {+ j4 t2 Q7 P) F9 w, u# L
if(is_file($file)) file_del($file);
' O ?8 g3 b2 X( } $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');1 g/ S: j% r$ M9 |8 W
: e. h- @; \% a A( w" n/ X 9 V# l7 o% ~$ @7 x$ a# b
) b7 P+ |; [: Y S% y* ` $upload->adduserid = false;
- h2 O( J3 |" R- D+ g& l" y! k' Z
2 ~) _: b7 q, J2 e" ` l! a/ f# A' u2 v/ o5 Z' h3 x if($upload->save()) {
3 h8 z E' e2 {$ a2 A) d...2 s% K7 V# j+ H! Z# @1 }' |% K
Z! h; ^) R6 ^9 F5 C } else {
* a( C$ S+ [' S* b, L: V& ? ...* s1 [$ R" o) e- b! |/ N/ Y% J
6 a6 z. m. b6 `) R5 P( N& q0 V}2 p3 D; x2 `3 v4 Z, |$ [/ I
$ i( [2 Z& ?% K9 H x$ R break;
6 B6 M. Y! M" u+ P, J 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
: U a( n* J& {( w2 x, m# ] upload对象构造函数如下,include/upload.class.php:25:
<?phpclass upload {
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
: e2 t3 H& e7 k2 w0 [; { global $DT, $_userid;
: i: B% s* {5 L% e foreach($_file as $file) {
% ]: J' i7 n* B4 c3 j/ ?+ n. U( p3 h8 Y1 }3 \- P $this->file = $file['tmp_name'];
7 m* I7 t3 ~; H Z+ W9 e( e9 q $this->file_name = $file['name'];
4 H6 b2 t" }) s6 H. K' {# v, P+ m( q% V $this->file_size = $file['size'];- A [! m. l3 r* g% ?9 d
, R* k! W6 J' ?& b2 a9 a $this->file_type = $file['type'];
/ {8 M" y/ P; Q. z$this->file_error = $file['error'];
# Z, o4 ]2 b) \7 a. i
}
+ d- z0 m+ v6 ` $this->userid = $_userid;- f* G9 G& [) W' W } v
6 \& Z/ N$ B5 n6 p3 B. _) u+ k' _6 x/ A9 @ $this->ext = file_ext($this->file_name);1 l* |0 y( m( i* v x
/ O5 v# [4 O3 ?, I# e8 o: L $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];4 a1 G' J1 R8 i& K
1 } T% d- w; y% i, B6 \5 t2 C0 \9 y& ]' Q1 N3 l/ ` $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;. z3 D& k: H6 V6 z' h& ~
% ~+ I8 R6 T$ k" o8 \/ ?( h# D2 e+ G. \8 x+ X& b. I$ T $this->savepath = $savepath;5 y2 j: V+ O. k' A+ ` p
! ?2 m) T" |6 z; V1 p+ S% R $this->savename = $savename;
2 m5 \& ~6 S2 W$ O% I) h6 [# ]; d+ B5 r% }1 x& h) d! E }}
" E( a6 P& \4 E7 r1 x* N 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。; n6 ]7 q& v( |. G' |! _
https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 ' o) G8 d& u" Q+ b- l& B
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:5 X2 Y4 S6 S4 V+ g0 q9 q $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.php2 |4 O8 p# y& N" Q/ t. c8 b
+ B( ^0 K# l1 v! n3 A 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
: g# u2 ~) p" ]- ?1 w " o9 c3 y* b' n" r" V' y
3 S% `- P2 k7 C: q- }4 ~ 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
* I3 @9 O; _, K" C+ n <?phpclass upload {
! e+ q# @, m. C) _ function save() {" l' w2 {. i6 C; ^" n S- p# c
3 F2 U, J: Z; A6 \4 I& g% Q4 H3 O9 j3 ^) d1 w1 O+ g include load('include.lang');
3 ^/ \6 s$ ]1 a8 d# V$ [$ aif($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
! U. u8 t' z, G: W if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
5 X8 r+ o7 b a4 d8 r' s% }# F4 a- Q
' K) H. Y- E9 G5 w3 M& k8 ~/ Z) K if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
) i/ [( V4 F, ^* {- g1 U! l1 h$ y9 [2 M9 [: Z+ \; S ^& _/ S+ }8 ~4 g F/ Q
6 F' ^/ U! l( V5 x3 |: Y2 m$this->set_savepath($this->savepath);
$this->set_savename($this->savename);
5 S6 w8 l# i2 P 5 q$ B5 D& c) u6 {- l$ S9 I
+ }7 v+ z, ~0 y" f if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);# d% Y& W X. z A
% v" l. J4 n- M9 d: l# w) s if(!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']);, [' w; O( T' \, T3 \% H* `
1 L7 m3 \) `' m. \8 J L* {
0 f9 H$ q# z% S; J6 F; ~. b $this->image = $this->is_image();
if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
! F% i, h t6 U( k2 Y8 Z return true;3 R7 l( i- o* O$ d6 f8 q
8 c1 M7 h' P0 S; c4 J8 d; \9 W }}
$ o+ o" l6 a$ C! o$ d先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:" U) r5 a' d3 ]9 |
- Z$ a, L3 e/ H( n0 T& T* w# r0 d' J <?php
function is_allow() { N% P" \/ ?, p
! I4 u5 k7 i; Z) f( k8 `/ l, O if(!$this->fileformat) return false;; t/ P7 R+ z' \" O5 D
, l& c3 t! g0 A" W if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
3 _& R* [6 o4 U1 O* j2 T8 r- l1 e: x$ R6 m 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;
% s/ t$ x( k7 n" D! e return true;) o! i R) d* p2 I
( \, [/ c$ W' ?. c7 u9 y1 G- y5 O) N, V4 a% E' ?) d' b& E( N }
+ c* X+ j1 j3 ~ 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
* O; T) g! Y6 ^ 接着会进行真正的保存。通过$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文件。* W2 ~6 ]: o3 w. _ S; |
3 D* e: i$ y$ k* h! x; T+ ]+ m8 V4 T# N0 V7 u8 s5 X8 ?. T; [ 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。, M7 J2 B9 ^' U. }& Y5 P4 b/ O4 Y9 ^$ t* \
& q2 `8 X( G6 J! E% @7 c7 ] 9 j/ j3 I( i- R0 j. J0 m& j% R
* @: v5 W2 a% _' Vhttp://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
/ A% V! [* ?8 k, T+ E 然后访问; y# i6 v4 Z! I6 v- L! L 不过实际利用上会有一定的限制。: |6 I2 R2 V# I( y- Q( H5 b) {( u
4 h* n! \+ h) @# C, ?0 l4 O5 S9 x 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。6 x* L+ M) L. K
) l) K& B. M7 r
& Q) k1 h% R1 ?; z5 `5 R; y; Z2 G 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:) Q( P$ H: F: \" L& C
7 ?6 H9 r% A2 z( \; l8 _" T0 N% C 省略...$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]);省略..., X% u4 k+ u0 M: f
2 q# j. K0 k( @& p. j 因此要利用成功就需要条件竞争了。
$ W0 f8 l# ~* H: H$ _2 {3 z7 K6 ?) J- }' H& ]( u ' m Q% k2 j) `3 O2 L, A6 Y
# u6 y# o# [! Q! F; r 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:/ L9 s9 H) d) `- C F$ P1 ?
) K3 c( k" e W: N( _ function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}8 f9 q; i8 s4 m2 X* w6 a2 ?# V
- q! w7 V" N( b; U% D5 O$ k9 @: h: x& n / Y. x: s5 x) l" Y/ _+ J
' H* w8 k& V3 ?2 t/ j5 ~* f在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
' l/ ]+ \4 J( H8 d+ c 在is_allow()中增加对$this->savename的二次检查。; k; f; U" L- U' d- L
嘛,祝各位大师傅中秋快乐!2 S4 Q* d* x' }4 h
欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |