0 S0 p* n) R( h1 Z. y i j 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。, ` z/ T: a9 I! i* s
6 D5 B. D& _ u
' [ V/ Y! e4 O# [5 I 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:/ L5 K5 c: {$ ~% T& B
) r9 L$ p7 z# x. d) l' Q" @1 h3 k
. x* h0 ^% I" h4 _3 [
- U' y- F) @2 C8 b 对应着avatar.inc.php代码如下:9 b: s+ H" N7 } R* O
9 w% G, x+ C$ v2 j <?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) {5 C, v1 z0 `5 \- E1 R! p
case 'upload':
if(!$_FILES['file']['size']) {* `7 a3 L# u( {2 {9 k( T0 L
: _# v( p) b9 v/ J" V! L9 Z if($DT_PC) dheader('?action=html&reload='.$DT_TIME);
1 y! |# F: W4 M/ A6 c3 E exit('{"error":1,"message":"Error FILE"}');5 q) Y7 y+ `" h% M. o
1 ~; I# k. L& F: j+ O" m9 p' d7 O+ U; `! q' @ }
+ b7 \$ W ?* _, Z4 H4 S require DT_ROOT.'/include/upload.class.php';5 {* ~0 i. |8 a! p
$ext = file_ext($_FILES['file']['name']);
$name = 'avatar'.$_userid.'.'.$ext;! d6 D( S$ E. d( A% u. F
1 u1 H5 u4 U4 [6 Y- @6 O6 K $file = DT_ROOT.'/file/temp/'.$name;* a8 a0 ^" \# a3 ?7 R, g. h' X/ y
]& `$ I% x+ X9 l+ |( y/ U
, p! I! k9 M/ n f+ Q: r if(is_file($file)) file_del($file);8 K1 K/ t6 S, N
8 Z8 A( k5 Q l6 @ $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');& L! D# B4 o' L% O
) o% \& F3 a. A4 r- y; f- P 4 H; f6 t4 b, O5 n& Y
" P, O' q, H5 y: r/ e $upload->adduserid = false;
) i; G) G3 `, ?
if($upload->save()) {5 A1 [* U4 K( j, T4 X8 M# i A% P
2 N1 @5 e' f. y; _# d ...' W2 [" z( C v0 y3 F. J# u
0 a6 n4 `& w/ |+ m7 y+ j. P" a/ l$ n! [: a! M- ?% |( T } else {! ?- P& s9 {% k8 g( q+ Q
4 [- c" t3 D- J; H# x ...8 Z' |+ K+ _. f7 A" m6 p
0 Q% k6 n( n* r# R) s }
- d3 i7 ?0 Q- ]. D9 s% A, o3 d0 x9 N$ \4 \& u break;8 r4 \* z! i$ E$ e; g, {- z: b
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。; G8 c# h% k& p
% L- e. O/ `5 s* L upload对象构造函数如下,include/upload.class.php:25:( t# R" I1 @8 P' H- g( L
<?phpclass upload {
, k& _/ c& X, ]1 k function __construct($_file, $savepath, $savename = '', $fileformat = '') {) x" M6 k7 C9 m7 \$ E
global $DT, $_userid;
& p7 q ?' e$ d t7 W foreach($_file as $file) {
; b: o7 X8 z, v6 c" k4 M6 e$this->file = $file['tmp_name'];7 b- b. g) u3 L1 v. W3 S0 q" }
$ F# o* y. W- }' t, k ]! Y. Z. r $this->file_name = $file['name'];3 E+ n @: E) f1 R! w" u8 `
0 c3 G7 j% m r) V) a; S' B $this->file_size = $file['size'];0 f3 f* b8 P2 s, k: k7 w
4 i' B7 x6 h& _' g$ m' L $this->file_type = $file['type'];& [! } l# P; @- Q
$this->file_error = $file['error'];
; M. v4 ~2 m0 i+ F9 p % r' ?; x- Y" Y0 J3 W4 o( n
0 a0 {- X: q9 Q: |9 {$ { }6 f! K! f9 y! w1 w5 e4 k3 Y6 j
+ D. m7 Y7 E) H& k5 d1 \* w9 V$ x $this->userid = $_userid;$ E' D2 N. Y- b% t! U3 ]
4 B+ C" s+ W6 q $this->ext = file_ext($this->file_name);
9 z, {+ u. E5 N/ B9 D. g $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];, \0 I! E; D4 A) |& T# ]- I
; S- T3 `3 A3 _2 ]$this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;
$this->savepath = $savepath;
& r! L6 d! _6 @) N2 i $this->savename = $savename;
# I* Z# ?# f" J, b5 R+ z/ Y5 X1 I3 t2 m, w }}: W/ S# U$ A$ S- R) e
这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。7 `8 Z/ Y' y6 k/ g, n' \
https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中
因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:8 E) K$ ?3 D4 a% @ A- D& `, E& m( o6 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* |, W6 E7 W$ Z9 [' I! c. I
. f1 I. x- h# {$ `% {# Q# k! w 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
4 \. k5 h# m3 M! W
. v, N) h2 Q R+ B& I7 [ 回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
) o8 Z3 A7 G4 x- d<?phpclass upload {8 l, g- c- `: \) L6 B. a9 i
* B0 b3 Q; r. p2 I3 G3 J function save() {/ H- z2 [) O- {$ v& S
/ Y* B% C9 ^; N$ g# M include load('include.lang');
1 F4 L2 Y/ ]& R. pif($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
" ]# B9 x4 s: ?& x0 M' @ 0 T% E1 k: {+ D; P3 i, o% I
* o& i( v8 F" ^! t' G5 |4 [8 `' ~ if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');
( d$ K, F! x0 h b
. e$ T: B) x) @9 I! J$ E( @if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);
- t3 c* o( I# ~/ F7 ^7 j0 |
/ j& q9 I) \. e5 B# `9 y $this->set_savepath($this->savepath);
. ]" k# w) M( u) ?2 m* e& a6 B' K$this->set_savename($this->savename);: B5 o2 {. D- ?; g
: U- n% v4 ~, f7 D* S $ L% p: G8 ]- O* h3 U A
5 F; A7 J( f" Y& A) y; {* p$ D6 b$ }6 x' z if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);
0 u4 A1 f9 p! u8 L; |! X" W if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);, G0 c- n' y9 @5 e9 Y3 @
if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);. D7 p/ Y% H( k2 ^) V( B
, B# b f! i5 {6 a2 z: O! @8 n2 A & [/ }# H: S& u. J
' E# j b3 K' ~ $this->image = $this->is_image();
( Q v5 W' ]! q& n+ M2 B if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);% Y# L+ f- j: Y
* q, O- A0 j. b3 ~& I% B+ X% C. {return true;' e& \+ B" P# L4 W; [: p v' f! I
}}8 G4 J+ T1 a8 d7 ?8 O0 l7 D8 ^
先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:
, \$ u" z R) L1 F3 C <?php
$ y$ m! ]. P/ V8 f7 B# M function is_allow() {
8 y7 O0 `9 x1 o# `9 B: R! Q' O6 |( t# E% e$ ^ if(!$this->fileformat) return false;
if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; W5 e; ?5 D6 |5 f- 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;
5 D* X6 c0 q% A# g2 \( k return true;
$ W, b2 p2 r) s, [* ? }9 \0 f# F- y; r/ W' n
9 D: J- Z& B# o/ u( C+ a! \+ f1 T1 b$ ]) T 可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。# a+ j9 O. s9 R2 A, H
接着会进行真正的保存。通过$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文件。
2 E c! H) o: `" n1 Z4 v 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。& s) x1 ~2 B0 D H6 s
5 ~2 O. k3 D' P4 m1 {0 `3 m* U- X/ s9 J- T4 g3 {
# @% H; k; o2 Q
$ q9 p: Y, F6 w 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
3 u! i5 t. R5 l; k+ W% F9 q 不过实际利用上会有一定的限制。
0 C7 C, |2 m. _. f7 Q& D 第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。
: K4 l% b& O5 u3 S& a5 |' K- O8 ] 第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:$ O6 J4 M" w8 V' k$ q* L) N
3 p" L, F7 Y/ s" l8 N; Q) _( f- j) j4 h" W. ^: l 省略...$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]);省略...
因此要利用成功就需要条件竞争了。) L, B; c" j& O: I" r
3 [* |8 U! I9 M
/ ?0 P( w, G8 L0 |. _2 F" i( X 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:
, q% a/ x- v7 a5 k! D* A9 O' y0 ] function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}
. d% `! b2 M4 l5 x, N8 {/ {
0 [" v" D1 o6 B 在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
3 X, P! m# p5 [" J g! N* X) J 在is_allow()中增加对$this->savename的二次检查。
嘛,祝各位大师傅中秋快乐!
+ U8 o- m' D( ?. K# ~
欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |