精心打造的網站眼看就要部署到正式環境了,但在對外之前,是否仔細思考過安全性?前面討論了許多語言特性、效能優化、技術選型,但若網站安全性不足,無法保護使用者資料、甚至成為惡意程式的寄生處,前面的努力都將付諸流水。本章討論常見的網站資安問題。

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