Python 2 Tutorial 第五堂(2)表單與 CSRF




目前你的 polls.urls 模組中,每個 url 都設定了 name

目前的 polls.urls


有沒有想過,如果在不同 App 的 urls 模組中,也有重複的 name 設定值該怎麼辦?實際上,你可以為每個 App 的 urls 模組設定不同的名稱空間(Namespace),來避免名稱衝突的問題發生,在接下來的練習 13 中,也要來看看如何建立一個簡易表單。

練習 13:建立 URL 名稱空間與簡易表單

在目前的 mysite/urls.py 檔案中,在 include 函式上增加 namespace='polls'

建立 URL 名稱空間


接下來,你就可以在模版中使用這個名稱空間設定,例如,修改 polls/index.html 模版:

使用名稱空間


接著要來建立一個簡易表單了,修改 polls/detail.html,如下包括 HTML 的 <form> 標籤內容:

建立簡易表單


在 polls/views.py 中增加以下內容與修改 resultsvote,讓 results 可以根據請求的 poll_id 與指定的模版檔案繪製畫面,而 results 用以取得 poll_id 更新選項結果:

處理表單請求


當然,我們必須建立 polls/results.html 模版檔案:

建立模版檔案 polls/results.html


接著你可以試著連結網站,在上頭作些投票,你應該可以看到以下結果:

練習 13 成果

簡介 CSRF

在練習 13 中看到了個 {% csrf_token %},這是什麼?CSRF 全名 Cross-Site Request Forgery,中文常翻為跨站請求攻擊或跨站偽造請求,這是利用 Web 應用程式在設計 HTTP 請求時,因為考量不周全造成的漏洞,從而進行攻擊的手法,通常是在 Web 應用程式站外的其他頁面中,包括惡意程式碼或鏈結,當使用者已通過驗證且會話(Session)未過期時,瀏覽該頁面或點選該惡意鏈結,就會造成攻擊成功的可能性。

一個 CSRF 攻擊的情境範例會像是 ...
  1. Bob 登入了 www.webapp.com,並且會話尚未過期。
  2. Bob 瀏覽了另一個頁面,這個頁面中包括了惡意駭客置入的 <img src="http://www.webapp.com/project/1/destroy">,然後 Bob 的某個專案就莫名奇妙被刪除了。
  3. Bob 瀏覽的頁面是不是跟 www.webapp.com 同一個網站並不重要,也許是在另一個論譠、Blog 或特意發給 Bob 的郵件中。
瀏覽器遇到 <img> 時,就會自動以 GET 請求 src 指定的網址,就這個情境來說,攻擊要能成立的前題,是 /project/1/destroy 這樣的請求就能刪除專案,這很顯然是 URL 設計時的不良,加上應用程式沒有在重大操作之前,進一步確認使用者身份與意圖而導致。

對於 HTTP 請求,有些人會有 GET 不安全,而 POST 比較安全的錯誤觀念,乍看這個例子好像是如此,實際上,也可以透過 POST 來發動類似的請求。例如:
<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">好康在這裡</a>

就算不點選,只要滑鼠略過圖也可以 ...
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />

就算沒有任何滑鼠操作,現在只要利用一些 JavaScript 寫些 Ajax 請求,都有可能讓這類攻擊發生 ...

先前談過,CSRF 是利用 Web 應用程式在設計 HTTP 請求時,因為考量不周全造成的漏洞,因此,防範方式就是認真思考 HTTP 請求方法之使用。單就 <form>method 允許設置的 GET 與 POST 來說,至少要想一下:
  • GET 應用於等冪(Idempotent)操作,相同請求重複多次都必須有相同結果,就 GET 而言,語義上也是「取得」資訊,因此 GET 請求不建議用於改變應用程式狀態。
  • POST 應用於非等冪操作,同樣請求重複多次,可能會產生不同結果,也就是會改變應用程式狀態。
(實際上要考量的不只有等幂性,還有請求方法是否安全(Safe),進一步地,在 REST 架構設計下,還有更多的 HTTP 請求方法(像是 PUT、DELETE 等)考量,可參考 重新認識HTTP請求方法。) 如果 GET 確實地應用於等冪操作,對於非 GET 請求,通常會用個安全代碼,在 Django 中,這可由 {% csrf_token %} 來產生:

 
避免跨站請求攻擊


當你瀏覽表單時,{% csrf_token %} 會產生隱藏欄位,當中包括了一組安全代碼,例如:

自動產生的隱藏欄位與安全代碼


Django 應用程式接受請求時,必須同時在請求中找到這組安全代碼,從而確認請求是來自於同一站上的表單。

(這個例子並不單只是如何防範 CSRF,主要想表達的是,安全其實是現代設計應用程式時應主動納入的考量,現在有不少框架也將安全納為特色之一,讓開發者不用煩惱安全防護實作時的枝微末節。)