1 z4 X0 p) G5 Z) @6 T+ _ q# c% C 2018年9月21日,Destoon官方发布安全更新,修复了由用户“索马里的海贼”反馈的一个漏洞。6 l! x7 }( U8 r0 |
& E6 K( U: C) _& A4 b
+ @: A$ @4 w7 Y/ _- }7 J
2 D7 N6 u* v% Z# T7 H0 a 根据更新消息可知漏洞发生在头像上传处。Destoon中处理头像上传的是 module/member/avatar.inc.php 文件。在会员中心处上传头像时抓包,部分内容如下:
- f$ r9 ~- U: s
3 T0 R5 r+ w0 }: W
对应着avatar.inc.php代码如下:
<?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) {. B, {+ Z$ f5 l" c7 i0 [
1 E& T' j( i4 ~( ~9 _/ j, \6 ]4 [( ~9 K7 ?, P" W; g7 d case 'upload':
if(!$_FILES['file']['size']) {8 O ~8 @' d3 f" H
- L0 z* O# w2 c. y/ Y if($DT_PC) dheader('?action=html&reload='.$DT_TIME);: W2 v- S8 \. ^( t, Y4 ~- _/ m* n3 b
( q* H' h0 `, c8 P- Sexit('{"error":1,"message":"Error FILE"}');
/ H8 `; N3 n. E, d}
$ d! `, K1 H, L- U5 p$ prequire DT_ROOT.'/include/upload.class.php';
- k* H) t3 d, P' ?, `; Z+ m- j% T6 s3 e# G, W4 E2 F
* T7 ?: `5 i- c3 a+ r& v1 o $ext = file_ext($_FILES['file']['name']);" K& j) f' U% e* P% T
+ H. f& t) x3 d8 q$ `8 t5 v$ `& U- r0 T $name = 'avatar'.$_userid.'.'.$ext;2 b5 P6 y1 _( P4 W+ ?7 l
+ f5 [7 ?) I$ |6 o! n1 ~7 Z/ Q& a( }( \0 w0 F $file = DT_ROOT.'/file/temp/'.$name;
! ~5 h( H' j3 [
if(is_file($file)) file_del($file);
$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');2 v5 v `0 e' I3 d' ?, R9 ~* J" c
1 d4 R! P; L2 o* O h
$upload->adduserid = false;: r* _3 D+ F1 O3 h# @2 Q/ H2 `
, e3 A, v$ l4 A, \. v
if($upload->save()) {
...
} else {
: R2 e; q+ \& f1 Y0 e- _' _$ b3 Y( |6 V) H- I/ Y: | ...
}
" q# o! S; W4 t; `, h8 Fbreak;
这里通过$_FILES['file']依次获取了上传文件扩展名$ext、保存临时文件名$name、保存临时文件完整路径$file变量。之后通过new upload();创立一个upload对象,等到$upload->save()时再将文件真正写入。
; X% O7 d+ k/ S upload对象构造函数如下,include/upload.class.php:25:
! }: v8 f' O; G0 H1 [<?phpclass upload {* v; ?2 [6 N: Y4 K- g* s
2 F* {( y; T+ S function __construct($_file, $savepath, $savename = '', $fileformat = '') {
" l3 S/ r3 m- _$ i. G! g/ O& G* L global $DT, $_userid;3 e' x! X' o: J8 E; O- r3 p
v: ^) j5 U* d! B$ O, c* u- v- q1 t" S6 ^: R+ k8 M9 h& U1 ^ foreach($_file as $file) {
$this->file = $file['tmp_name'];2 A- m6 z# E" a" l9 b; N
; l% n: K6 @$ S $this->file_name = $file['name'];
" X; b/ b5 q) p0 V" m $this->file_size = $file['size'];
$this->file_type = $file['type'];) |1 V4 j8 d+ |- q3 K
$this->file_error = $file['error'];) n2 |$ o; ]" `+ w% H
( D% D; i7 x8 s, P3 E% R / v' G8 l9 M9 p
' ^2 e) M6 W" q D8 s) e; v }, r& e6 ]. R- o9 x4 ~) e7 y
$this->userid = $_userid;
' o) V8 Y% `) ~4 `& N" K) r8 _" z4 S* j $this->ext = file_ext($this->file_name);
! W* D5 G8 z1 q, f r& m! E! A0 d( o) i* c; a( j $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype'];, `* k: H+ W* e" O% U/ A/ R8 ^4 k* I, B
8 X8 s. C% B% h( A3 d3 S $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024;; a! g8 l" U& [ Z7 k
& |6 g, U( V' g- c8 D/ q }$this->savepath = $savepath; \1 H2 V; @% O! P
$this->savename = $savename;- T4 h F0 R. |2 X: C
, k* J6 z) I/ C$ y0 L' o V, r- E5 |6 G/ M, @ }}
7 z1 }2 w$ W5 G, ~, V 这里通过foreach($_file as $file)来遍历初始化各项参数。而savepath、savename则是通过__construct($_file, $savepath, $savename = '', $fileformat = '')直接传入参数指定。 v0 E/ g+ G; ~" s
( i/ [. o7 N; A3 k; U0 q因此考虑上传了两个文件,第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html),则在avatar.inc.php中 3 V8 R; l% x: s' 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/ U9 `& W# m; M' }) C3 `
) e5 I. n+ H4 w1 N9 ^7 \3 r% J, o( t8 I0 K 而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:* V9 Q! s8 d7 J4 B# s8 c
1 `1 T. |7 ?1 B, U; i, k* A6 N
1 V- x& ?( q/ d! u
回到avatar.inc.php,当进行文件保存时调用$upload->save(),include/upload.class.php:50:
, y& }# A$ q) e+ ^, ]! z<?phpclass upload {8 j" U8 C5 `1 O! ?4 Q% U
function save() {1 i" o' w6 }- b3 t8 U' {! {
include load('include.lang');
3 _# k2 ]$ f0 t/ ^3 C, C* ~! w- F2 p8 k) |) U( K3 N if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')');
& w* u1 c5 S. Z# s" a
if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)');7 _9 h5 {! {5 T% n
i; n, K$ p6 s3 A: F1 p8 M( D8 Q# W. R" w1 M6 E5 `2 W: [: L
* l3 R2 y% z3 B+ I; j- d- L2 e7 o& e( d, {% G if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']);( b, A4 j6 N! A* l( P; W
7 x8 q% n& D$ ?4 ~' T) M* k$ l% N f
+ _ L* u9 }5 A" w+ B9 p$this->set_savepath($this->savepath);
( L# g, D/ h- P" d* D $this->set_savename($this->savename);
4 F! K* W7 E8 [ q/ ^7 R3 p4 ~- d) n: ?9 X& e6 {8 }6 ` 1 ^6 U; O' ~: h* ?
8 x' K) { n& I8 ^ if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']);' p) n& g0 o% F! ^, B! p% y
" H8 ?' \, C) O3 h if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']);- j( I- i* ~4 H. c" x
/ n. X- n$ H! \; e- U3 J4 ^( ~/ } if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']);
& h) O" K5 w% q, S* K1 j# j. G" {6 P5 t# \2 V
/ G$ X; Q' H1 u7 Q2 E5 v5 q; i5 X. X) |3 m- Z/ c: ?. D $this->image = $this->is_image();1 r1 h: F: r0 ~3 c
- y& c8 \: `! _ n6 F4 S if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD);3 |5 ~/ J3 G! ^) i# R
) h" W- o+ j3 b( } return true;
}}+ f; \4 |: { U3 `
' n1 x, y. o1 Y7 E9 [- w/ ~1 [, ]" G先经过几个基本参数的检查,然后调用$this->is_allow()来进行安全检查 include/upload.class.php:72:% U; j `) p* V% ]
3 _4 X( M) _ }( ]5 P+ G; R1 r9 X0 a) d<?php
: p+ j2 J5 D: p* c, |) o3 F* x9 k6 ] S function is_allow() {
1 R2 T# @1 [/ U5 k/ gif(!$this->fileformat) return false;
3 X# T; ^6 K( x6 E0 A( C2 aif(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false;
+ q+ O& U) {3 y8 {# y9 w; f6 q9 D( O Z7 l* i 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;% ~) x' k, S4 V: Z1 A( L
( ?, y- a! V1 |( H |2 A, `return true;: u, e7 r% f" I, y
* Y2 g# }: y+ t$ j$ J }
可以看到这里仅仅对$this->ext进行了检查,如前此时$this->ext为jpg,检查通过。
' k! ]+ H+ G, u8 d1 F 接着会进行真正的保存。通过$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文件。
! T/ Q3 T4 j1 V' E+ R2 Z; k 综上,上传两个文件,其中第一个文件以php为结尾如1.php,用于设置后缀名为php;第二个文件为1.jpg,jpg用于绕过检测,其内容为php一句话木马(图片马)。3 R# O6 n* B5 M# n# c9 }( E3 i
3 }# f1 {7 k) p% Y" P( W- |* X9 ?( W7 h' M( F w" p! c
/ a. ?7 m' g* ]# G7 ]4 p4 [6 V: c& z 然后访问http://127.0.0.1/file/temp/avatar1.php 即可。其中1是自己的_userid
9 o4 G7 V5 J- F0 ~+ N; p6 S% _3 V; P- j不过实际利用上会有一定的限制。
1 }/ ?* Y5 S" {7 [第一点是destoon使用了伪静态规则,限制了file目录下php文件的执行。! N" F. U l( \" L
: j- [$ E, q, y+ B! w4 I1 v; h2 a m0 ^2 F
( p% q) a! v0 G' r( }1 Q
第二点是avatar.inc.php中在$upload->save()后,会再次对文件进行检查,然后重命名为xx.jpg:7 {& j7 m& V7 g) N
S' y4 ~, E7 D( R* `, X) Q: O- w' | 省略...$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]);省略...
' G' G. {9 ~& `- b 因此要利用成功就需要条件竞争了。
. n9 Q9 B7 {$ U- u' n: Z) r
" I4 o3 P \" y 在upload的一开始,就进行一次后缀名的检查。其中is_image如下:4 V/ X4 a# G3 h/ z
6 z/ E+ C/ I+ y! J2 x0 v. d3 Q. I function is_image($file) { return preg_match("/^(jpg|jpeg|gif|png|bmp)$/i", file_ext($file));}# l8 n) |6 G. N/ |
, T4 e* c9 _! X
在__construct()的foreach中使用了break,获取了第一个文件后就跳出循环。
在is_allow()中增加对$this->savename的二次检查。
+ H9 s. d/ A% Z1 G: ^" L L7 P1 D9 }' j( `$ W& H 嘛,祝各位大师傅中秋快乐!
@- m! T' u9 L& m6 ~8 _1 h
| 欢迎光临 中国网络渗透测试联盟 (https://www.cobjon.com/) | Powered by Discuz! X3.2 |