2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。4 K" ?" f# j* }7 r* h
0 ? y- R. Y2 S7 Y
& y4 I8 Y3 }) z! U" l 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
' Y7 u! M4 g% v3 x j; j7 o
: }' K ^9 J8 t5 E
对应着avatar.inc.php代码如下:+ }& [( _3 ~8 o
8 ~% r" S/ r' E<?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) {+ @( \* K5 g. {; E& d
case 'upload':9 O$ b# H, o4 t5 l
if(!$_FILES['file']['size']) {, I- G5 b! M) j5 t( [& V- ^4 G
3 r: Z" `2 Q1 p! Cif($DT_PC) dheader('?action=html&reload='.$DT_TIME);
7 y3 x. v' ]7 o9 ^% t2 k2 F+ U, B# S2 D$ r$ q2 V$ a exit('{"error":1,"message":"Error FILE"}');
}
require DT_ROOT.'/include/upload.class.php';
: ~9 t0 o- }* D8 K' Y' R# b$ L. F4 G0 u0 c+ I6 [' \ - y8 i( o! ^: g
: }3 B# }3 W+ B+ P) S5 w- U4 c! N# O' u $ext = file_ext($_FILES['file']['name']);% A8 @/ A! k. b7 O. I' g
' F+ d: P. Z9 z6 g7 s, N. y# s) [! d3 k3 S/ p0 W1 ] $name = 'avatar'.$_userid.'.'.$ext;5 Y! l c7 S6 t5 G2 G
~) {: r- `" W+ b! L7 U% b) f3 |9 V% b- V4 ~5 K6 @# o $file = DT_ROOT.'/file/temp/'.$name;+ X* {6 S' ?9 Z0 |/ u( w; o
% r3 P& \6 K& N6 }/ ^1 ^9 e
if(is_file($file)) file_del($file);
( u" P, s2 j% E. J3 K$ K$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');" F' K0 }2 A% p
, p$ H$ @; d6 ~, h8 P3 q# Q( M! z9 f9 I 0 O+ O. ]1 `$ `$ Y: ]
, Q) I& c" z4 a& P9 h$upload->adduserid = false;6 p6 i5 [5 h+ _" x
, f1 p% ~5 r3 a* J9 f, j3 V# i: O' R' V) ~ 3 P0 H- X* o1 N/ `0 Y' S: A
' d3 V# U4 t% d' \( \# H: y- l. `% F/ i2 k+ f- D6 t( N" K if($upload->save()) {
$ ?: [$ R4 _4 Z- n6 f# F6 D ...
5 k% j0 y% K' l/ d/ P4 g" T) t' J } else {9 u0 L) t$ o* Z3 m' G2 f+ \! {
...
}
' _1 T. ]; X" \) ], h" Wbreak;' m! }) \# x* _# M4 x. p
; R0 _* Y! D r" ]' o9 I0 f0 r$ k% u1 M8 n 这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
0 i# {" {+ M9 D& O0 s/ @) _+ z0 fupload对象构造函数如下,include/upload.class.php:25:2 X3 e2 |3 ]# P* h" K
9 l! c) q/ g h& i1 p& Q, o, S' S, G, |& m6 _2 D6 F <?phpclass upload {+ M" H1 A F0 D. x
6 J; R0 D& C, m( `! w" o) F( f; wfunction __construct($_file, $savepath, $savename = '', $fileformat = '') {
global $DT, $_userid;1 y2 R$ _- d' F/ z4 e% {# Q
2 r2 K0 M% O( L) j3 Y( K2 b3 _6 W& y, \7 m foreach($_file as $file) {
1 Z: J4 [8 i: s! I! w1 G5 R( @0 [! a0 b3 n" ~0 d $this->file = $file['tmp_name'];) G9 o& V7 y3 O# ]. z" G9 o" z
" ~/ s; }" {1 B' t2 ~. D/ y7 M $this->file_name = $file['name'];1 H" s) ~5 S; ?
) j" {4 C' X/ P. E9 O9 s* G$this->file_size = $file['size'];
0 ~5 y( G/ c4 ^1 I. o$this->file_type = $file['type'];
1 }0 i3 d% A0 h* n7 t7 s9 ~/ Qg& L5 {$ M( H5 [7 [3 [ $this->file_error = $file['error'];
5 e2 i+ k: g* g# ]* o9 }& z& g7 S9 g/ q& K- ]4 { + ~# _8 J( q9 g. }& o
' C" W7 u% L: l$ N* A }! q, @! n Q4 P0 ]% o+ f
9 K& q9 B* p5 N4 }' v1 n8 v6 P$this->userid = $_userid;# [5 ^2 m" \7 I9 P* V6 C
9 Y& \& c9 G$ `1 e $this->ext = file_ext($this->file_name);
, p( g1 L7 {4 T. A: e1 a& g; K4 q$ B& d* r$ m $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];, q1 X+ s$ P0 v' K$ s! G5 {. \+ l
, z3 G, z! P3 a2 k+ z3 u7 r1 |3 T$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;3 r7 @* ]* \2 ` D7 P8 S
k3 r+ i* ^, H3 F( v3 E; x2 c- l, S2 }7 Q' [- h' _9 u# j $this->savepath = $savepath;# c$ f& C6 @$ ~/ d# R. i
7 h' {% r% Z6 W7 t0 z$this->savename = $savename;
, L3 b" a" L. w/ V* X8 ~6 O}}! P) `; O/ c' Q8 ~, I3 O& a
/ M6 }* _# Q# B; Z& a9 ^5 ]6 B2 E6 O7 i' _ 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。
, `: x# e+ o+ X; C3 W因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 0 p+ y$ o1 k( Y1 N
$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
5 I6 c6 _, t" J而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:2 t. H0 U7 d. ~! T- M
- k, t% v0 h9 q* V) D* i! x
7 o- J5 s* P* ^. F( l* U 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:5 e4 k/ n6 l. g
) H. Y* K; x. n* |0 J3 E m; W<?phpclass upload {/ s" X1 N2 V) i2 i0 |
* t- Z* P* [$ W2 D5 Z! P. Rfunction save() {, F$ U1 Z0 Y; X7 \, v9 V
. I3 o R. a0 x8 I3 ?; [+ l! D+ g) a& b2 l7 k! r1 h include load('include.lang');4 Z% m$ V c1 x2 }
- V; H* u6 e, s, Z5 j1 A if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');' X7 e; P, T* V
; [8 m3 \9 d4 M
6 D, M: K7 Y @1 c$ V; o- {2 v if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');1 S! s/ M7 l5 ~1 w
. @+ p9 g6 G8 x/ ~. Y, y" d. q2 D3 ]; r, O l8 x
; t; ?! x& y9 F& eif(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
4 |, u* B8 O2 D: M8 E! `' f: L : ~* E4 _4 C# F0 x% O+ P
4 J4 l. o0 {- Z! J) D& s$ O# N9 m( T8 w- n6 C' R $this->set_savepath($this->savepath);
% M$ _3 G P. |# }* _1 V4 c0 ]& A; j6 X; s- m. g2 b# P $this->set_savename($this->savename);
: ]* y" w3 e9 X) W6 K8 u% @! ?( a( Z2 v, c
if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);5 a9 e9 O' |) W! Y) {& Z" Z, |
if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);
0 }7 c- t( |5 D4 u, J i7 o- N1 u; v/ F" t5 g- Y: [ H. E" N if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);3 G! t/ g; l" p6 U7 {
, C+ ?& E* w- B8 \' ~5 c' i( }0 N1 [' [* H- r# _
* Y( |+ d1 O# s6 Z5 B $this->image = $this->is_image();' k/ [2 X' n1 ?/ A! \; B7 z7 [
* R: L! p; z/ |- X if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);' T9 c3 J+ [2 r9 P
: X, |! C& W+ j: v, e8 e4 D8 S4 R return true;
' D% y% r: Z* U. R# E& I, R$ ?; J. P& S8 l l }}
# w6 E( @8 c1 X8 J) B. @+ s4 |! F3 E1 }% j. t 先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
<?php8 X) C/ Z$ o8 k( _7 I" y! q5 }* ^
- G6 n' \+ v1 k# x: t) }function is_allow() {( a3 b0 |0 @6 c3 s7 v4 b; k8 r3 b
8 M4 T1 F3 Z* wif(!$this->fileformat) return false;
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;. X9 p4 s) H8 _ ?, F5 Y1 E5 H! v
6 w! y& N* S8 c- Jif(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;
( k+ J8 E; T; D" treturn true;
) J8 I5 q- ]: U; Y }
; \% J1 v3 Q/ ]" B: V8 p 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
' O$ k+ L& F8 e9 z8 ]1 s Y接着会进行真正的保存。通过$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文件。
5 s6 j+ c( e1 Y+ ]6 }综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。( L0 F1 u7 i3 z) q1 v" a
' e& N" L$ I# r3 A( e. R
9 t ]" O" X$ [
然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid0 R2 S$ x) v$ e. W2 Q
7 p. M" V% P7 w, m2 Y R 不过实际利用上会有一定的限制。
" ~; e% b; U) E& [" h* p, ^ 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
5 M, z& T$ B7 E
% F" Q6 z1 Q% b: ~3 i7 S" e3 ~( H- e
9 q; h+ g9 S5 g 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:: s4 q* ~; y' e! g
. W- ~6 p: K$ E9 Y6 M$ g; x- w6 H. H& D: k 省略...$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]);省略...' v4 ?* O9 H' A; M" ?+ R
$ s3 i, ^: R5 F: F$ u8 |; t因此要利用成功就需要条件竞争了。0 M7 ~8 u) f7 L! ^" L9 j- x. g
; U1 |( v" k1 B) |3 m' r5 n5 x! J
/ Y3 o# v, j+ V# F
! H: u( T$ g7 ` 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:7 e9 Y+ f- }0 j, V+ J8 E8 {( D! e( J
j* b- K, i: I+ `& J. Ffunction is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
) d+ O9 g/ w' w" C6 @ 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。5 n8 w; \* ]2 g5 Z! A
在is_allow()中增加对$this->savename的二次检查。
# `- c6 `' a0 |5 B) e 嘛,祝各位大师傅中秋快乐!1 m# W9 W* t& X d- f; k& g4 B
# T; _9 V) f! f, k+ B- m8 r0 i# Z+ M7 P9 \
| 欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |