, p8 O! R) |7 ~$ t 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。2 l- {6 L! p3 L( K1 P
+ _# v- H4 ?; Q( J 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
) N; f6 l& _, w: Y# h' F. J! Q3 s6 K" N
# D3 O3 p3 S' l% Q6 [/ q* l
对应着avatar.inc.php代码如下:- }5 K" @/ y, ?8 F
3 `. E1 e3 j K7 C% g2 m" I$ ] <?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) {/ H- D. ?: y3 K
6 V' v; ?6 o' T" p/ }) p" w8 g/ M3 w: f2 W0 y. T; k case 'upload':/ o% F4 l! O4 W
if(!$_FILES['file']['size']) {4 X( [4 b& H4 K, ~) b; C5 }
if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
exit('{"error":1,"message":"Error FILE"}');) j8 k7 _# Z6 B9 v, l, U; ^
}
! t5 q2 V \2 ]5 N) i require DT_ROOT.'/include/upload.class.php';- U% ]- m3 w1 g3 S. _0 I: S+ a
# K; y$ S$ Z. d! ^) { q
$ext = file_ext($_FILES['file']['name']);
* j3 K, D. ]+ C, w $name = 'avatar'.$_userid.'.'.$ext;( }; a) v, o* ]) Y% f
3 U2 a; U% |% R7 }1 \3 S $file = DT_ROOT.'/file/temp/'.$name;$ @+ t. R# _: b- F) N, C" y# u
: J/ K. [- t% \. I- ^1 n9 v 9 f5 W( _; W2 ~( J r1 `
( \( s; e/ _% g& [" c* t/ k$ N' R" m if(is_file($file)) file_del($file);
& g5 t: B% z* v- @7 F' _$ A1 m/ O* P# I3 B! V3 ^ $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
# i' Q" Y0 @# k( l6 \5 J6 u
$upload->adduserid = false;
z% U2 W; }' R
+ w% V) Y j9 i: J/ c: m if($upload->save()) {
...
} else {2 g3 M3 R8 h3 g/ O) E, i
: A' O6 k% H$ v# W ...; ^, k% ?3 R$ K; G$ x# H9 k
+ R# r8 c1 o' n2 F4 ^ }
" }: G, p% F# W% b) ]- J; `6 U9 ?& _, o break;
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
4 P6 @& y0 S1 q- W( p& j upload对象构造函数如下,include/upload.class.php:25:
7 O8 P3 Y$ g" p j, I" O8 ^ <?phpclass upload {
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
- o7 J' M% t: u I3 w* d1 b global $DT, $_userid;
4 T( k! e1 B7 S" m& E: X: g' P* y foreach($_file as $file) {8 O8 B, `) A. k. m7 I
" Z! J3 C, ~1 d, k' M6 ] $this->file = $file['tmp_name'];
& T6 }) s. {$ e2 u+ U& T $this->file_name = $file['name'];5 }5 Z/ ]0 M" R; P# W1 a
' {1 R9 m% r% m# Z1 ?. V$this->file_size = $file['size'];( r% p7 E( e& `# w
( c' L/ J5 w2 W3 d/ z" M $this->file_type = $file['type'];
: w: E, ]/ Z; Y( I" V. ? $this->file_error = $file['error'];
# R% M% ?& ?4 ?5 F
! G7 d1 U$ X0 `: c. { }7 k3 _( a8 F# t4 Z1 Z4 Y1 G
# _' O' x9 f/ l8 j" |9 ~- D9 E1 l $this->userid = $_userid;
$ q5 I6 K' O" n( O4 Z $this->ext = file_ext($this->file_name);9 `4 H- {5 M' z- X
$ Q6 s( G- E' ~, w: \ $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];
6 U# F( C* j2 d" B3 k+ D $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;$ E" l, W' n6 a' V" g$ j- v6 x
4 f, o8 N- S$ Z $this->savepath = $savepath;
$this->savename = $savename;
}}
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
% K' j( p. ]" {1 A& i. h2 \& I( n( I 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
2 g# G8 }& u" Y3 @0 D; T9 P4 \# m7 Y6 m f4 [; a" _ $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" y# w( A3 J) C8 _7 W3 E! N
5 o% w/ D, o8 H0 ? 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:8 l6 X/ k% z5 |7 _/ K
6 o v0 {0 u: g; ?. R6 V
# N7 X y4 J$ C' r& K& e
* O3 L s8 `0 z7 ?2 R 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
: u# \1 u6 m. H- u' K1 z f7 W" X6 K3 D$ Q <?phpclass upload {
/ A3 A- U6 e! Z, }0 N3 H function save() {# M' L- T( N2 o. \; m7 F
( u: i% E0 J# e: L, W8 v% P include load('include.lang');) [+ f. u N/ ^5 d
0 H2 s+ f* { z: @* {/ }" Q7 h0 e) `1 I2 A4 k( a if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
& ^! t0 J: d3 _+ C/ l / n6 h }# ?+ d1 g X
7 V) y3 d) |& [' q' T if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');. {! [" n+ C6 }6 p$ z a* N
$ |6 _# E5 G# V 1 s, R8 Q% d! b" T& `9 H
1 p1 P% [# @/ d5 A3 u' k if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
7 f. M" m" }& m6 Y( c* M, f % M0 v" {1 r, T. a4 N5 y- z
$this->set_savepath($this->savepath);
" ^% k8 V; T; U $this->set_savename($this->savename);
* h% s, m0 L. A% ~& w 9 F* b; p, W5 c% {/ Z) M
* m G9 { u* |( b; y if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);" ]( p6 X5 m1 V3 w- h
. _# A5 K4 C: }' U' s) v if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
4 a v/ ?! z* |& P' i) w' R if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);! n9 X& `- R3 f: B7 `
( ^8 _5 k0 i! f9 i0 ?4 x- p6 W
) X6 F% Y" t. }* v7 a( f# @ $this->image = $this->is_image(); {& ~: ]: q$ k
7 p- s o4 e, m" J* @% U7 k' S) T! i1 l/ F if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);0 _% B( _% r) w8 e5 z& {8 g
6 }% N& I) P, c2 p. L- M8 j" f return true;. I7 g6 t4 ^6 j; @+ l p
1 {: v2 O$ [- |# |* }% [3 a. E$ }! e7 d }}9 [* r; ^* d' l1 t9 o, e( o
+ l" X$ S% h! q+ X) q6 ?: c: n3 A. F3 y' Z1 ]) I, j6 m 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
6 y$ f- I% Q: D4 M; S <?php _: M% W5 E, L
) Y" s* {# y$ l# H6 D function is_allow() {
( |+ }% v- {2 j ^' ]' Z5 C- m' _ if(!$this->fileformat) return false;8 Y2 {9 p% [# c) x
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
& b. P& \# O7 z6 O0 W" O, Z. O# k* A 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;* a e$ f* C% N1 I4 D8 f
8 w% R* G: @3 B, p7 m return true;
. U; M2 |4 g/ D, X S2 E4 l/ o! C) w$ ] Z* T! \ }' K+ N# q; z- D7 E: S
. ^, u( f1 V0 I; f3 }: u; Q 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。& B( A8 S' ^! T4 U D) U: v3 E1 Q
# Z) S* p8 F% i& m2 E5 w: o- ] 接着会进行真正的保存。通过$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文件。
) P) D& h a4 l& w6 i4 L# J; f3 ?- W& L( m3 u2 y( }" g6 A 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。# |% S9 ]* P7 N( z! h1 u
- S/ w2 _8 @6 q: } h
http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
- m( k( X, _0 C1 ]$ s& q 然后访问- ^% W8 \$ C' c1 O1 e Y( k3 U 不过实际利用上会有一定的限制。
% Q5 M( H' i5 ~7 F- Y8 d第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
6 D+ r, _4 x: m
# j& C) _7 v! z 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:" W9 o8 o- S, m6 L5 w! N
6 N$ E2 i/ [* 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]);省略...
: A# l3 | c- L& j: A' a) a" I. `0 I 因此要利用成功就需要条件竞争了。3 T/ w+ R0 S; s4 W- w4 o! w$ ?# g/ Y
$ K3 f6 o; U$ K" s
: H9 O' H; Z2 c3 k3 p
& l" o' k9 h C8 W- I& [/ j 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
9 K& d9 r6 @$ F% h: C# R6 u9 t; g1 h; ~6 D# k: g2 ^ function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}* Z: ?; @4 e1 I
- t! ^( {% `! P% F4 U& _7 g- S$ z
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。 H3 Z ]! W7 D7 x+ n
( P1 S7 x2 U: H# c* v* \ 在is_allow()中增加对$this->savename的二次检查。
4 g9 a) d4 r2 N& s! R! X1 V- J6 ^, P 嘛,祝各位大师傅中秋快乐!- a9 \, L: S' s/ c+ C$ Z6 @4 Q8 e
欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |