<style> .fragment { font-size: 0.7em; } </style> ## 《探索網頁前端資安宇宙》 ## 第一章 --- ## 1-1 瀏覽器的安全模型 ---- - <span class="fragment">網頁前端與後端最大的不同,在於前端的程式碼是跑在瀏覽器上面</span> - <span class="fragment">瀏覽器負責 render HTML 、解析 CSS 並繪製、執行頁面上的 JavaScript</span> - <span class="fragment">以網頁前端來說,它的執行環境就是瀏覽器</span> - <span class="fragment">網頁前端跑在作業系統中的其中一個應用程式(瀏覽器)上面,越內層限制越多</span> <div class="fragment" style="text-align:center"> <img src="https://hackmd.io/_uploads/BkQSsfR1Je.png" width="600px"/> </div> ---- - <span class="fragment">有些事不是網頁前端做不到,而是瀏覽器不讓你做</span> - <span class="fragment">舉例來說,後端伺服器可以執行檔案讀寫操作</span> - <span class="fragment">但網頁前端卻不一定能做到,是因為瀏覽器不讓做</span> - <span class="fragment">所以說:瀏覽器不給你的,你拿不到就是拿不到</span> ---- ### 瀏覽器做了哪些安全限制? ---- #### 禁止主動讀寫本機的檔案 - <span class="fragment">後端的程式碼是直接跑在作業系統上,也就是一個一般的應用程式</span> - <span class="fragment">如果沒有特別限制權限則基本上想做什麼都可以</span> - <span class="fragment">前端則有諸多限制,比如不能主動讀寫電腦裡面的檔案</span> - <span class="fragment">前端可以透過 `<input type="file"/>` 來讓使用者選擇檔案也可將內容檔案讀出來</span> - <span class="fragment">但無法直接用 `fetch('file:///data/index.html')` 之類的操作去讀取檔案</span> - <span class="fragment">也無法使用 `window.open('file:///data/index.html')` 去開啟</span> ---- #### 為什麼要限制瀏覽器不能讀取檔案? - <span class="fragment">如果可以讀取檔案的話會怎樣?</span> - <span class="fragment">就可以直接讀取你的 /etc/passwd,讀取你的 SSH key,讀取你所有設定檔有敏感資訊的檔案,非常可怕</span> - <span class="fragment">所以瀏覽器禁止 JavaScript 去主動讀取檔案是非常有必要的,否則你只要開個網頁所有檔案內容就都被看光了</span> ---- - <span class="fragment">實際案例:https://blogs.opera.com/security/2021/09/bug-bounty-guest-post-local-file-read-via-stored-xss-in-the-opera-browser/</span> - <span class="fragment">簡單來說就是在建立筆記時,可以包含一個連結,可以使用正常網址也可以使用 `javascript:alert(1)` 這種類型的連結去執行程式碼,也因為 Opera 筆記頁面屬於特殊協定,例如可以開啟 `file://` 的網頁,還可以幫網頁截圖並得到截圖結果,而利用 XSS 可以去開啟本機檔案並截圖,傳到攻擊者的伺服器達到偷取檔案的目的,此漏洞價值 4000 美金</span> ---- #### 禁止呼叫系統 API - <span class="fragment">一般應用程式可以做很多事,例如更改系統設定或網路設定,可以透過作業系統提供的 API 進行很多操作,然而瀏覽器沒有提供網頁前端相對應的 API ,因此做不到</span> - <span class="fragment">在網頁前端執行 JavaScript 時,只能使用瀏覽器提供給我們開發者的東西,例如可以使用 fetch 去發送請求,也可以用 setTimeout 去設定計時器等</span> ---- - <span class="fragment">如果想要使用作業系統的 API ,除非瀏覽器也有提供相對應的 API,否則網頁上的 JavaScript 是無法使用的</span> - <span class="fragment">比如說:</span> - <span class="fragment">藍芽:瀏覽器有提供與藍芽設備溝通的 API:Web Bluetooth API,因此網頁上的 JavaScript 可以做出跟藍牙有關的應用</span> - <span class="fragment">麥克風與攝影機:而另一個 MediaDevices API 則是讓 JavaScript 可以取得麥克風跟攝影機的資料</span> ---- - <span class="fragment">而瀏覽器在提供這些 API 時,同時也會實作權限管理的機制:</span> - <span class="fragment">通常會跳出通知要求使用者主動同意並允許該權限,才能讓網頁拿到東西,同樣是為了安全性考量</span> <span class="fragment">![image](https://hackmd.io/_uploads/SJoOSwAykx.png =50%x) ![image](https://hackmd.io/_uploads/SkI78vRkke.png =50%x) </span> - <span class="fragment">通常瀏覽器會開放給 JavaScript 的權限都不大,無法執行偏向系統層面的操作 </span> ---- #### 禁止存取其他網頁的內容 - <span class="fragment">這可以算是瀏覽器最重要的一個安全假設:一個網頁永遠不該有權限存取到其他網頁的內容</span> - <span class="fragment">因此每個網頁都只有針對自己的權限,可以改自己的 HTML ,執行想要的 JavaScript 程式碼,但不該拿到其他網頁的資料,這就是同源政策(same-origin policy,有時被簡稱為 SOP)</span> - <span class="fragment">不只是頁面上的「內容」拿不到,甚至連別的頁面的「網址」都拿不到</span> <span class="fragment"> ```js const win = window.open("https://github.com/") setTimeout(() => { console.log(win.location.href) },3000) ``` </span> ---- - <span class="fragment">看起來非常基本且也很必要,但其實瀏覽器要實作出這個功能並沒有那麼容易,瀏覽器也是經歷了許多次攻擊,加上很多防禦措施及調整架構以後才讓自己越來越安全,越能符合這個安全的要求</span> - <span class="fragment">舉例來說,從瀏覽器層面要阻止一個網頁去存取其他網頁的資料從表面上看起來不難,就在存取時檢查來源就好,不同來源就擋掉,講起來容易,但隨著攻擊手法進步,瀏覽器需要考慮的事情就更多了</span> - <span class="fragment">比如說,Chrome 為了因應可以透過 CPU 的缺陷來讀到同一個 process 的資料而將自己的架構調整得更安全,確保不同的網頁無論是使用什麼方式載入(包含圖片及 iframe 等),都會使用不同的 process 去處理,而這一系列的安全措施被稱為 Site Isolation</span> - <span class="fragment">[關於瀏覽器 process 的推薦文章](https://medium.com/starbugs/%E8%BA%AB%E7%82%BA-web-%E5%B7%A5%E7%A8%8B%E5%B8%AB-%E4%BD%A0%E6%87%89%E8%A9%B2%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E7%80%8F%E8%A6%BD%E5%99%A8%E6%9E%B6%E6%A7%8B%E6%BC%94%E9%80%B2%E5%8F%B2-feat-%E6%B8%B2%E6%9F%93%E5%BC%95%E6%93%8E%E9%81%8B%E4%BD%9C%E6%A9%9F%E5%88%B6-6d95d4d960ee)</span> ---- #### 針對「沒辦法存取其他頁面上的東西」這點來看一個繞過的範例 - <span class="fragment">假設現在網頁是 `a.example.com`</span> - <span class="fragment">裡面有一個 iframe 的網址是 `b.example.com`</span> - <span class="fragment">用 `frames[0].location = 'about:blank'` 將 iframe 重新導向之後,iframe 就會變得跟 `a.example.com` same-origin</span> - <span class="fragment">此時去讀取 iframe 的歷史紀錄:`frames[0].navigation.entries()`,就可以從裡面拿到原本 `b.example.com` 的網址</span> ---- - <span class="fragment">這是一個不應該發生的 Bug,當 iframe 重新導向到其他網址之後,`navigation.entries()` 就應該清空才對,否則可能可以夠透過這種方式取得 access token</span> <span class="fragment">![image](https://hackmd.io/_uploads/r1XM9PC1kx.png =70%x)</span> - <span class="fragment">這是一個繞過同源政策的案例,雖然只能讀取網址,但依然是一個資安漏洞.價值 2000 美金</span> ---- ### 小結 - <span class="fragment">在理解瀏覽器的安全模型時,重點只有一個,瀏覽器不給你的,拿不到就是拿不到,反之你卻拿到了,那就代表你找到了瀏覽器的漏洞,可以去回報漏洞拿獎金</span> - <span class="fragment">而最嚴重的漏洞是,可以讓攻擊者突破瀏覽器的限制,去做出違反瀏覽器安全假設的事情</span> ---- - <span class="fragment">比如說剛才介紹過的 SOP bypass,可以違反同源政策,去讀取到其他網頁的資料,雖然剛剛介紹的只能讀取到 URL ,但有些更厲害的可以讀取到內容</span> - <span class="fragment">假如你打開某一個網頁,看了網站上的內容,但這個網站偷偷執行一段 JavaScript ,利用 SOP bypass 的漏洞去讀取你 gmail 的所有信件內容</span> - <span class="fragment">聽起來很恐怖了,但還有更恐怖的:RCE(Remote Code Execution)遠端程式碼執行</span> ---- - <span class="fragment">RCE(Remote Code Execution)遠端程式碼執行:攻擊者可以透過 JavaScript 去利用瀏覽器的漏洞,在電腦上執行任意指令</span> - <span class="fragment">假如你打開某一個網頁,雖然看完關掉了,但駭客可以對你的電腦下指令,把你電腦的資料全部偷出來,或是偷植入惡意軟體之類的,以往就有不少這種案例</span> ---- - <span class="fragment">曾經有人利用 Chrome 執行 JavaScript 的引擎(V8)的最佳化程式碼 Bug 來達到攻擊,藉由型態的混淆,來達到讀取以及寫入超出範圍的記憶體位置的目的,這招術名字是 Type Confusion</span> - <span class="fragment">做到這點以後,就能夠搭配 [WebAssembly](https://developer.mozilla.org/zh-TW/docs/WebAssembly/Concepts) 的特性,把編譯過的 WebAssembly 程式碼蓋掉換成任意的程式碼,就達成了任意程式碼執行的目的</span> - <span class="fragment">攻擊的 JavaScript 長相自行查閱書籍</span> ---- - <span class="fragment">知道了瀏覽器的限制,未來就可以跟 PM 說</span> <span class="fragment" style="font-size:1em">這功能前端做不到,因為瀏覽器不讓我用這功能(兩手一攤)</span> ---- ### 問題回顧 1. <span style="font-size:0.8em">網頁前端與後端執行環境的主要區別是什麼?為什麼前端無法像後端一樣執行檔案讀寫操作?</span> 2. <span style="font-size:0.8em">瀏覽器的安全限制中,為什麼禁止前端程式碼主動讀寫本機檔案?這項限制的目的和潛在風險是什麼?</span> 3. <span style="font-size:0.8em">瀏覽器提供了一些系統 API,例如 Web Bluetooth API 和 MediaDevices API,但這些 API 的使用需要經過什麼樣的權限管理機制?</span> 4. <span style="font-size:0.8em">同源政策(SOP)是什麼?舉例說明一個可能繞過同源政策的攻擊手法,並解釋瀏覽器如何進行防禦。</span> 5. <span style="font-size:0.8em">RCE(Remote Code Execution)遠端程式碼執行的攻擊是什麼?簡述一個 RCE 攻擊案例,並說明這類攻擊的風險。</span> --- ## 1-2 前端資安還是得從 XSS 開始談起才對味 ---- ### XSS 是什麼?可以做到哪些事情? - <span class="fragment">誕生於 1999 年左右</span> - <span class="fragment">全名:Cross-site scripting,不叫 CSS 是因為已是 Cascading Style Sheets 的縮寫,因此取叫 XSS</span> - <span class="fragment">以現在的角度來看這個名稱其實不太對,很多 XSS 並不只有 Cross-site ,這個作者之後會再講到 site 與 origin 的差別</span> - <span class="fragment">簡單來說,XSS 代表著攻擊者可以在其他人的網站上面執行 JavaScript 程式碼</span> ---- - <span class="fragment">舉例來說,假設有個網站是這樣寫:</span> <span class="fragment"> ```php <?php echo "Hello, ", $_GET['name']; ?> ```</span> - <span class="fragment">這段會直接在頁面上輸出 query string 中的內容,只要瀏覽 `index.php?name=lois` ,頁面上就會出現:「`Hello, lois`」,非常正常</span> - <span class="fragment">那如果瀏覽的是 `index.php?name=<script>alert(1)</script>` 呢?</span> - <span class="fragment">輸出結果就會變成: `Hello, <script>alert(1)</script>` </span> ---- - <span class="fragment"> `<script>` 裡面的內容就會被當作是 JavaScript 程式碼來執行,畫面上就跳出了一個 alert 視窗,代表著我可以在其他人的網站裡面執行 JavaScript 程式碼,甚至於偷取所有 localStorage 裡面的東西,如果身份驗證用的 token 存在裡面,偷走就可以用其他人的身份登入網站</span> - <span class="fragment">所以有些人提倡身份驗證用的 token 應該存在 cookie,因為 localStorage 會被偷,而 cookie 如果有加上 HttpOnly 這個 flag 是完全碰不到的</span> ---- - <span class="fragment">但若剛好網站沒有使用 HttpOnly 這個 flag ,就能利用 `document.cookie` 或是更新的 cookieStore API 來拿到該網站的 cookie ,就算真的偷不到,也可以直接使用 `fetch()` 來呼叫 API 受害者身份向 Server 發送請求</span> - <span class="fragment">假設 Youtube 有 XSS 漏洞,攻擊者就可以利用這個漏洞新增影片、刪除影片或是偷取觀看紀錄或後台數據等,只要是正常操作可以做到的事攻擊者都能做到</span> ---- - <span class="fragment">所以你會發現,很多網站再改密碼時,會需要再輸入一次密碼,尤其是在已登入的情況下,為什麼要再輸入一次?</span> - <span class="fragment">因為你絕對知道自己的密碼,但攻擊者不知道,當後端提供的改密碼 API 需要提供 `currentPassword` 跟 `newPassword` 兩個參數並通過身份驗證後即可更改密碼,那就算攻擊者找到並利用 XSS 漏洞,他也無法更改你的密碼,因為他不知道你現在的密碼</span> - <span class="fragment">反之,如果改密碼不需要 `currentPassword` ,那攻擊者可以利用 XSS 直接把你密碼改掉,等於竊取了整個帳號,透過 XSS 拿到的 auth token 有時間限制,到了會過期,但如果攻擊者是直接改掉密碼,那就能用你的密碼正大光明登入</span> - <span class="fragment">所以有許多敏感操作都會需要再輸入一次密碼,或是甚至有第二組密碼,目的之一就是為了防禦這種情況</span> ---- ### XSS 的來源 - <span class="fragment">之所以會有 XSS 的問題,是因為直接在頁面上顯示了使用者的輸入,導致使用者可以輸入一個惡意的 payload 並植入 JavaScript 程式碼</span> ---- #### 以 payload 的來源分類 XSS ---- #### 1. 內容是如何被放到頁面上的 ---- - <span class="fragment">以方才提到的 PHP 的例子,攻擊者的內容是直接在後端就輸出了,因此瀏覽器收到 HTML 時,裡面就已有 XSS 的 payload</span> - <span class="fragment">以 HTML 檔案做例子,一樣可以透過 `index.html?name=<script>alert(1)</script>` 的方式植入任何我們想要的內容,但這次是從前端去輸出內容,是透過 innerHTML 的方式把 payload 新增到頁面上</span> - <span class="fragment">如何區分這兩種呢?可以在瀏覽器中按下右鍵,接著選擇檢視原始碼,就會出現這個頁面 HTTP Response,有在裡面的內容才是從後端輸出的,沒有在裡面的則都是之後用 JavaScript 動態去調整的</span> - <span class="fragment">這兩者的差別就是透過前端新增內容的例子不會跳出 alert ,原因是因為使用 innerHTML 時,插入的 `<script>` 是沒效果的,並不會被執行到,因此攻擊者必須調整 XSS payload 才能執行程式碼</span> ---- #### 2. Payload 有沒有被儲存 ---- - <span class="fragment">方才舉的例子都是直接拿 query string 的內容呈現在頁面上,因此攻擊的 payload 並沒有被儲存在任何地方</span> - <span class="fragment">如果要攻擊的話,得想辦法讓目標去點擊帶有 XSS payload 的連結,才能觸發攻擊,也可以透過其他方式或結合其他手法降低這個門檻,例如使用短網址讓對方看不出異樣,在這種情況,就是針對這一對象進行攻擊</span> ---- - <span class="fragment">而另一種情況,假設留言板的留言可以插入 HTML 程式碼,且沒有做任何過濾,那麼可以留一個帶有 `<script>` 標籤的內容,如此一來任何觀看這個留言板的人都會受到攻擊,攻擊對象就是所有使用者</span> - <span class="fragment">假設 Facebook 的貼文帶有 XSS 漏洞,那所有看到貼文的人都會被攻擊,甚至可以將此攻擊變成 wormable 的,也就是像蠕蟲一樣可以自我複製,利用 XSS 去幫受害者發文,這樣就會也更多人看到貼文遭受到攻擊</span> ---- ### Self-XSS - <span class="fragment">有兩種解釋:</span> - <span class="fragment">自己攻擊自己,例如打開網頁的開發者工具,然後自己貼上 JavaScript 程式碼,就是一種 self-xss ,有些網站會特別在開發者工具中警告你不要這樣做</span> <span class="fragment">![image](https://hackmd.io/_uploads/S1TbW_0J1x.png =50%x)</span> ---- - <span class="fragment">只能攻擊到自己的 XSS ,前面所提的 XSS 都是攻擊別人用的,因為別人看得到你的 payload </span> - <span class="fragment">但有些時候只有自己看得到,假設電話號碼欄位有 XSS 漏洞,但電話號碼是個人隱私資料,只有在你自己的設定頁面看得到,別人看不到,這種狀況就是 self-xss,看起來無用,但跟其他漏洞串接後,有可能別人就看得到了</span> ---- ### Blind XSS - <span class="fragment">XSS 在你看不到到地方以及不知道的時間點被執行了</span> - <span class="fragment">把 XSS payload 的內容從 alert() 改成一個傳送封包的 payload,例如 fetch(’https://test.huli.tw/xss’),如此當 XSS 在看不見得地方觸發時,就會發送一個請求到攻擊者的 server ,能夠從 server 觀察到</span> - <span class="fragment">曾有一個實際案例是攻擊者在 Shopify 的商家後台新增一名員工,在商家後台沒有觸發,但在內部後台卻觸發了,最後得到賞金</span> ---- ### 問題回顧 1. <span style="font-size:0.8em">XSS 的全名是什麼?為什麼不叫 CSS?</span> 2. <span style="font-size:0.8em">什麼是 Self-XSS?請舉出一個例子。</span> 3. <span style="font-size:0.8em">當一個網站發生 XSS 漏洞時,攻擊者可以做什麼事?請舉例。</span> 4. <span style="font-size:0.8em">為什麼很多網站在改密碼時需要再輸入一次現有密碼?</span> 5. <span style="font-size:0.8em">什麼是 Blind XSS?它和傳統 XSS 有什麼不同?</span> --- ## 1-3 再多了解 XSS 一點點 - <span class="fragment">針對不同情境,攻擊者會需要調整 XSS payload 才能確保效果,例如注入點在 innerHTML 的話,用 ``<script>alert(1)</script>` 就起不了作用</span> - <span class="fragment">因此我們必須再多了解 XSS 一點點,才能知道有哪些方法可以攻擊,也才知道如何防禦</span> ---- ### 能夠執行 JavaScript 的方式 - <span class="fragment">`<script>` 標籤的缺點有二:一是很容易被 WAF 網頁用的防火牆所識別,二是在 innerHTML 的情境下不管用</span> - <span class="fragment">除了 `<script>` 也可以用其他標籤搭配 inline event handler 來執行程式碼:`<img src="x" onerror="alert(1)">`,通常這個 x 路徑不會存在,如果存在 onerror 就不會被觸發</span> - <span class="fragment">除了 onerror 以外,只要是 event handler 都是可利用的對象: `<button onlick="alert(1)"></button>拜託點我</button>`</span> ---- - <span class="fragment">只要點了按鈕就會彈出 alert,不過這種差別在於「使用者必須做一些動作」才能觸發 XSS,例如說點擊按鈕.而前面 img 的例子,使用者什麼也不用做,XSS 就會被觸發了</span> - <span class="fragment">如果想要更短的,可以用 svg 的 `onload` 事件: `<svg onload="alert(1)">`</span> - <span class="fragment">補充:在 HTML 中屬性的雙引號 `""` 不是必要的,如果內容沒有空格,拿掉也無妨,甚至連標籤跟屬性間的空格都可以用 / 取代,因此 svg 的 payload 可以寫成: `<svg/onload=alert(1)>`,不需要空格也不需要雙引號跟單引號,就能構造出一個 XSS 的 payload </span> ---- - 在 HTML 裡面有支援很多的 event handler,以下是比較常用的: - onerror - onload - onfocus - onblur - onanimationend - onclick - onmouseenter ---- - <span class="fragment">除了 on 開頭的這些 event handler 以外,還有一種方式可以執行程式碼: `<a href=javascript:void(0)>Link</a>`,這是為了讓元素點下去沒有任何反應</span> - <span class="fragment">但如果將其換成可執行的程式碼:`<a href=javascript:alert(1)>Link</a>`,點下去就會跳出彈跳視窗</span> - <span class="fragment">總結在 HTML 想要執行 JavaScript 的話基本上有以下幾種方式:</span> - <span class="fragment">`<script>` 標籤</span> - <span class="fragment">屬性中的 event handler(都會是 on 開頭)</span> - <span class="fragment">javascript: 偽協議</span> - <span class="fragment">如果想知道更多 payload 可參考書上的附加資源</span> ---- ### 不同情境的 XSS 以及防禦方式 - <span class="fragment">通常會把可以植入 payload 的地方稱為注入點,以底下這段程式碼而言:</span> <span class="fragment"> ```html <div> Hello, <span id="name"></span> </div> <script> const qs = new URLSearchParams(window.location.search) const name = qs.get('name') document.querySelector('#name').innerHTML = name </script> ``` </span> - <span class="fragment">注入點就在 `document.querySelector('#name').innerHTML = name` 這行</span> - <span class="fragment">不同的注入點會影響怎麼攻擊以及怎麼防禦,以下簡單分類出三個不同的情境</span> ---- #### 注入 HTML - <span class="fragment">這是最常見的情況,無論是上面的案例或是底下的 PHP 都一樣:</span> <span class="fragment"> ```php <?php echo "Hello, <h1>" . $_GET['name'] . '</h1>'; ?> ``` </span> - <span class="fragment">這兩種案例都是直接給你一塊空白的 HTML 讓你去操作,因此可以寫入任何想要的元素非常自由</span> - <span class="fragment">舉個例子,用 `<img src=not_exist onerror=alert(1)>` 這個非常常見的 payload 就能執行 JavaScript</span> - <span class="fragment">而防禦方法就是把使用者輸入的 `<` 跟 `>` 全部取代掉,就沒有辦法插入新的 HTML 標籤,因此也就無法做到任何事情</span> ---- #### 注入屬性 - <span class="fragment">有時會看到像這樣的程式碼:</span> <span class="fragment"> ```html <div id="content"></div> <script> const qs = new URLSearchParams(window.location.search) const clazz = qs.get('clazz') document.querySelector('#content').innerHTML = ` <div class="${clazz}"> Demo </div> ` </script> ``` </span> - <span class="fragment">輸入的內容是作為某個屬性的值,被包在屬性裡面的</span> ---- - <span class="fragment">這時候如果還是用 `<img src=not_exist onerror=alert(1)>` 就會不起作用,因為輸入的值會變成是屬性的內容,並不會被解析成新的標籤</span> - <span class="fragment">想要執行 XSS 的話,必須先跳脫這個屬性並且關閉標籤,像是這樣: `"><img src=not+exist onerror=alert(1)>` ,如此一來,整段 HTML 就會變成:</span> <span class="fragment"> ```html <div class=""><img src=not_exist onerror=alert(1)>"> Demo </div> ``` </span> ---- - <span class="fragment">跳脫屬性以後,就可以插入想要的 HTML 標籤了</span> - <span class="fragment">從此可以看出,為什麼情境很重要,如果以為 XSS 都是剛剛提的第一種情境並只處理了 `<>` 這兩個字元,在這個情境下就會失效,因為攻擊者可以不透過新的標籤來攻擊</span> - <span class="fragment">例如利用 `"tabindex=1 onfocus="alert(1)" x="` 這個完全不含有 `<>` 的 payload ,HTML 會變成:</span> <span class="fragment"> ```html <div class="" tabindex=1 onfocus="alert(1)" x=""> Demo </div> ``` </span> ---- - <span class="fragment">與新增 HTML 標籤不同,這種攻擊方式利用原本 div 標籤的 onfocus 事件來執行 XSS ,所以在做過濾的時候,除了 `<>` 以外,需要連 `'` 跟 `"` 也一起編碼</span> - <span class="fragment">另外,這也是為什麼應該要避免寫出這樣的程式碼:</span> <span class="fragment"> ```html document.querySelector('#content').innerHTML = ` <div class=${clazz}> Demo </div> ``` </span> - <span class="fragment">上面屬性沒有用 `"` 或是 `'` 包起來,因此就算我們以為有做了防護,把 `<>"'` 這些字元都做了編碼,攻擊者還是可以透過空格來新增其他屬性</span> ---- #### 注入 JavaScript - <span class="fragment">除了 HTML 以外,有些時候使用者的輸入甚至會反映在 JavaScript 裡面,例如:</span> <span class="fragment"> ```html <script> const name = "<?php echo $_GET['name'] ?>"; alert(name); </script> ``` </span> - <span class="fragment">如果單看這一段程式碼,或許有些人會以為只要編碼 `"` 就夠了,因為這樣就沒有辦法跳出字串</span> - <span class="fragment">但這樣做其實有問題,因為可以利用 `</script>` 先把標籤關掉,再注入其他的標籤等</span> ---- - <span class="fragment">這個情境還是要跟之前一樣,把 `<>"'` 都做編碼,讓攻擊者沒辦法跳脫字串</span> - <span class="fragment">但儘管如此,仍需注意如果在輸入裡面加一個換行,就會因為換行導致整段程式碼無法執行,出現 SyntaxError</span> ---- - <span class="fragment">如果是這種情況:</span> <span class="fragment"> ```html <script> const name = ` Hello, <?php echo $_GET['name'] ?> `; alert(name); </script> ``` </span> - <span class="fragment">這裡用了新的 template string 語法,此時就可以利用 `${alert(1)}` 的方式來注入 JavaScript 程式碼,達成 XSS</span> - <span class="fragment">但如果把 `<>"'` ,都做編碼就能保證一定安全嗎?不一定</span> ---- ### 問題回顧 1. <span style="font-size:0.8em">為什麼在 innerHTML 的情境下,使用 <script> 標籤無法發動 XSS 攻擊?還有什麼其他方式可以執行 JavaScript?</span> 2. <span style="font-size:0.8em">除了 <script> 標籤之外,請舉例說明一個可以利用事件處理器(如 onerror)來執行 JavaScript 的 XSS payload,並解釋其運作方式。</span> ---- 3. <span style="font-size:0.8em">當使用者輸入被注入到 HTML 內的屬性中時,攻擊者如何跳脫屬性並插入新的 HTML 標籤?請舉一個 payload 的例子。</span> 4. <span style="font-size:0.8em">為什麼不應該寫出如下的程式碼?</span> ``` <div class=${clazz}> Demo </div> ``` - <span style="font-size:0.8em">攻擊者如何利用這種程式碼發動 XSS 攻擊?</span> 5. <span style="font-size:0.8em">在 JavaScript 的 template string 中,如何利用 ${} 來達成 XSS 攻擊?請舉一個可能的範例並說明其運作原理。</span> --- ## 1-4 危險的 JavaScript:偽協議 ---- - <span class="fragment">在 `innerHTML` 的注入點裡面, `<script>` 是不會被執行的,然而可以搭配 iframe 來使用</span> - <span class="fragment">iframe 除了眾所皆知的 src 屬性可以放入網址以外,還有另一個叫做 srcdoc 的屬性,可以放入完整的 HTML 來決定 iframe 的內容,而當這個 iframe 跟當前頁面會是 same-origin ,因為 iframe 就等於是新的頁面,因此原本沒用的 `<script>` 標籤放在這邊就有用了,而且因為是屬性,所以內容可以先做編碼,意思是一樣的: `document.body.innerHTML = '<iframe srcdoc="<script>alert(1)</script>"></iframe>'`</span> - <span class="fragment">因此就算注入點是 `innerHTML` ,也能使用 `<iframe srcdoc>` 外加 `<script>` 執行程式碼</span> ---- ### 什麼是 JavaScript:偽協議 - <span class="fragment">偽協議的英文原文是 pseudo protocol ,就是虛擬碼 pseudo code 的 pseudo</span> - <span class="fragment">比起 HTTP 、 HTTPS 或是 FTP 這些「真協議」,偽協議的意思比較像是與網路無關的特殊協議,例如 mailto: 或是 tel: 也都是偽協議的一種</span> - <span class="fragment">而 javascript: 偽協議之所以特殊,就是因為可以利用它來執行 JavaScript 程式碼</span> ---- ### 哪些地方可以使用 JavaScript:偽協議? - <span class="fragment">第一個是之前提過的 href:`<a href=javascript:alert(1)>Link</a>`,只要使用者點了連結就會「XSS,啟動!」</span> - <span class="fragment">第二個是 `<iframe>` 的 src: `<iframe src=javascript:alert(1)></iframe>`,這個不需要使用者做任何操作就會觸發</span> ---- - <span class="fragment">最後, `<form>` 的 action 其實也可以放入同樣的東西, `<button>` 的 formaction 也是,而這兩者都跟 `<a>` 一樣需要點一下才會觸發:</span> <span class="fragment"> ```html <form action=javascript:alert(1)> <button>submit</button> </form> <form id=f2> </form> <button form=f2 formaction=javascript:alert(2)>submit</button> ``` </span> ---- ### 為什麼它很危險? - <span class="fragment">因為是很常被忽略的一塊,而且它的注入點在實際應用中也很常被使用到,舉個例子,如果網站上有個功能可以讓使用者在發文時填入 Youtube 影片網址,並且在文章中自動嵌入</span> - <span class="fragment">然後寫該功能的人沒有太多資安意識,就會寫成這樣: `<iframe src="<?= $youtube_url ?>" width="500" height="300"></iframe>`</span> - <span class="fragment">只要把 `javascript:alert(1)` 當作是 YouTube 網址填入,就是一個 XSS 漏洞了,就算加上網址內是否包含 `youtube.com` 的檢查</span> - <span class="fragment">也可以用 `javascript:alert(1);console.log('youtube.com')` 繞過</span> ---- - <span class="fragment">正確方式是檢查網址是否為 YouTube 影片的格式,並要確保網址是以 `https://` 開頭</span> - <span class="fragment">如果覺得這個功能不常見,那在 profile 頁面填入自己的 blog 或是 facebook 網址,並且在頁面上加個超連結,這功能就很常見了</span> - <span class="fragment">後端的實作寫成程式碼就會類似這樣: `<a href="<?php echo htmlspecialchars($data) ?>">link</a>`</span> ---- - <span class="fragment">儘管 [htmlspecialchars](https://www.php.net/manual/zh/function.htmlspecialchars.php) 會把 `<>"'&` 都做編碼,讓攻擊者沒辦法新增標籤,也沒辦法跳脫雙引號新增屬性,但攻擊者依舊可以插入 `javascript:alert(1)` ,因為這裡面沒有任何不法字元</span> - <span class="fragment">另外現在的前端框架基本上都會自動幫你做好跳脫,沒有在 React 裡面使用 dangerouslySetInnerHTML 或是 Vue 裡面使用 v-html 的話,基本上都是沒什麼問題的,但 href 就不同了,理由同上,因為它的內容是沒問題的</span> ---- - <span class="fragment">因此如果你在 React 裡面這樣寫會出事:</span> <span class="fragment"> ```react import React from 'react'; export function App(props) { // 假設底下的資料是來自於使用者 const href = 'javascript:alert(1)' return ( <a href={href}>click me</a> ); } ``` </span> - <span class="fragment">這就是一個 XSS 漏洞,一點下去就可以執行程式碼,不過 React 在 v16.9 的時候有針對這個新為新增警告,在文件裡面也有說明</span> ---- - <span class="fragment">而 Vue 的話則是可以這樣寫:</span> <span class="fragment"> ```html <script setup> import { ref } from 'vue' const link = ref('javascript:alert(1)') </script> <template> <a :href="link">click me</a> </template> ``` </span> - <span class="fragment">一樣可以成功執行 JavaScript ,而這攻擊方式在 Vue 的文件裡有提到,叫做 URL Injection ,推薦使用者在後端就應該要把 URL 做驗證以及處理,而不是等到前端才處理,如果非要在前端處理的話,有提到可以用 senitize-url 這一套 library</span> ---- ### 頁面跳轉也有風險 - <span class="fragment">有許多網站都會實作一個「登入後重新導向」的功能,把使用者導到登入前原本想造訪的頁面,像這樣:</span> <span class="fragment"> ```jsx const searchParams = new URLSearchParams(location.search) window.location = searchParams.get('redirect') ``` </span> - <span class="fragment">這樣的程式碼有個問題: `window.localtion` 的值也可以是 javascript: 偽協議</span> - <span class="fragment">執行上面這段程式碼之後,就會看到熟悉的 alert 出現,這模式是前端工程師比較需要注意的,如同作者前面講過的,重新導向本來就是一個很常見的功能,在實作的時候絕對要注意這個問題,避免寫出有問題的程式碼</span> ---- - <span class="fragment">作者之前在一個叫做 Matters News 的網站發現這個漏洞,在點下登入之後會呼叫 `redirectToTarget` ,這個函式的程式碼是這樣:</span> <span class="fragment"> ```jsx /** * Redirect to "?target=" or fallback URL with page reload. * * (works on CSR) */ export const redirectToTarget = ({ fallback = 'current', }: { fallback?: 'homepage' | 'current' } = {}) => { const fallbackTarget = fallback === 'homepage' ? `/` // FIXME: to purge cache : window.location.href const target = getTarget() || fallbackTarget window.location.href = decodeURIComponent(target) } ``` </span> ---- - <span class="fragment">在拿到 target 之後就直接使用了: `window.location.href = decodeURIComponent(target)` 來做重新導向,而 `getTarget` 其實就是去 query string 把 target 的值拿出來</span> - <span class="fragment">所以如果登入的網址是: `https://matters.news/login?target=javascript:alert(1)` ,在使用者按下登入並且成功之後,就會跳出一個 alert ,觸發 XSS</span> ---- - <span class="fragment">不僅如此,這個 XSS 一旦被觸發了,影響力非同小可,因為這是登入頁面,所以在這個頁面上執行的 XSS 可以直接抓取 input 的值,也就是偷到使用者的帳號密碼,如果要執行實際的攻擊,可以針對網站的使用者寄送釣魚信,在信中放入這個惡意連結可以讓使用者點擊,由於網址是正常網址,點擊之後到的頁面也是真的網站頁面因此可信度滿高的</span> - <span class="fragment">在使用者輸入帳號密碼之後並且登入之後,用 XSS 把帳號密碼偷走並把使用者導回首頁,就可以不留痕跡的偷走使用者帳號,達成帳號奪取</span> ---- ### 防禦方式 - <span class="fragment">首先,如果有像是上面提到的 sanitize-url 這種 library 的話是再好不過,雖然並不是百分之百沒風險,但至少比較多人用,有些問題跟繞過方式可能都已經修掉了</span> - <span class="fragment">有些人可能會想:這需求應該不難吧?自己處理就好啦,那來看看自己處理通常會發生什麼事:</span> ---- - <span class="fragment">既然攻擊的字串是 `javascript:alert(1)` ,可能會有些人想說那就判斷開頭是不是 `javascript:` 就好,或是把字串中的 `javascript` 全部移除</span> - <span class="fragment">但這樣是行不同的,因為這是 href 屬性的內容,而 HTML 裡面的屬性內容是可以經過編碼的,也就是說,可以這樣做: `<a href="&#106avascript:alert(1)">click me</a>` ,裡面完全沒有我們想過濾的內容,也不是以 `javascript:` 開頭,所以可以繞過限制</span> ---- - <span class="fragment">比較好的判斷方式是只允許 http:// 跟 https:// 開頭的字串,基本上就會有事,而有些更嚴謹的會利用 JavaScript 去解析 URL:</span> <span class="fragment"> ```jsx console.log(new URL('javascript:alert(1)')) /* { // ... href: "javascript:alert(1)", origin: "null", pathname: "alert(1)", protocol: "javascript:", } */ ``` </span> - <span class="fragment">如此就能根據 protocol 判斷是否為合法的協議,來阻擋名單之外的內容</span> ---- - <span class="fragment">而有個常見的錯誤判斷方式一樣會利用 URL 來解析,但卻是看 hostname 或是 origin 等等來檢查網址是否合法:</span> <span class="fragment"> ```jsx console.log(new URL('javascript:alert(1)')) /* { // ... hostname: "", host: "", origin: null } */ ``` </span> ---- - <span class="fragment">當 hostname 或是 host 為空的時候,就代表是不合法的網址,這樣的方式雖然乍看之下沒問題,但可以利用 `//` 或是 JavaScript 中是註解的特性,搭配換行字元來做出一個看起來像網址,但其實是 `javascript:` 偽協議的字串:</span> <span class="fragment"> ```jsx console.log(new URL('javascript://huli.tw/%0aalert(1)')) /* { // ... hostname: "", host: "", origin: null } */ ``` </span> ---- - <span class="fragment">雖然看起來像網址,但在 Chrome 上沒有問題,不會誤判,可是 Safari 就不同了,同樣的程式碼在 Safari 16.3 上面執行,輸出結果是:</span> <span class="fragment"> ```jsx console.log(new URL('javascript://huli.tw/%0aalert(1)')) /* { // ... hostname: "huli.tw", host: "huli.tw", origin: "null" } */ ``` </span> - <span class="fragment">在 Safari 上面就可以成功解析出 hostname 以及 host</span> ---- - <span style="font-size:0.8em">如果真的很想用如果真的很想用 RegExp 來判斷是不是 javascript: 偽協議的話,可以參考 React 的實作(很多 library 都用了一樣的 RegExp):</span> <span class="fragment"> ```js // A javascript: URL can contain leading C0 control or \u0020 SPACE, // and any newline or tab are filtered out as if they're not part of the URL. // https://url.spec.whatwg.org/#url-parsing // Tab or newline are defined as \r\n\t: // https://infra.spec.whatwg.org/#ascii-tab-or-newline // A C0 control is a code point in the range \u0000 NULL to \u001F // INFORMATION SEPARATOR ONE, inclusive: // https://infra.spec.whatwg.org/#c0-control-or-space /* eslint-disable max-len */ const isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i; ``` </span> - <span class="fragment">從這個正規表達式也可以看出 javascript: 的自由性,在開頭之前就可以加上一些字元,甚至在每個字串中間也可以加上無線數量的換行跟 tab,這就是為什麼要自己做判斷是很難的,因為一定要先看過 spec 才能整理出這些行為</span> ---- - <span class="fragment">除了剛剛提到的這些,其實只要加個 `target="_blank"` 就有大大的效果,因為很多瀏覽器已經處理好這個問題了</span> - <span class="fragment">在 Chrome 點了 javascript: 開頭的連結,會新開一個網址為 about:blank#blocked 的分頁,在 Firefox 會新開一個沒有網址的分頁,在 Safari 則是什麼事情都不會發生,在這三個桌面瀏覽器上都不會執行 JavaScript</span> ---- - <span class="fragment">在真實世界中,確實大多數的連結都有加上 `target="_blank"` 這個屬性,不過如果使用者是用滑鼠的中鍵,這情況可能就不一樣了,因為對瀏覽器來說滑鼠中鍵的行為(或是用 command+左鍵),跟直接點擊連結是不同的,因此如果是用滑鼠中鍵,就會執行 JavaScript</span> - <span class="fragment">因此無論如何,都應該把 root cause 修掉,而不是依賴瀏覽器的保護</span> - <span class="fragment">Telegram 實際案例查閱書籍,最後修復方式是用 URL 檢查並且確保 protocol 不是 `javascript:` </span> ---- ## 小結 - <span class="fragment">javascript: 偽協議的可怕之處在於它可以被放在 `<a>` 的 href 裡面</span> - <span class="fragment">一來這是很常見的使用情況,二來開發者容易忘記,又或是根本不知道,最後導致漏洞發生</span> - <span class="fragment">儘管多數情況超連結都是新開分頁,不會執行 javascript 程式碼,但難保有些地方行為不同(沒有加 target )或是瀏覽器較舊,以及使用其他方式開分頁,對使用者來說就是一個風險</span> - <span class="fragment">也需注意在做重新導向時需要注意 javascript: 偽協議的問題,如果沒有特別防止的話就是一個 XSS 漏洞</span> - <span class="fragment">身為開發者還是需要時時刻刻留意這些問題,並在程式碼中做適當處理,就如千古名言道:「永遠不要相信來自使用者的輸入」</span> ---- ### 問題回顧 1. <span style="font-size:0.8em">什麼是 `javascript:` 偽協議?它與其他協議(如 HTTP、FTP)有什麼不同?</span> 2. <span style="font-size:0.8em">如何利用 `javascript:` 偽協議來執行 JavaScript 程式碼。</span> 3. <span style="font-size:0.8em">在使用 `<iframe>` 標籤時,如果網站允許嵌入第三方連結,開發者應如何避免 XSS 漏洞?</span> 4. <span style="font-size:0.8em">為什麼只檢查網址是否以 `javascript:` 開頭或試圖移除 javascript 這個字串,無法有效防止 XSS 攻擊?</span> 5. <span style="font-size:0.8em">當開發者在處理頁面跳轉(如重新導向登入前的目標頁面)時,應該如何防止` javascript:` 偽協議帶來的安全風險?</span> ---- ## 第一章重點摘要 ### 1-1 瀏覽器的安全模型 - <span style="font-size:0.8em">瀏覽器的安全模型用一句話總結就是:瀏覽器不給網頁前端 JavaScript 的東西,你拿不到</span> - <span style="font-size:0.8em">瀏覽器對網頁前端 JavaScript 做了幾個安全限制:</span> - <span style="font-size:0.8em">禁止主動讀寫本機的檔案</span> - <span style="font-size:0.8em">禁止呼叫系統 API(除非瀏覽器給你,例如藍芽 API)</span> - <span style="font-size:0.8em">禁止存取其他網頁的內容(包含網址)</span> ---- ### 1-2 前端資安還是得從 XSS 開始談起才對味 #### XSS 是什麼?可以做什麼? - <span style="font-size:0.8em"> XSS 全名 Cross-site scripting,已存在超過 20 年,簡單來說 XSS 代表著攻擊者可以在其他人的網站上執行 JavaScript</span> ---- #### XSS 的來源 - <span style="font-size:0.8em">XSS 的來源就是因為直接在頁面上顯示使用者的輸入,導致使用者可以輸入一個惡意的 payload 並植入 JavaScript 程式碼</span> - <span style="font-size:0.8em">以兩個角度來看 XSS:</span> - <span style="font-size:0.8em">內容是如何被放到頁面上的:後端直接輸出,瀏覽器收到 HTML 時就已有 XSS 的 payload</span> - <span style="font-size:0.8em">Payload 有沒有被儲存:沒有被儲存時必須要讓目標去點擊帶有 XSS payload 的連結,例如透過短網址包裝,或是留言板可插入 HTML 程式碼</span> ---- #### 特殊的 XSS - <span style="font-size:0.8em">Self-XSS</span> - <span style="font-size:0.8em">自己攻擊自己:打開開發者工具,自己貼上 JavaScript 程式碼</span> - <span style="font-size:0.8em">只能攻擊到自己的 XSS:只有你自己打開的設定頁被植入 JavaScript 程式碼</span> - <span style="font-size:0.8em">Blind XSS</span> - <span style="font-size:0.8em">Blind 看不到的意思,Blind XSS 就是:XSS 在你看不到的地方以及不知道的時間點被執行了</span> - <span style="font-size:0.8em">即使不知道是否有後台,也可以透過會傳送封包的 XSS payload 在看不見的地方被觸發,並且可在 server 中觀察到</span> ---- ### 1-3 再多了解 XSS 一點點 - <span style="font-size:0.8em">能夠執行 JavaScript 的方式 - <span style="font-size:0.8em">`<script>` 標籤</span> - <span style="font-size:0.8em">屬性中的 event handler(都會是 on 開頭)</span> - <span style="font-size:0.8em">`javaScript:` 偽協議</span> ---- #### 不同情境的 XSS 以及防禦方式 - <span style="font-size:0.8em">注入 HTML</span> - <span style="font-size:0.8em">直接給你一塊空白的 HTML 讓你可以直接寫入任何元素,比如 `<img src=not_exist onerror=alert(1)>`</span> - <span style="font-size:0.8em">防禦方式:取代 `<` 跟 `>`</span> - <span style="font-size:0.8em">注入屬性</span> - <span style="font-size:0.8em">輸入的內容作為某個屬性的值,跳脫屬性關閉標籤後就可以加入元素</span> - <span style="font-size:0.8em">防禦方式:除了 `<` 跟 `>`,`"`與 `'` 也需要做編碼</span> ---- - <span style="font-size:0.8em">注入 JavaScript</span> - <span style="font-size:0.8em">使用者的輸入直接反應在 JavaScript 中,可以先利用 `</script>` 先把標籤關閉,再注入其他元素</span> - <span style="font-size:0.8em">防禦方式除了將 `<>"'` 進行編碼之外,還有換行符號,但若程式碼使用的是樣板字面值,則可以利用 `${alert(1)}` 插入 JavaScript 程式碼</span> - <span style="font-size:0.8em">`<iframe>` 的妙用:`innerHTML` 中直接插入 `<script>` 是無效的,但可以使用 `<iframe>` 的 `srcdoc` 屬性插入 `<script>` 標籤</span> ---- ### 1-4 危險的 `javascript:` 偽協議 #### 什麼是 `javascript:` 偽協議 - <span style="font-size:0.8em">偽協議英文原名:pseudo protocol</span> - <span style="font-size:0.8em">可以用來執行 JavaScript 執行碼</span> ---- #### 哪些地方可以使用 `javascript:` 協議 - <span style="font-size:0.8em">`<a>` 的 `href`</span> - <span style="font-size:0.8em">`<iframe>` 的 `src`</span> - <span style="font-size:0.8em">`<form>` 的 `action`</span> - <span style="font-size:0.8em">`<button>` 的 `formaction`</span> ---- #### 為什麼很危險? - <span style="font-size:0.8em">常被忽略的一塊</span> - <span style="font-size:0.8em">注入點在實際應用中常被使用到,比如輸入 URL 時帶入 `javascript:` 注入 JavaScript(URL Injection)</span> - <span style="font-size:0.8em">防禦方式為後端就應該要把 URL 做驗證以及處理,而不是等到前端才來處理,或是可使用第三方 library 來處理</span> ---- #### 頁面跳轉也有風險 - <span style="font-size:0.8em">`window.location` 的值也可以是 `javascript:` 偽協議</span> - <span style="font-size:0.8em">如果程式碼實作上是去 query string 取得 `window.location.href` ,那就可以在使用者帶有 Javascript 的 query string 中執行 XSS,進而竊取帳密</span> ---- #### 防禦 `javascript:` 方式 - <span style="font-size:0.8em">使用具有社群支持的第三方 library</span> - <span style="font-size:0.8em">使用許多 library 都有在使用的 RegExp</span> - <span style="font-size:0.8em">加入 `target="_blank"`(滑鼠中鍵無法避免)</span> - <span style="font-size:0.8em">`new URL()` 檢查 protocol 不是 `javascript:`</span>
{"description":"網頁前端與後端最大的不同,在於程式碼是跑在瀏覽器上面,瀏覽器負責 render HTML 、解析 CSS 並繪製、執行頁面上的 JavaScript,以網頁前端來說,它的執行環境就是瀏覽器","title":"《探索網頁前端資安宇宙》第一章","contributors":"[{\"id\":\"4bbc509b-9cfe-44a8-9646-7804ee030d7c\",\"add\":36091,\"del\":3599}]"}
    258 views