: l& `( d! ?4 C P8 H& ^6 v' v 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。7 G, N- r% d8 e M7 n
( o/ h" o: X! ^
& {2 `+ h- g5 y. I 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:5 e3 c6 K% k* G$ b* f
! q8 T8 i5 k5 x4 k. e
# ^% N# ~0 r2 n; X; B9 S 对应着avatar.inc.php代码如下:- \+ q- I5 B6 }; ^; f* D. A! g# p
2 Z0 {& F! U2 U4 M; 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) {# F) L" I( P N6 v
, q6 Q% W: U( E' z case 'upload':1 A' u: V% ^9 e$ Q
6 T; l; ?" T# b if(!$_FILES['file']['size']) {& x c6 V! U6 n! w
% W1 g- K0 o9 J/ Z0 d# V) I& H6 v. O+ ~/ ^& \ if($DT_PC) dheader('?action=html&reload='.$DT_TIME);$ s; ]2 E/ T7 S: N* L9 W
2 j% U6 C4 o7 z' h: @+ I# r exit('{"error":1,"message":"Error FILE"}');6 O" n: N" K" I2 v8 o# p6 k
* x( `* p! |/ L$ B) b) |}
; x t! ^1 r: I9 @* {0 I+ M require DT_ROOT.'/include/upload.class.php';/ R3 R6 S7 u# p) s. R
k5 `) k3 E+ S8 z7 D `6 o6 C. Q4 p+ N$ s V4 v% x, L( S
9 c; c# V# D/ z! F0 r" b $ext = file_ext($_FILES['file']['name']);
$name = 'avatar'.$_userid.'.'.$ext;" w$ a7 L! Y! L& b: {
( ~ F" {0 b3 A; p" f \& h# w $file = DT_ROOT.'/file/temp/'.$name;
9 @ g. H! K' x* f: i
2 v5 F& U* \7 y C if(is_file($file)) file_del($file);
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
+ [( k; [, Z! L% G2 [4 Y
$upload->adduserid = false;
: m, H, ]5 b9 v7 p) B
' J3 c1 k! K, q; Z if($upload->save()) {* f+ {3 v, {% q5 X1 C. Y
...* ?; P. x, L' ]0 n
+ h3 H$ r: | Z6 y3 f8 o } else {
% D) |8 ?5 x/ N9 Q ...* K2 D) M/ x0 Q0 q0 j
& ~& \3 l% q m" w }
3 \. s& l7 E* {- v" u; O1 I break;
; D* N! V* `2 b. m2 A6 j1 H7 o 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。- o8 i8 b& V' c& s0 c- I% ?/ N
upload对象构造函数如下,include/upload.class.php:25:
+ n) F) u4 h" E <?phpclass upload {
function __construct($_file, $savepath, $savename = '', $fileformat = '') {
7 f6 S, |" ]4 { global $DT, $_userid;5 c6 k4 v/ V( `9 w, a
0 T! u; w" F* W: M, x! c! z foreach($_file as $file) {4 i8 f- U! |( V; |" N
" c$ [3 J/ a( M+ G- y7 r $this->file = $file['tmp_name'];
9 r' O# W: L. R( P. P7 v $this->file_name = $file['name'];. K7 t9 `% U; d* Y# n% B
: S" [" [, M' {, f- A( A1 N9 E! ^4 y5 u& S4 k $this->file_size = $file['size'];
1 F {) {% l$ S1 M) H $this->file_type = $file['type'];3 k: A6 z/ m' B& {' \/ A
|. N' {: G r9 c. W$this->file_error = $file['error'];
4 A# y! |) e* v: Y& J E* w ' e# m- m" \' k9 E5 i! _
! c) t, `4 ?9 x9 J z0 ` }. {! B5 E/ R1 C5 S
1 y2 Z; w% a# M& i) O8 u8 U5 _ $this->userid = $_userid;2 t9 o' u* P* o' z* H( x/ V
$this->ext = file_ext($this->file_name);
! b, [: [' k3 o o2 T; k9 v $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];; g+ t; \# @" f% d, Q6 g
5 x- R" d- s9 r& U# y- y' B- } $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; d1 l& u) x& u% h! O2 `
, ?/ p$ Z+ A6 ]6 n( N! F/ X B$this->savepath = $savepath;
$this->savename = $savename;
}}0 k3 q- B' V6 q1 f) t
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
& z5 _1 T! W( `) m 因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
7 d, V) ~9 S2 E7 h5 Z+ q* F: b $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.php3 X7 ^2 _8 [) H: K
- b! A" U3 B( L/ ^; j, v9 l 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
" \: Q, V* S2 r z: P
; \. B3 b0 B3 B7 T7 ] 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:/ e6 I, C) E; I Z" {& W
! s' k, s1 z4 {9 |6 w<?phpclass upload {
! E8 z( A' ^! W+ f% {3 y! a function save() {& I( x) H( e7 |3 b) R0 c7 A9 p
include load('include.lang');
) l4 Q: K; Z3 J% i# C) \ if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');. }4 W# I$ U- ~) G; I: Y1 V9 w
, J9 \9 w6 c; H8 x+ c3 E/ D$ f
/ U4 i/ z6 Z7 U1 s5 `0 z: G- h: w8 P+ o* B+ ^2 @ if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
3 e, ]3 P! G9 w9 l8 @6 B3 S1 n$ l9 f2 |: p
5 g5 f& i! f" t* U3 w! r: K O: u+ j5 \( G3 @ if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
W) b# ]: j) R/ C6 G. L# L 9 o" L8 N: U4 \6 M; }/ i, H: m' S4 {
$this->set_savepath($this->savepath);
7 u4 q) Z- R0 A/ v) F( Y1 ~0 {3 ^! } $this->set_savename($this->savename);
, r3 r4 {+ m4 @- g% f- f/ o* g1 ~) F* |- t# @7 J l $ p! A6 D" Z0 m6 l
. e% I. G8 @- M if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
6 Y A* }0 P6 G7 J4 A Sl4 a0 M7 ~4 \# t 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']);
$ t5 @3 T; q. g' F: m* x. l
% U* o! z8 }! E1 `! P: @" H $this->image = $this->is_image();1 c# @, u9 S i/ c: a
% i/ d7 B e* q4 Z. w0 ~ if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);; }9 } c; ?, ^' e
return true;0 h9 O" z( h$ V6 `" Z+ B- H
}}
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:5 s v e! A, O* o; S- e! i! H
2 n% q2 M8 i: E9 i/ i <?php
5 E4 N: u7 h5 Q5 `# T# J function is_allow() {
{% Q7 n3 R& b! r _0 j9 Q; H, w6 o if(!$this->fileformat) return false;: Z0 S$ O, m( H B* }$ V
: ^5 U& E4 r. G if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;7 `3 ^$ u9 m' ]( R! c/ U4 \ k, k
- E5 x9 y2 w1 \4 X7 A# \9 s' K 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;$ e! P# U; ^4 I2 ]( Z$ H" b y7 G
! L& I/ W) U+ T. H4 H. ^( C; a, a* M& `5 p" M i N return true;% _: ]2 p- i+ w! M2 H: C8 n
}8 u$ Y- `$ t9 _: G9 ^8 M. E
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。4 O5 s4 F y/ I4 B# d* k+ v$ s
8 B" b3 l6 K/ c, e; g9 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文件。
; g" I4 {& [/ Y3 i) R% Z) e( e9 B: H: y k1 \ 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。% x( S% q* i5 p% V
+ _( W4 q& R- R5 i2 W! V
; ?7 V& F7 X: T1 M{( ?0 C0 O* U+ e 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
7 t$ ^6 Q5 y% F: t% n 不过实际利用上会有一定的限制。4 h) D; s) l) ?0 u O l
6 {% k7 z; S: z; Z 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。7 I' ^- U3 j2 P9 \& M" M1 G3 l
# q; _, ~1 D- ]0 \# V 2 U# y& G/ S& K) E; K+ b
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:/ F& H9 }1 N: M9 O+ l$ \# e4 z
2 m1 W. P/ U, `+ t$ a) ^& F 省略...$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]);省略...
" J% }$ z, r( i. c 因此要利用成功就需要条件竞争了。
5 S. Q( W" |- ^# u" o* S ) X; `4 G) @: A
1 F$ L1 O# j9 H9 W- B/ h- y6 d# P4 J8 @* k' l( c2 C 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
6 B8 ?) ^, d" D8 X function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}( _" V+ q) }, S0 B( b o; o
. {8 G8 j( h" [
. c( j8 r& ]1 V1 {0 r$ q! p' l v在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。4 h% \* d- E7 L
; m: L0 {+ b( w" z( V 在is_allow()中增加对$this->savename的二次检查。
8 b6 I4 t: L6 w; w7 K0 h V1 w7 p6 E 嘛,祝各位大师傅中秋快乐!
' C p0 o0 E& Y( W5 r2 \7 j' J; M" y; u- B7 L6 | W2 z1 r% H+ a7 ?0 y r
欢迎光临 中国网络渗透测试联盟 (https://cobjon.com/) | Powered by Discuz! X3.2 |