2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。) p4 l4 p( r' i. ?5 x) Y
! n: Z2 t& i+ i$ q5 A1 J. L9 X% _" G
% Z) G4 [* P7 u# a0 ]: `7 Q
# W9 \8 T9 _% R 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
9 u8 l, H0 O7 w5 D3 Z' w
9 Z" {4 u6 \) I3 l0 H8 w, j
对应着avatar.inc.php代码如下:: [5 Z; s! f& n; {: Y! z/ E0 S. U
& s# {& z/ ^9 |( w$ ]" B$ _<?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) {" y! y8 I& w- M4 i+ |; B; Q3 i( ^
a* N" R2 u! X% tcase 'upload':
- u* p2 I( Z+ b3 H% z( s9 @: j) r0 R! Z if(!$_FILES['file']['size']) {
- G! ^' S. T3 a( ~! \0 q& i8 w9 I& a$ [* L4 p2 |0 K if($DT_PC) dheader('?action=html&reload='.$DT_TIME);2 [$ v3 R9 Y C0 D& y) s
3 V, k1 L" A6 z% [" z! c# Y/ E9 t w, X# U8 L8 s' l S exit('{"error":1,"message":"Error FILE"}');' X; T z6 W2 N; e7 M
) t2 U$ K J9 a' q2 Y) J}4 T4 f( P: }! j) `2 O
1 K: q9 m8 O6 @6 w! ^/ Xrequire DT_ROOT.'/include/upload.class.php';$ d+ F/ o6 o- Q* o' N( W/ j: ^ f
a+ v8 W) G, h& L, z6 a7 c+ v# N
$ext = file_ext($_FILES['file']['name']);, [5 T& K, p" {* n, E0 C ]
( A4 l( O! P3 G& d3 N- P; q; B+ j7 m8 z3 V; Z+ f" w0 f $name = 'avatar'.$_userid.'.'.$ext;7 ^ [% m& K& s
0 j0 V0 ~7 @& M $file = DT_ROOT.'/file/temp/'.$name;
E4 V7 {* o. \8 ~, J( m, Y& q5 C' H5 e& c
if(is_file($file)) file_del($file);
+ Q! F6 H) d9 d8 q" }, g# E: H3 l8 u $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');0 N# V6 a+ B% P! s$ P
6 ?% j) |* l$ k( x) }" R! F% s; y: w a& { 5 f8 W$ S! t: _ i. E0 w9 p
+ o6 d0 T: l- P ~. s$ B* L5 N' g) J6 H7 Z6 ?5 W6 D3 F $upload->adduserid = false;" U- @' h9 N L W2 ?5 s0 m$ z
9 g5 q2 \; h c/ ^/ Q3 V& {
& t) k! l5 o _5 o3 d' p if($upload->save()) {' _& [/ y$ x6 N4 r0 ]
1 ]2 C; y+ p% R) n) S' _( e, `6 o ...
. O, b6 G7 @5 T S } else {
* H3 b1 Z4 d" }( D; c9 p% Y ...
}1 k5 G5 b7 S9 \
+ N. q: }7 G3 p" J7 j- u break;
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
& Q# d! i& N, `. J g" l! S3 v3 R. U4 F% f, l' m upload对象构造函数如下,include/upload.class.php:25:' l/ s& Y2 Z+ f! @ d4 W
3 K% L& a9 B! G* e<?phpclass upload {- \4 a1 V, D+ x+ F, z! X0 z- r
$ Z3 h: N x3 d1 p function __construct($_file, $savepath, $savename = '', $fileformat = '') {
! L$ ^" a2 o! s+ M+ M0 r& \global $DT, $_userid;; W: s6 m* _6 e4 e
8 ]. m, K* _0 o+ T7 ?6 _. q foreach($_file as $file) {
6 ]* m4 m! f- b1 R G* Q" t2 Z/ `- @ $this->file = $file['tmp_name'];
) O0 n+ l+ \& u& U$this->file_name = $file['name'];
5 u; e6 Y7 _7 X& Y- ], ] $this->file_size = $file['size'];8 S: Z+ R* Z6 w, m
& A6 j* _7 T: l! D$this->file_type = $file['type'];
, l2 b2 n! Z' V! _$this->file_error = $file['error'];
1 @" e! D8 D# |' _# j) ` 5 Z5 P3 i$ A' E V; g. Y" M
}" P4 R# W; p0 V: Q4 ~+ t* w+ u
$this->userid = $_userid;
) o# ^1 W% Q% N/ n9 ?* e" @ Q9 \: N" v$ X# P( b8 b* ^/ X $this->ext = file_ext($this->file_name);
$this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];. P6 L* J" L2 s* r
) Y. K' |3 T, c( w( D# d$ @) r $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
- L% x8 ^( F7 [! o& S' _: e2 V, J" Y3 T% a. N) f2 M4 a& Y $this->savepath = $savepath;
$ G5 K7 h! Z' J- p: {- t$this->savename = $savename;( X, O4 h# z3 _" @ d1 N$ o
& R1 c4 g" U( O0 m+ \: }& v" X8 s7 t! a6 |$ m5 R5 Q }}
( {. o9 Y' S a" j' w, B& r; _7 Y$ x/ Z. h' W; ~ r' s 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
/ ^; u1 t+ @+ z3 R" J% s" M 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 " w0 O2 m# ~, O2 B0 [+ @( C
5 ~; ~! N2 g$ s: ]$ ~3 }2 J( 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.php$ ?- r9 X! e6 m& k K
% k: g$ q2 L9 X1 V& Y) U+ _! p. `) u" u w# e6 N( ~" z. g, ]* ^ 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:1 Z0 c' z! f& g5 V a) _7 y3 N" n
% S: h" ~0 r% c' j$ o! H) ^, S
8 o: P; N4 } Y, {
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
$ G4 a9 B8 b- K- w- T' N6 j( v" N% }3 `<?phpclass upload {7 A: i4 X4 M$ h- p' B
2 Q9 j9 V- a' e/ v: T function save() {
' ?# n: Z# c% n$ K6 m: U" ?# G. I2 c* M) F/ q8 }3 p include load('include.lang');
if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
! N. U% h9 v& ^& g& c1 z$ Z) g3 q1 Z0 x& P* L* E& i) {% ]
! I% n9 c6 X. F& w: j9 `* I' S" J. Y3 d if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');- F: b. Z) M( L$ ~7 ^& w3 p
3 \% _6 Z, w8 w' c( F+ g4 G$ @: }* }2 X% z/ o3 ] * k% f% }3 k. k
if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);" ~5 Z5 x& N7 R0 F" k
- J% V3 L# W2 K$ H1 Y% x5 I! N+ s* }1 q. [ 4 W$ B/ A% {- \5 _ O8 L! i
$this->set_savepath($this->savepath);9 }3 o5 d3 R; T; F; G* y, x) W. b& k
$ S: z7 n7 G* g; p! M $this->set_savename($this->savename);
1 ~1 y; Q* T0 ]9 s( a& d3 i% k2 @, W8 @1 f+ l 0 n( Y8 \3 b% R: G& O8 }% ^8 [6 F
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);, G' C" J0 w1 r: s% X
9 E# g1 j, X* E2 g8 Lif(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);) x9 l5 `: x; L+ J
5 f7 E- O ]9 g$ p- W) a {3 C b: Z5 A$ w% X. w if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
) t7 V3 e' H( e2 x0 K
# L+ y: c- V& I7 x4 A $this->image = $this->is_image();% H0 _: T) b$ o
) f% ^- p' ^/ ^/ O, J2 J$ t V2 ]- t! m: h Z7 L- n1 O5 T if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);
& G+ i1 I6 |7 \9 j0 i1 h# {2 } return true;
' X- x( ^! f5 ?, A; a( r }}# O( f: ], J& B% P" R9 U) l+ r
8 T4 z$ x' X7 y0 C: L9 c0 @& W9 n; z/ v; ~4 ?. } 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
X) X7 z& Q3 O9 e. U$ s4 m: Y- c <?php8 I+ E7 k9 s$ t3 d- b, [ Q
* R+ c2 N8 ~: P4 X9 P# w4 f9 ?/ C/ c6 G# p2 Q( N4 w) M function is_allow() {
2 [' u3 z" q2 f$ S, k, `if(!$this->fileformat) return false;
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;( B8 L$ v- X" i' E
) l! d5 A" W1 A: p) z1 X+ y# iif(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;( L; m3 C8 Q9 V* u' A: f: n5 J
( f1 z5 O+ L" U+ z S7 N8 y5 q% t i" z7 W+ ? return true;
. ]: E- L. l: d( T# b}6 N i7 X* \% B
4 e/ Y! a- o( Z! Y& S1 J 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。% N5 Z3 R9 w/ O5 d
5 k1 n7 a) L% ]9 v4 S7 |: B4 m# T: T& W: x3 T* q/ l: D6 X, I 接着会进行真正的保存。通过$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文件。
综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。1 t1 L) R& o; [( ~
- f. c+ ~8 F2 a1 h _! c
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid8 m/ F. u0 Q9 t' B# C1 o$ ]
t2 J1 x- i i, e4 v+ N5 y* r6 I* I6 Q+ u5 V# y; J M5 F8 v 不过实际利用上会有一定的限制。
& m* Q9 i/ O& b" Z% z+ |第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
3 l8 @, v, l- V
3 g+ J t' v$ n3 p) I
, u0 `" Z. m3 [6 u 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:
# w: ~+ v. N/ h) s# ~1 z& H* R* U0 } 省略...$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]);省略...
; G8 F+ @$ R- @% w- H- @/ ?因此要利用成功就需要条件竞争了。& x N% H9 p% }/ D
3 Q6 r& o! W3 Z' G: x) O! D7 }) t7 R* B9 a M
3 E2 L, S$ e6 T. `
* \5 f; v* q- n 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:4 {1 u* i4 |* I+ ]) ~- Q2 C. _
function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
! |" D# H$ Q a/ C
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。& x% c+ ?& @! `* x; N6 x2 X
. F/ x9 w9 Y+ D; j/ r) a( l. f! n( Z3 e8 T 在is_allow()中增加对$this->savename的二次检查。
3 V! S; e, s$ _- e$ ~7 ?0 X9 ] p& A* o 嘛,祝各位大师傅中秋快乐!
, f( Y2 K7 _8 y8 w f( d 2 J9 P- [" u8 q$ K
| 欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |