{"id":13936369,"url":"https://github.com/twtrubiks/CSRF-tutorial","last_synced_at":"2025-07-19T21:32:28.473Z","repository":{"id":84518910,"uuid":"106297044","full_name":"twtrubiks/CSRF-tutorial","owner":"twtrubiks","description":"Use Django To Introduce CSRF and Cookies , Session 📝","archived":false,"fork":false,"pushed_at":"2018-03-27T03:24:18.000Z","size":33,"stargazers_count":50,"open_issues_count":1,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-15T12:17:03.840Z","etag":null,"topics":["cookie","csrf","django","double-cookie-submit","session","synchronizer-token-pattern"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-09T14:52:06.000Z","updated_at":"2024-09-07T12:10:27.000Z","dependencies_parsed_at":"2023-03-02T04:30:28.408Z","dependency_job_id":null,"html_url":"https://github.com/twtrubiks/CSRF-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/twtrubiks/CSRF-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FCSRF-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FCSRF-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FCSRF-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FCSRF-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/CSRF-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2FCSRF-tutorial/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266019657,"owners_count":23864916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cookie","csrf","django","double-cookie-submit","session","synchronizer-token-pattern"],"created_at":"2024-08-07T23:02:36.497Z","updated_at":"2025-07-19T21:32:23.463Z","avatar_url":"https://github.com/twtrubiks.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# CSRF-tutorial 📝\n\nUse Django To Introduce CSRF and Cookies , Session 📝\n\n* [Youtube Tutorial](https://youtu.be/J075bvFA5-Q)\n* [Youtube Tutorial -  Django 防範 CSRF 原理](https://youtu.be/0BOFeQCEfGU)\n\n## 前言\n\n大家在看這篇文篇時，建議先看我之前的 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial)，\n\n因為這篇文章算是 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial) 的後續介紹，建議大家有\n\n空的話先看一下 :smile:，本篇文章我將會說明 CSRF 並且透過 [Django](https://github.com/django/django) 簡單實作介紹，也會\n\n順便帶大家認識  Cookies 和 Session。\n\n## CSRF\n\n什麼是 CSRF？ 他可以吃嗎  :joy:\n\nCSRF 全名為 Cross Site Request Forgery 也被稱為 One-Click Attack ，又稱 跨站請求偽造，\n\n為什麼我要特別提到 CSRF ？\n\n先來思考一個問題，我們之前有介紹過 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial)，\n\n而且 CORS 預設也是不能跨站存取，那這樣還會有安全上的問題嗎？\n\n答案是有 :flushed: 就是我們現在要介紹的 CSRF :open_mouth:\n\n先說明一下 CSRF 的攻擊行為，使用者（ 受害者 ）在不知情的情況下，被其他網域\n\n借用身份來完成受害者未經同意的 HTTP request（ 也就是偽造出使用者本人發出的\n\nrequest ）。為什麼會這樣，原因是因為瀏覽器的機制，你只要發送 request 給某個\n\n網域，就會把關聯的 cookie 一起帶上去。如果使用者是登入狀態，那這個 request\n\n當然就包含了他的資訊（ 例如說 session id ），這 request 看起來就像是使用者本\n\n人發出去的。\n\n由於這裡提到了 Cookies 和 Session，不管各位懂不懂，請允許我任性的簡單介紹一下 :stuck_out_tongue_closed_eyes:\n\n## 介紹 Cookies 和 Session\n\n先簡單解釋一下 Cookies 和 Session 的不同，**Cookie 儲存在 client 端，Session 儲存在 server 端。**\n\n### Cookies\n\n最常看到的 Cookie 應用是在填寫表單，大家一定都有在網頁上填寫資料到一半（ 或\n\n曾經填寫過這個表單 ），然後不小心關掉或下一次有再進到同樣的網頁填表單時，他\n\n會幫你自動填入，這就是透過 Cookie 完成。另外一個常見的就是使用者帳號密碼的保\n\n存，下次登入時可以幫你自動填入。\n\nCookies 的特性:\n\n* Domain specific，只對同一個 domain 起作用。舉個例子，在 *.twtrubiks.com 存入的 cookie，不會出現在 *.not-twtrubiks.com。\n\n* 生命週期，預設為當你關閉瀏覽器時，這些資料就會被刪除，不過，我們可以透過 cookie 的 expires 屬性設定 `max-age` 來決定保存多久後會刪除。\n\n### Session\n\nSession 是搭配 Cookie 的一種技術，Cookie 是在 Client 端建立一個文件用來暫存一些資\n\n料或是網頁的狀態，但因為某些敏感資料存在 Client 端會有安全性問題，就是資料容易被\n\n偽造，因為 Cookie 中的所有資料在 Client 端都可以被修改 ( 雖然也可以透過加密防範 )，\n\n所以一些重要的資料就不適合放在 Cookie 中，而且 Cookie 如果資料太多也會影響傳輸效\n\n率，因此才把敏感資料或狀態儲存在 Server 端，也就是 Session 。\n\n當你瀏覽一個網頁時，Server 端會隨機產生一段字串給你，然後存在你的 Cookie 中，通常\n\n是 session id，當你下一次瀏覽時，Cookie 會帶上 session id，然後經過 Server 端的資料\n\n比對，就知道你是哪個使用者。\n\nSession 儲存方式有下列幾種:\n\n* 記憶體：MemoryStore，適合開發（ development ）時，因為會有 no cross-process caching 的問題。\n* Cookie：將 Session 存在 Cookie 中，缺點是會增加傳輸量。\n* Cache 快取：常見的有 [Redis](https://redis.io/) 或 [Memcached](https://memcached.org/)，這個方法是比較常見的方法。\n* 資料庫：速度比 Cache 慢。( [Django](https://github.com/django/django) 預設是存在 DB 中 )\n\n詳細的 Django Session 可參考 [https://docs.djangoproject.com/en/1.11/topics/http/sessions/](https://docs.djangoproject.com/en/1.11/topics/http/sessions/)\n\n## 使用 Django 介紹 CSRF 攻擊情境\n\n先介紹一下裡面的資料夾\n\ncsrf_tutorial_backed 為 backed，run 起來為 [http://127.0.0.1:8000/](http://127.0.0.1:8000/)\n\ndjango_attack_site 為模擬攻擊（ 被加料 ）的網站，run 起來為 [http://127.0.0.1:8002/](http://127.0.0.1:8002/)\n\n我們今天的主角是阿鬼 :satisfied:  阿鬼是一個管理員，管理文章評論是否可以刪除，如下圖\n\n![](https://i.imgur.com/n5PaM90.png)\n\n阿鬼登入了 [http://127.0.0.1:8000/](http://127.0.0.1:8000/)，並且阿鬼**沒登出**網站，\n\n( 登入的部份我借用 **Django admin** 幫我完成 ，我的 帳號/密碼 設定為 twtrubiks/password123 )\n\n溫馨小提醒:heart:\n\n這裡我們先把 csrf_tutorial_backed 裡面內建的防禦機制關閉，也就是先註解掉 MIDDLEWARE 中的\n\n`django.middleware.csrf.CsrfViewMiddleware`，詳細的後面會再做解釋。\n\n阿鬼此時又瀏覽了其他的網站 [http://127.0.0.1:8002/]( http://127.0.0.1:8002/)，這個網頁中包含了惡意注入的 js，\n\n（ 也有可能是一封信或網頁中的一段惡意程式 ）\n\n惡意 js 程式碼如下\n\n```html\n\u003ciframe style=\"display:none\" name=\"csrf-frame\"\u003e\u003c/iframe\u003e\n\u003cform action='http://127.0.0.1:8000/delete/' method='POST' target=\"csrf-frame\" id=\"csrf-form\"\u003e\n    \u003cinput type=\"hidden\" name=\"id\" value=\"69\"/\u003e\n    \u003cinput type=\"submit\" value=\"submit\"/\u003e\n\u003c/form\u003e\n\u003cscript\u003edocument.getElementById(\"csrf-form\").submit()\u003c/script\u003e\n```\n\n上面 js 的使用方法以及來源可參考 [example-of-silently-submitting-a-post-form-csrf](https://stackoverflow.com/questions/17940811/example-of-silently-submitting-a-post-form-csrf)\n\n這個 js 很可怕，你不點他，他都會自動幫你送出 :scream:\n\n當阿鬼點了 [http://127.0.0.1:8002/]( http://127.0.0.1:8002/) 之後，阿鬼的某個檔案（ id 為 69）就莫名奇妙的被刪除了，\n\n[http://127.0.0.1:8002/]( http://127.0.0.1:8002/) 只有一個 submit ，就算沒點，資料也被砍了\n\n![](https://i.imgur.com/wlszNK1.png)\n\n我們觀察一下 Cookie，你會發現 sessionid 在裡面，Server 端當然認為你是本人，\n\n所以合理的把檔案砍了\n\n![](https://i.imgur.com/AW4G1MH.png)\n\n但阿鬼根本不知道阿～\n\n我們來確認一下阿鬼的資料還在不在，id 為 69 的資料的確被砍了\n\n![](https://i.imgur.com/aZaprXU.png)\n\n這也是為什麼 CSRF 會被叫做 One-Click Attack 的關係，甚至可以讓你連點都不用點，\n\n你的資料就不知不覺的消失了 :scream:\n\n## 防範 CSRF 攻擊\n\n剛剛介紹了 CSRF 的攻擊行為，大家一定覺得很可怕 :grimacing:\n\n但其實不用太擔心，也是有防範的方法 :relaxed:\n\n現在大部分的 Framework 都有內建防禦 CSRF 的功能，要開啟也非常簡單。\n\n像是在 Django 中的 MIDDLEWARE 裡就有防禦機制\n\n```python\n MIDDLEWARE = [\n    ......\n    'django.middleware.csrf.CsrfViewMiddleware',\n    ......\n]\n```\n\n我們剛剛在前面模擬 CSRF 攻擊成功，就是將 `django.middleware.csrf.CsrfViewMiddleware` 註解掉，\n\n詳細原理以及步驟可參考 [https://docs.djangoproject.com/en/1.11/ref/csrf/](https://docs.djangoproject.com/en/1.11/ref/csrf/)\n\n如果把這個打開 ( Django 預設也是開啟的，而且不建議關閉 ) ，就可以成功\n\n阻擋 CSRF 攻擊，如果你再試一次剛剛的流程，這次你會發現資料沒有被刪\n\n除，並且透過 Server 端 Console 看到 CSRF token missing or incorrect （ 403 ）\n\n![](https://i.imgur.com/X5dvCc1.png)\n\n題外話，那使用者該如何保護自己避免 CSRF 攻擊呢 ？\n\n最簡單的就是每次使用完網站就登出，這樣就可以避免掉 CSRF，\n\n不過也不能把避免 CSRF 的攻擊，都交給使用者防範，因為我知道\n\n大家都和阿鬼一樣很懶不喜歡登出 :kissing:\n\n所以，Server 的防範（雖然很簡單）還是要做好 :grinning:\n\n如果看到這邊你還是不太了解，建議可以參考影片 [Youtube Tutorial](https://youtu.be/J075bvFA5-Q)\n\n## Django 防範 CSRF 原理\n\n因為認為還是有必要了解原理，所以增加這段來說明原理 :memo:\n\n* [Youtube Tutorial -  Django 防範 CSRF 原理](https://youtu.be/0BOFeQCEfGU)\n\nDjango 主要有用到下面兩個原理來防範 CSRF\n\n* Synchronizer Token Pattern ( STP )\n* Double Submit Cookie\n\n下面我將介紹 Django 防範 CSRF 的原理\n\n***Synchronizer Token Pattern ( STP )***\n\n首先，server 會產生一組隨機的 token，並且加在 form 上面，\n\n這個欄位是 hidden，名稱為 `csrfmiddlewaretoken`，如下面程式碼\n\n( `csrfmiddlewaretoken` 這個值每次都會不一樣 )\n\n```html\n\u003cform action=\"/comment/\" method=\"post\"\u003e\n        \u003cinput type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"MPCHeo4bEhSu9ivqvyb7KJbRnqiXJ7kapXY5UWqNbwBmO1LVpbHN4KgZt1KKtbMu\"\u003e\n        \u003cdiv class=\"form-group\"\u003e\n            \u003clabel class=\"control-label\" for=\"id_name\"\u003eName\u003c/label\u003e\n            \u003cinput type=\"text\" name=\"name\" maxlength=\"20\" class=\"form-control\" placeholder=\"Name\" title=\"\" required=\"\" id=\"id_name\"\u003e\u003c/div\u003e\n        \u003cdiv class=\"form-group\"\u003e\n            \u003clabel class=\"control-label\" for=\"id_text\"\u003eText\u003c/label\u003e\n            \u003cinput type=\"text\" name=\"text\" maxlength=\"200\" class=\"form-control\" placeholder=\"Text\" title=\"\" required=\"\" id=\"id_text\"\u003e\u003c/div\u003e\n        \u003cdiv class=\"form-group\"\u003e\n            \u003cbutton type=\"submit\" class=\"btn btn-success btn-product\"\u003e\n                \u003ci class=\"fa fa-check\" aria-hidden=\"true\"\u003e\u003c/i\u003e POST\n            \u003c/button\u003e\n        \u003c/div\u003e\n\u003c/form\u003e\n```\n\n***Double Submit Cookie***\n\n另一個 token 是當你成功登入時，會儲存在 client side ( 使用者端 ) 的 Cookies 中，名稱為 `csrftoken`，\n\n這個 token 是唯一的，如果登出再登入，此 token 也會不一樣。\n\n![](https://i.imgur.com/JDeRwZ2.png)\n\n接著當使用者按下 submit 時，server 會將 cookie 裡面的 `csrftoken` 以及 form 裡面的 `csrfmiddlewaretoken` 進行比對\n\n( **注意，會將  `csrftoken` 以及 `csrfmiddlewaretoken` 這兩個值解密後再比對**  )，\n\n如果相同，代表這個請求合法，否則，會返回 403 Forbidden。\n\n這裡我先解釋一下解密的部分，\n\n我們先看一下  cookie 裡面的 `csrftoken`，值為 `Bsi2Aqys1l8jUnyvSPZHAQaEAI5ucul2eAEqgYU4yARbz6O0MsvnURfMGjxhWyNm`，\n\n再看 form 裡面的 `csrfmiddlewaretoken` ，值為 `MPCHeo4bEhSu9ivqvyb7KJbRnqiXJ7kapXY5UWqNbwBmO1LVpbHN4KgZt1KKtbMu`，\n\n咦 ?  這兩個值根本不一樣啊 :confused:\n\n這就是我說的解密的部分，這兩個 token 都有進行加密，\n\n你可以將這兩個值丟進去 [csrf.py](https://github.com/django/django/blob/master/django/middleware/csrf.py#L56) 裡的 `_unsalt_cipher_token` function，\n\n你會發現結果都是 `NiwyQIwMHpT2PTqF4NGQubfigLCXUeCu` :open_mouth:\n\n如果你想玩玩看，我將 `_unsalt_cipher_token` function 放在 [decrypt_token.py](https://github.com/twtrubiks/CSRF-tutorial/blob/master/csrf_tutorial_backed/decrypt_token.py) 這邊 :relaxed:\n\n以上就是整個 Django 防範 CSRF 的原理 :smirk:\n\n更多詳細的介紹可參考 [https://docs.djangoproject.com/en/1.11/ref/csrf/#how-it-works](https://docs.djangoproject.com/en/1.11/ref/csrf/#how-it-works)\n\n## 結論\n\n這次透過 [Django](https://github.com/django/django) 介紹 CSRF，相信大家以後聽到這個名詞一定不陌生了，其實，多數框架都有\n\n防禦機制，不用太擔心，但是儘管都有內建防禦機制，我認為還是有必要了解一下什麼是 CSRF\n\n，免得以後看到或被問到的時候，產生了一堆:question::question::question::question:\n\n最後，因為文章內容很多是我去網路上查資料，自己再加以整理的，如果有介紹不清楚或有錯誤\n\n的地方，歡迎大家 issuse 給我，希望大家會喜歡，謝謝大家 :relaxed:\n\n## 執行環境\n\n* Python 3.6.2\n\n## Reference\n\n* [Same-Origin Policy, CORS and CSRF](https://hackmd.io/s/H1cY3TTYe#same-origin-policy-cors-and-csrf)\n* [TechBridge-讓我們來談談 CSRF](http://blog.techbridge.cc/2017/02/25/csrf-introduction/)\n\n## Donation\n\n如果有幫助到您，也想鼓勵我的話，歡迎請我喝一杯咖啡:laughing:\n\n![alt tag](https://i.imgur.com/LRct9xa.png)\n\n[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)\n\n## License\n\nMIT license\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2FCSRF-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2FCSRF-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2FCSRF-tutorial/lists"}