經過一番努力,精心打造的網站眼看就要部屬到正式環境了;但在網站對外之前,你有先仔細思考過你的網站安不安全嗎?在本系列文前面的旅程中,我們討論了許多語言特性、效能優化、技術選型,但如果網站的安全性不足,無法正確保護網站使用者的資料、甚至成為惡意程式的寄生處,那前面堆砌了再多美好也都成枉然。今天就讓我們一起來看看網站常見的資安問題吧。
本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!
在眾多安全性漏洞中,SQL Injection 絕對是最嚴重,也最好處理的一種安全漏洞。發生在對資料庫執行查詢句時,如果將惡意使用者給予的參數直接使用組合在查詢句上,便有機會發生。
舉個例子,假設原本某網站登入驗證的查詢句長這樣:
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
而惡意使用者輸入的參數為:
userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";
由於這邊直接將參數與查詢句做字串連接,合併後,SQL 便成為了這樣:
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
// 相當於
strSQL = "SELECT * FROM users;"
這樣一來,就形同沒有帳號密碼就可以登入,甚至可以取得整個資料庫的資料結構(SELECT * FROM sys.tables
)、任意更改、查詢資料,整個網站的機敏資料就全部外洩了。
解決方法也很簡單,只要透過參數化查詢來避免直接將參數與查詢句組合,並給予適當的輸入檢查、插入跳脫字元、嚴格設定應用程式權限,就能夠有效避免 SQL Injection 囉。
XSS(Cross-site scripting),也叫做 JavaScript Injection,是現代網站最頻繁出現的問題之一,指的是網站被惡意使用者植入了其他程式碼,通常發生在網站將使用者輸入的內容直接放到網站內容時。例如論壇、討論區等可輸入任意文字的網站,惡意使用者如果寫入 <script>
,且前端、後端都沒有針對輸入內容做字元轉換、過濾處理,直接將使用者輸入的字串當成頁面內容的話,就有可能遭到 XSS。
常見的有分成幾個類型:將惡意程式寫入 DB,等資料被讀取出來時就會執行的「儲存型 XSS」,將使用者輸入的內容直接帶回頁面上的「反射型 XSS」,以及利用 DOM 的特性,各種花式執行惡意程式的「DOM-based 型 XSS」。
儲存型及反射型都很好理解,DOM-based 型就非常精彩了;可以參考 OSWAP 整理的 XSS Filter Evasion Cheat Sheet,絕大多數的 XSS 方式,都是透過各個元素的 background-image
屬性,或是元素上的各種事件 callback;其中值得特別留意的是 SVG,由於 SVG 中可以寫入任意 HTML,還可以寫上 onload
事件,若將 SVG 當成一般圖片處理,直接當成網站內容使用,遇到惡意使用者的話,後果不堪設想;開發者們在做上傳圖片時,務必要將 SVG 過濾掉喔!
XSS 的避免方法其實也很簡單,只要在資料輸入/輸出時做好字元轉換,讓惡意程式碼不被執行,而是被解析成字元即可。
關於 XSS,網路上的說明、教材非常多,筆者建議對 XSS 有興趣的讀者,可以到 XSS Challenges 練習看看,學會怎麼攻擊,過程中自然也就能明白如何防守了。
先前我們聊過 Cookie & Session,而 CSRF(Cross Site Request Forgery) 就是一種利用 Cookie 及 Session 認證機制的攻擊手法;由於 Session 認證的其實不是使用者,而是瀏覽器,那麼只要透過網頁 DOM 元素可以跨域的機制,對已經得到認證的網站發出請求,就可以假冒使用者,取得不該取得的機敏資料。
例如某間銀行的轉帳 API URL 是這樣:
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
而惡意攻擊者如果在網站內塞入 <img />
:
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
當不知情的使用者瀏覽到惡意攻擊者的網站,<img/>
便會自動送出這個請求,如果使用者在銀行的 Session 尚未過期,這個請求很可能就會被銀行接受,最後便會在使用者本人不知情的情況下,「被」完成轉帳。
這種攻擊方式可以與前述的 XSS 相輔相成,例如在未防範 XSS 的論壇網站中植入 <img/>
,src
屬性則是取得機敏資訊的 API URL 等等。
解決的方法,主要有幾種:
csrftoken
,並檢查是否為同一個數值,如果請求來源是自家網站,驗證就會通過;反之,由於外部網站無法在程式碼中取得其他網站的 Cookie,因此無法在請求中帶上 csrftoken
。更詳細的說明 & 介紹,可以參考 Huli 的 讓我們來談談 CSRF。
JSON Hijacking 是利用現代網站大多透過 API 進行前後端資料交換的特性,只要能獲得使用者權限,並呼叫取得資料的 API,再加上改寫原生的 JavaScript 物件,就可以竊取使用者的機敏資訊。
獲得權限的部分如同 CSRF,透過 <script>
可以跨域的特性,直接使用瀏覽器使用者的 Cookie;因此惡意開發者只要在網頁上透過 <script>
呼叫取得資料的 API,當陣列資料回來時,利用 JavaScript 會直接執行的特性,完成資料竊取。
例如:
Object.prototype.__defineSetter__('user',function(obj){
for(var i in obj) {
alert(i + '=' + obj[i]);
}
});
當回傳的資料中包含 user
屬性時,由於 Setter 被透過 Object.prototype.__defineSetter__
改寫,user
物件中的值便會被全部讀取。
然而 Object.prototype.__defineSetter__
可以修改原生物件造成的問題,已經早在 ES4 就被修復了,JSON Hijacking
也因此銷聲匿跡好一陣子;但從 ES6 開始又多了 Proxy,JSON Hijacking 又再次成為可能:
<script>
<script>
Object.setPrototypeOf(
__proto__,
new Proxy(__proto__, {
has: function(target, name) {
alert(
name.replace(/./g, function(c) {
c = c.charCodeAt(0)
return String.fromCharCode(c >> 8, c & 0xff)
})
)
}
})
)
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
範例來自 Gareth Heyes - JSON hijacking for the modern web,詳細說明可以參考他的 Blog。
看起來很恐怖,那麼該如何解決呢?除了如同前述的 CSRF Token 外,許多大公司還採用了另一種有趣的解決方式。
例如讀者您可以瀏覽 Facebook 的任意頁面,並打開瀏覽器的開發者工具的 Network 面板,你會看到 Facebook 的 API 回應內容開頭為 for (;;);
,這也是利用了 <script>
引入的 JavaScript 會馬上執行的特性,將惡意攻擊者的網站卡在迴圈。
除了今天文中提到的四種常見的網站資安漏洞之外,其實一個網站還有非常多細節要考慮,例如密碼等機敏資訊的儲存不要透過明碼,針對來源IP 做流量限制防止 DOS 等等,但礙於篇幅,及筆者本身對相關知識略顯不足,就不在這篇一併說明了。但還是希望讀者們在進行網站開發時,保持資安意識,盡可能做好基本的防護措施。
以上就是今天的內容啦,明天是最後一天,一起來用經典的問題做個總整理吧!
筆者
Gary
半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。相信一切安排都是最好的路。