├── .gitignore ├── LICENSE ├── README.md ├── csrf_tutorial_backed ├── comments │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── models.py │ ├── static │ │ └── img │ │ │ └── fb_icon.png │ ├── templates │ │ └── comments │ │ │ ├── base.html │ │ │ ├── comment.html │ │ │ └── index.html │ ├── tests.py │ └── views.py ├── csrf_tutorial │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── db.sqlite3 ├── decrypt_token.py └── manage.py └── django_attack_site ├── .gitignore ├── LICENSE ├── db.sqlite3 ├── django_tutorial ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── manage.py ├── musics ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── tests.py └── views.py └── templates └── hello_django.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | migrations/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSRF-tutorial 📝 2 | 3 | Use Django To Introduce CSRF and Cookies , Session 📝 4 | 5 | * [Youtube Tutorial](https://youtu.be/J075bvFA5-Q) 6 | * [Youtube Tutorial - Django 防範 CSRF 原理](https://youtu.be/0BOFeQCEfGU) 7 | 8 | ## 前言 9 | 10 | 大家在看這篇文篇時,建議先看我之前的 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial), 11 | 12 | 因為這篇文章算是 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial) 的後續介紹,建議大家有 13 | 14 | 空的話先看一下 :smile:,本篇文章我將會說明 CSRF 並且透過 [Django](https://github.com/django/django) 簡單實作介紹,也會 15 | 16 | 順便帶大家認識 Cookies 和 Session。 17 | 18 | ## CSRF 19 | 20 | 什麼是 CSRF? 他可以吃嗎 :joy: 21 | 22 | CSRF 全名為 Cross Site Request Forgery 也被稱為 One-Click Attack ,又稱 跨站請求偽造, 23 | 24 | 為什麼我要特別提到 CSRF ? 25 | 26 | 先來思考一個問題,我們之前有介紹過 [Same-Origin Policy and CORS Tutorial 📝](https://github.com/twtrubiks/CORS-tutorial), 27 | 28 | 而且 CORS 預設也是不能跨站存取,那這樣還會有安全上的問題嗎? 29 | 30 | 答案是有 :flushed: 就是我們現在要介紹的 CSRF :open_mouth: 31 | 32 | 先說明一下 CSRF 的攻擊行為,使用者( 受害者 )在不知情的情況下,被其他網域 33 | 34 | 借用身份來完成受害者未經同意的 HTTP request( 也就是偽造出使用者本人發出的 35 | 36 | request )。為什麼會這樣,原因是因為瀏覽器的機制,你只要發送 request 給某個 37 | 38 | 網域,就會把關聯的 cookie 一起帶上去。如果使用者是登入狀態,那這個 request 39 | 40 | 當然就包含了他的資訊( 例如說 session id ),這 request 看起來就像是使用者本 41 | 42 | 人發出去的。 43 | 44 | 由於這裡提到了 Cookies 和 Session,不管各位懂不懂,請允許我任性的簡單介紹一下 :stuck_out_tongue_closed_eyes: 45 | 46 | ## 介紹 Cookies 和 Session 47 | 48 | 先簡單解釋一下 Cookies 和 Session 的不同,**Cookie 儲存在 client 端,Session 儲存在 server 端。** 49 | 50 | ### Cookies 51 | 52 | 最常看到的 Cookie 應用是在填寫表單,大家一定都有在網頁上填寫資料到一半( 或 53 | 54 | 曾經填寫過這個表單 ),然後不小心關掉或下一次有再進到同樣的網頁填表單時,他 55 | 56 | 會幫你自動填入,這就是透過 Cookie 完成。另外一個常見的就是使用者帳號密碼的保 57 | 58 | 存,下次登入時可以幫你自動填入。 59 | 60 | Cookies 的特性: 61 | 62 | * Domain specific,只對同一個 domain 起作用。舉個例子,在 *.twtrubiks.com 存入的 cookie,不會出現在 *.not-twtrubiks.com。 63 | 64 | * 生命週期,預設為當你關閉瀏覽器時,這些資料就會被刪除,不過,我們可以透過 cookie 的 expires 屬性設定 `max-age` 來決定保存多久後會刪除。 65 | 66 | ### Session 67 | 68 | Session 是搭配 Cookie 的一種技術,Cookie 是在 Client 端建立一個文件用來暫存一些資 69 | 70 | 料或是網頁的狀態,但因為某些敏感資料存在 Client 端會有安全性問題,就是資料容易被 71 | 72 | 偽造,因為 Cookie 中的所有資料在 Client 端都可以被修改 ( 雖然也可以透過加密防範 ), 73 | 74 | 所以一些重要的資料就不適合放在 Cookie 中,而且 Cookie 如果資料太多也會影響傳輸效 75 | 76 | 率,因此才把敏感資料或狀態儲存在 Server 端,也就是 Session 。 77 | 78 | 當你瀏覽一個網頁時,Server 端會隨機產生一段字串給你,然後存在你的 Cookie 中,通常 79 | 80 | 是 session id,當你下一次瀏覽時,Cookie 會帶上 session id,然後經過 Server 端的資料 81 | 82 | 比對,就知道你是哪個使用者。 83 | 84 | Session 儲存方式有下列幾種: 85 | 86 | * 記憶體:MemoryStore,適合開發( development )時,因為會有 no cross-process caching 的問題。 87 | * Cookie:將 Session 存在 Cookie 中,缺點是會增加傳輸量。 88 | * Cache 快取:常見的有 [Redis](https://redis.io/) 或 [Memcached](https://memcached.org/),這個方法是比較常見的方法。 89 | * 資料庫:速度比 Cache 慢。( [Django](https://github.com/django/django) 預設是存在 DB 中 ) 90 | 91 | 詳細的 Django Session 可參考 [https://docs.djangoproject.com/en/1.11/topics/http/sessions/](https://docs.djangoproject.com/en/1.11/topics/http/sessions/) 92 | 93 | ## 使用 Django 介紹 CSRF 攻擊情境 94 | 95 | 先介紹一下裡面的資料夾 96 | 97 | csrf_tutorial_backed 為 backed,run 起來為 [http://127.0.0.1:8000/](http://127.0.0.1:8000/) 98 | 99 | django_attack_site 為模擬攻擊( 被加料 )的網站,run 起來為 [http://127.0.0.1:8002/](http://127.0.0.1:8002/) 100 | 101 | 我們今天的主角是阿鬼 :satisfied: 阿鬼是一個管理員,管理文章評論是否可以刪除,如下圖 102 | 103 | ![](https://i.imgur.com/n5PaM90.png) 104 | 105 | 阿鬼登入了 [http://127.0.0.1:8000/](http://127.0.0.1:8000/),並且阿鬼**沒登出**網站, 106 | 107 | ( 登入的部份我借用 **Django admin** 幫我完成 ,我的 帳號/密碼 設定為 twtrubiks/password123 ) 108 | 109 | 溫馨小提醒:heart: 110 | 111 | 這裡我們先把 csrf_tutorial_backed 裡面內建的防禦機制關閉,也就是先註解掉 MIDDLEWARE 中的 112 | 113 | `django.middleware.csrf.CsrfViewMiddleware`,詳細的後面會再做解釋。 114 | 115 | 阿鬼此時又瀏覽了其他的網站 [http://127.0.0.1:8002/]( http://127.0.0.1:8002/),這個網頁中包含了惡意注入的 js, 116 | 117 | ( 也有可能是一封信或網頁中的一段惡意程式 ) 118 | 119 | 惡意 js 程式碼如下 120 | 121 | ```html 122 | 123 |
124 | 125 | 126 |
127 | 128 | ``` 129 | 130 | 上面 js 的使用方法以及來源可參考 [example-of-silently-submitting-a-post-form-csrf](https://stackoverflow.com/questions/17940811/example-of-silently-submitting-a-post-form-csrf) 131 | 132 | 這個 js 很可怕,你不點他,他都會自動幫你送出 :scream: 133 | 134 | 當阿鬼點了 [http://127.0.0.1:8002/]( http://127.0.0.1:8002/) 之後,阿鬼的某個檔案( id 為 69)就莫名奇妙的被刪除了, 135 | 136 | [http://127.0.0.1:8002/]( http://127.0.0.1:8002/) 只有一個 submit ,就算沒點,資料也被砍了 137 | 138 | ![](https://i.imgur.com/wlszNK1.png) 139 | 140 | 我們觀察一下 Cookie,你會發現 sessionid 在裡面,Server 端當然認為你是本人, 141 | 142 | 所以合理的把檔案砍了 143 | 144 | ![](https://i.imgur.com/AW4G1MH.png) 145 | 146 | 但阿鬼根本不知道阿~ 147 | 148 | 我們來確認一下阿鬼的資料還在不在,id 為 69 的資料的確被砍了 149 | 150 | ![](https://i.imgur.com/aZaprXU.png) 151 | 152 | 這也是為什麼 CSRF 會被叫做 One-Click Attack 的關係,甚至可以讓你連點都不用點, 153 | 154 | 你的資料就不知不覺的消失了 :scream: 155 | 156 | ## 防範 CSRF 攻擊 157 | 158 | 剛剛介紹了 CSRF 的攻擊行為,大家一定覺得很可怕 :grimacing: 159 | 160 | 但其實不用太擔心,也是有防範的方法 :relaxed: 161 | 162 | 現在大部分的 Framework 都有內建防禦 CSRF 的功能,要開啟也非常簡單。 163 | 164 | 像是在 Django 中的 MIDDLEWARE 裡就有防禦機制 165 | 166 | ```python 167 | MIDDLEWARE = [ 168 | ...... 169 | 'django.middleware.csrf.CsrfViewMiddleware', 170 | ...... 171 | ] 172 | ``` 173 | 174 | 我們剛剛在前面模擬 CSRF 攻擊成功,就是將 `django.middleware.csrf.CsrfViewMiddleware` 註解掉, 175 | 176 | 詳細原理以及步驟可參考 [https://docs.djangoproject.com/en/1.11/ref/csrf/](https://docs.djangoproject.com/en/1.11/ref/csrf/) 177 | 178 | 如果把這個打開 ( Django 預設也是開啟的,而且不建議關閉 ) ,就可以成功 179 | 180 | 阻擋 CSRF 攻擊,如果你再試一次剛剛的流程,這次你會發現資料沒有被刪 181 | 182 | 除,並且透過 Server 端 Console 看到 CSRF token missing or incorrect ( 403 ) 183 | 184 | ![](https://i.imgur.com/X5dvCc1.png) 185 | 186 | 題外話,那使用者該如何保護自己避免 CSRF 攻擊呢 ? 187 | 188 | 最簡單的就是每次使用完網站就登出,這樣就可以避免掉 CSRF, 189 | 190 | 不過也不能把避免 CSRF 的攻擊,都交給使用者防範,因為我知道 191 | 192 | 大家都和阿鬼一樣很懶不喜歡登出 :kissing: 193 | 194 | 所以,Server 的防範(雖然很簡單)還是要做好 :grinning: 195 | 196 | 如果看到這邊你還是不太了解,建議可以參考影片 [Youtube Tutorial](https://youtu.be/J075bvFA5-Q) 197 | 198 | ## Django 防範 CSRF 原理 199 | 200 | 因為認為還是有必要了解原理,所以增加這段來說明原理 :memo: 201 | 202 | * [Youtube Tutorial - Django 防範 CSRF 原理](https://youtu.be/0BOFeQCEfGU) 203 | 204 | Django 主要有用到下面兩個原理來防範 CSRF 205 | 206 | * Synchronizer Token Pattern ( STP ) 207 | * Double Submit Cookie 208 | 209 | 下面我將介紹 Django 防範 CSRF 的原理 210 | 211 | ***Synchronizer Token Pattern ( STP )*** 212 | 213 | 首先,server 會產生一組隨機的 token,並且加在 form 上面, 214 | 215 | 這個欄位是 hidden,名稱為 `csrfmiddlewaretoken`,如下面程式碼 216 | 217 | ( `csrfmiddlewaretoken` 這個值每次都會不一樣 ) 218 | 219 | ```html 220 |
221 | 222 |
223 | 224 |
225 |
226 | 227 |
228 |
229 | 232 |
233 |
234 | ``` 235 | 236 | ***Double Submit Cookie*** 237 | 238 | 另一個 token 是當你成功登入時,會儲存在 client side ( 使用者端 ) 的 Cookies 中,名稱為 `csrftoken`, 239 | 240 | 這個 token 是唯一的,如果登出再登入,此 token 也會不一樣。 241 | 242 | ![](https://i.imgur.com/JDeRwZ2.png) 243 | 244 | 接著當使用者按下 submit 時,server 會將 cookie 裡面的 `csrftoken` 以及 form 裡面的 `csrfmiddlewaretoken` 進行比對 245 | 246 | ( **注意,會將 `csrftoken` 以及 `csrfmiddlewaretoken` 這兩個值解密後再比對** ), 247 | 248 | 如果相同,代表這個請求合法,否則,會返回 403 Forbidden。 249 | 250 | 這裡我先解釋一下解密的部分, 251 | 252 | 我們先看一下 cookie 裡面的 `csrftoken`,值為 `Bsi2Aqys1l8jUnyvSPZHAQaEAI5ucul2eAEqgYU4yARbz6O0MsvnURfMGjxhWyNm`, 253 | 254 | 再看 form 裡面的 `csrfmiddlewaretoken` ,值為 `MPCHeo4bEhSu9ivqvyb7KJbRnqiXJ7kapXY5UWqNbwBmO1LVpbHN4KgZt1KKtbMu`, 255 | 256 | 咦 ? 這兩個值根本不一樣啊 :confused: 257 | 258 | 這就是我說的解密的部分,這兩個 token 都有進行加密, 259 | 260 | 你可以將這兩個值丟進去 [csrf.py](https://github.com/django/django/blob/master/django/middleware/csrf.py#L56) 裡的 `_unsalt_cipher_token` function, 261 | 262 | 你會發現結果都是 `NiwyQIwMHpT2PTqF4NGQubfigLCXUeCu` :open_mouth: 263 | 264 | 如果你想玩玩看,我將 `_unsalt_cipher_token` function 放在 [decrypt_token.py](https://github.com/twtrubiks/CSRF-tutorial/blob/master/csrf_tutorial_backed/decrypt_token.py) 這邊 :relaxed: 265 | 266 | 以上就是整個 Django 防範 CSRF 的原理 :smirk: 267 | 268 | 更多詳細的介紹可參考 [https://docs.djangoproject.com/en/1.11/ref/csrf/#how-it-works](https://docs.djangoproject.com/en/1.11/ref/csrf/#how-it-works) 269 | 270 | ## 結論 271 | 272 | 這次透過 [Django](https://github.com/django/django) 介紹 CSRF,相信大家以後聽到這個名詞一定不陌生了,其實,多數框架都有 273 | 274 | 防禦機制,不用太擔心,但是儘管都有內建防禦機制,我認為還是有必要了解一下什麼是 CSRF 275 | 276 | ,免得以後看到或被問到的時候,產生了一堆:question::question::question::question: 277 | 278 | 最後,因為文章內容很多是我去網路上查資料,自己再加以整理的,如果有介紹不清楚或有錯誤 279 | 280 | 的地方,歡迎大家 issuse 給我,希望大家會喜歡,謝謝大家 :relaxed: 281 | 282 | ## 執行環境 283 | 284 | * Python 3.6.2 285 | 286 | ## Reference 287 | 288 | * [Same-Origin Policy, CORS and CSRF](https://hackmd.io/s/H1cY3TTYe#same-origin-policy-cors-and-csrf) 289 | * [TechBridge-讓我們來談談 CSRF](http://blog.techbridge.cc/2017/02/25/csrf-introduction/) 290 | 291 | ## Donation 292 | 293 | 如果有幫助到您,也想鼓勵我的話,歡迎請我喝一杯咖啡:laughing: 294 | 295 | ![alt tag](https://i.imgur.com/LRct9xa.png) 296 | 297 | [贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8) 298 | 299 | ## License 300 | 301 | MIT license 302 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/csrf_tutorial_backed/comments/__init__.py -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/admin.py: -------------------------------------------------------------------------------- 1 | # Register your models here. 2 | from django.contrib import admin 3 | 4 | from comments.models import Comment 5 | 6 | admin.site.register(Comment) 7 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | name = 'comments' 6 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import Comment 4 | 5 | 6 | class CommentForm(forms.ModelForm): 7 | name = forms.CharField( 8 | required=True, 9 | max_length=20 10 | ) 11 | 12 | text = forms.CharField( 13 | required=True, 14 | max_length=200 15 | ) 16 | 17 | class Meta: 18 | model = Comment 19 | fields = ('name', 'text') 20 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | # Create your models here. 5 | class Comment(models.Model): 6 | name = models.TextField(max_length=20) 7 | text = models.TextField(max_length=200) 8 | created = models.DateTimeField(auto_now_add=True) 9 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/static/img/fb_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/csrf_tutorial_backed/comments/static/img/fb_icon.png -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/templates/comments/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load bootstrap3 %} 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %} 8 | 9 | 10 | {% bootstrap_css %} 11 | {% bootstrap_javascript %} 12 | 13 | 14 | 15 | 16 | 34 | 35 |
36 | {% block content %} 37 | {% endblock %} 38 |
39 | 40 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/templates/comments/comment.html: -------------------------------------------------------------------------------- 1 | {% extends "Comments/base.html" %} 2 | {% load bootstrap3 %} 3 | {% load staticfiles %} 4 | 5 | {% block content %} 6 |
7 | {% csrf_token %} 8 | {% bootstrap_form form %} 9 | {% buttons %} 10 | 13 | {% endbuttons %} 14 |
15 | 16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/templates/comments/index.html: -------------------------------------------------------------------------------- 1 | {% extends "Comments/base.html" %} 2 | {% load bootstrap3 %} 3 | {% load staticfiles %} 4 | 5 | {% block content %} 6 | {% for comment in comments %} 7 |
8 |
9 | 10 |
11 |
12 |

{{ comment.name }} - id:{{ comment.id }} 13 | Posted on {{ comment.created }} 14 | 16 | 17 | 18 |

19 |

{{ comment.text }}

20 |
21 |
22 |
23 | {% endfor %} 24 | 25 | 26 | 73 | 74 | {% endblock %} -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/comments/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render, get_object_or_404, redirect 3 | 4 | from .forms import CommentForm 5 | from .models import Comment 6 | 7 | 8 | @login_required(login_url='/admin/') 9 | def index(request): 10 | comments_list = Comment.objects.order_by('-created') 11 | return render(request, 12 | 'Comments/index.html', { 13 | 'comments': comments_list 14 | }) 15 | 16 | 17 | @login_required(login_url='/admin/') 18 | def comment(request): 19 | if request.method == 'POST': 20 | form = CommentForm(request.POST) 21 | if form.is_valid(): 22 | form.save() 23 | return redirect('index') 24 | 25 | else: 26 | form = CommentForm() 27 | 28 | return render(request, 29 | 'Comments/comment.html', { 30 | 'form': form 31 | }) 32 | 33 | 34 | @login_required(login_url='/admin/') 35 | def delete_comment(request): 36 | if request.method == 'POST': 37 | comment_id = request.POST['id'] 38 | comment_data = get_object_or_404(Comment, id=comment_id) 39 | comment_data.delete() 40 | return redirect('index') 41 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/csrf_tutorial/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/csrf_tutorial_backed/csrf_tutorial/__init__.py -------------------------------------------------------------------------------- /csrf_tutorial_backed/csrf_tutorial/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for csrf_tutorial project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = 'a)mk2q)$+n0!#&mm(f&_!9h4j!7mjof^g^j6gc4qth3n68wr69' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | ALLOWED_HOSTS = ['*'] 28 | 29 | # Application definition 30 | 31 | INSTALLED_APPS = [ 32 | 'django.contrib.admin', 33 | 'django.contrib.auth', 34 | 'django.contrib.contenttypes', 35 | 'django.contrib.sessions', 36 | 'django.contrib.messages', 37 | 'django.contrib.staticfiles', 38 | 'bootstrap3', 39 | 'comments', 40 | ] 41 | 42 | MIDDLEWARE = [ 43 | 'django.middleware.security.SecurityMiddleware', 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'django.middleware.common.CommonMiddleware', 46 | # 'django.middleware.csrf.CsrfViewMiddleware', 47 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 48 | 'django.contrib.messages.middleware.MessageMiddleware', 49 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 50 | ] 51 | 52 | ROOT_URLCONF = 'csrf_tutorial.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | 'django.template.context_processors.debug', 62 | 'django.template.context_processors.request', 63 | 'django.contrib.auth.context_processors.auth', 64 | 'django.contrib.messages.context_processors.messages', 65 | ], 66 | }, 67 | }, 68 | ] 69 | 70 | WSGI_APPLICATION = 'csrf_tutorial.wsgi.application' 71 | 72 | # Database 73 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 74 | 75 | DATABASES = { 76 | 'default': { 77 | 'ENGINE': 'django.db.backends.sqlite3', 78 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 79 | } 80 | } 81 | 82 | # Password validation 83 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 84 | 85 | AUTH_PASSWORD_VALIDATORS = [ 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 97 | }, 98 | ] 99 | 100 | # Internationalization 101 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 102 | 103 | LANGUAGE_CODE = 'en-us' 104 | 105 | TIME_ZONE = 'UTC' 106 | 107 | USE_I18N = True 108 | 109 | USE_L10N = True 110 | 111 | USE_TZ = True 112 | 113 | # Static files (CSS, JavaScript, Images) 114 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 115 | 116 | STATIC_URL = '/static/' 117 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/csrf_tutorial/urls.py: -------------------------------------------------------------------------------- 1 | """csrf_tutorial URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | from django.contrib.auth.views import login, logout, logout_then_login 19 | 20 | from comments import views 21 | 22 | urlpatterns = [ 23 | url(r'^admin/', admin.site.urls), 24 | 25 | # login logout 26 | url(r'^login/$', login, name='login'), 27 | url(r'^logout/$', logout, name='logout'), 28 | 29 | url(r'^delete/', views.delete_comment, name='delete_comment'), 30 | url(r'^comment/', views.comment, name='comment'), 31 | url(r'^', views.index, name='index'), 32 | 33 | 34 | ] 35 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/csrf_tutorial/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for csrf_tutorial project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "csrf_tutorial.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/csrf_tutorial_backed/db.sqlite3 -------------------------------------------------------------------------------- /csrf_tutorial_backed/decrypt_token.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | # https://github.com/django/django/blob/master/django/middleware/csrf.py#L56 4 | 5 | CSRF_SECRET_LENGTH = 32 6 | CSRF_ALLOWED_CHARS = string.ascii_letters + string.digits 7 | 8 | token = '< csrftoken of cookie or csrfmiddlewaretoken of form>' 9 | salt = token[:CSRF_SECRET_LENGTH] 10 | token = token[CSRF_SECRET_LENGTH:] 11 | chars = CSRF_ALLOWED_CHARS 12 | pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) 13 | secret = ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok 14 | print(secret) 15 | -------------------------------------------------------------------------------- /csrf_tutorial_backed/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "csrf_tutorial.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /django_attack_site/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | migrations/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | .idea/ -------------------------------------------------------------------------------- /django_attack_site/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /django_attack_site/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/django_attack_site/db.sqlite3 -------------------------------------------------------------------------------- /django_attack_site/django_tutorial/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/django_attack_site/django_tutorial/__init__.py -------------------------------------------------------------------------------- /django_attack_site/django_tutorial/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_tutorial project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '&2y%^#rywhy^rpxd8y%a0rpt7z6ed6lt4+7s=nwqx!b5t5nr5%' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'musics' 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'django_tutorial.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [os.path.join(BASE_DIR, 'templates')] 59 | , 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'django_tutorial.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /django_attack_site/django_tutorial/urls.py: -------------------------------------------------------------------------------- 1 | """django_tutorial URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | from musics.views import hello_view 19 | 20 | urlpatterns = [ 21 | url(r'^admin/', admin.site.urls), 22 | url(r'^', hello_view), 23 | ] 24 | -------------------------------------------------------------------------------- /django_attack_site/django_tutorial/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_tutorial project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_tutorial.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /django_attack_site/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_tutorial.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /django_attack_site/musics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/django_attack_site/musics/__init__.py -------------------------------------------------------------------------------- /django_attack_site/musics/admin.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/django_attack_site/musics/admin.py -------------------------------------------------------------------------------- /django_attack_site/musics/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MusicsConfig(AppConfig): 5 | name = 'musics' 6 | -------------------------------------------------------------------------------- /django_attack_site/musics/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twtrubiks/CSRF-tutorial/e627faf78dd74643778b8a1a25fcc11461752e30/django_attack_site/musics/models.py -------------------------------------------------------------------------------- /django_attack_site/musics/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /django_attack_site/musics/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | # Create your views here. 5 | def hello_view(request): 6 | return render(request, 'hello_django.html') 7 | -------------------------------------------------------------------------------- /django_attack_site/templates/hello_django.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------