精心打造的網站眼看就要部署到正式環境了,但在對外之前,是否仔細思考過安全性?前面討論了許多語言特性、效能優化、技術選型,但若網站安全性不足,無法保護使用者資料、甚至成為惡意程式的寄生處,前面的努力都將付諸流水。本章討論常見的網站資安問題。
SQL Injection#
在眾多安全性漏洞中,SQL Injection 絕對是最嚴重,也最好處理的一種安全漏洞。發生在對資料庫執行查詢句時,如果將惡意使用者給予的參數直接使用組合在查詢句上,便有機會發生。
舉個例子,假設原本某網站登入驗證的查詢句長這樣:
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"而惡意使用者輸入的參數為:
userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";由於這邊直接將參數與查詢句做字串連接,合併後,SQL (Structured Query Language) 便成為了這樣:
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
-- 相當於
strSQL = "SELECT * FROM users;"這樣一來,就形同沒有帳號密碼就可以登入,甚至可以取得整個資料庫的資料結構(SELECT * FROM sys.tables)、任意更改、查詢資料,整個網站的機敏資料就全部外洩了。
解決方法很簡單:
- 透過參數化查詢,避免直接將參數與查詢句組合
- 適當的輸入檢查與跳脫字元插入
- 嚴格設定應用程式權限
XSS#
XSS(Cross-Site Scripting),也叫做 JavaScript Injection,是現代網站最頻繁出現的問題之一,指的是網站被惡意使用者植入了其他程式碼,通常發生在網站將使用者輸入的內容直接放到網站內容時。
例如論壇、討論區等可輸入任意文字的網站,惡意使用者如果寫入 <script>,且前端、後端都沒有針對輸入內容做字元轉換、過濾處理,直接將使用者輸入的字串當成頁面內容的話,就有可能遭到 XSS。
常見的有分成幾個類型:
- 將惡意程式寫入 DB,等資料被讀取出來時就會執行的「儲存型 XSS」
- 將使用者輸入的內容直接帶回頁面上的「反射型 XSS」
- 利用文件物件模型(DOM)的特性,各種花式執行惡意程式的「DOM-based 型 XSS」
儲存型及反射型都很好理解,DOM-based 型就非常精彩了;可以參考 OWASP 整理的 XSS Filter Evasion Cheat Sheet,絕大多數的 XSS 方式,都是透過各個元素的 background-image 屬性,或是元素上的各種事件 callback。
其中值得特別留意的是 SVG,由於 SVG 中可以寫入任意 HTML,還可以寫上
onload事件,若將 SVG 當成一般圖片處理,直接當成網站內容使用,遇到惡意使用者的話,後果不堪設想;開發者們在做上傳圖片時,務必要將 SVG 過濾掉喔!
XSS 的避免方法其實也很簡單,只要在資料輸入/輸出時做好字元轉換,讓惡意程式碼不被執行,而是被解析成字元即可。
關於 XSS,網路上的說明、教材非常多,筆者建議對 XSS 有興趣的讀者,可以到 XSS Challenges 練習看看,學會怎麼攻擊,過程中自然也就能明白如何防守了。
CSRF#
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 等等。
解決的方法,主要有幾種:
- Check Referer:伺服器檢查 Header 中的 Referer 值,也就是檢查請求的來源,如果是來自允許的網站,才會正常執行 API 的功能。
- CSRF Token:在 Cookie 及請求的資料欄位中都加上
csrftoken,並檢查是否為同一個數值,如果請求來源是自家網站,驗證就會通過;反之,由於外部網站無法在程式碼中取得其他網站的 Cookie,因此無法在請求中帶上csrftoken。 - SameSite Cookie:在 Cookie 加上 SameSite 屬性,確保 Cookie 僅能在自家網站使用。
JSON Hijacking#
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 又再次成為可能:
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);
})
);
},
})
);看起來很恐怖,那麼該如何解決呢?除了如同前述的 CSRF Token 外,許多大公司還採用了另一種有趣的解決方式。
例如讀者您可以瀏覽 Facebook 的任意頁面,並打開瀏覽器的開發者工具的 Network 面板,你會看到 Facebook 的 API 回應內容開頭為 for (;;);,這也是利用了 <script> 引入的 JavaScript 會馬上執行的特性,將惡意攻擊者的網站卡在迴圈。
結語#
除本文提到的四種常見漏洞外,網站還有許多資安細節需考慮,例如密碼等機敏資訊不以明碼儲存、針對來源 IP 做流量限制防止 DoS 等。礙於篇幅未一併說明,但仍希望讀者開發網站時保持資安意識,做好基本防護。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10228194