CSRF (Cross Site Request Forgery)跨站請求偽造, 基本上就是利用使用者session還有效的情況下, 外部網站對本網站假造提出請求 (可能是某個連結騙使用者去點擊), 於是網站就以為是使用者下達請求, 而被攻擊.
在Django中, 所有POST的request都會被Django csrf middleware檢查所保護, 只要在template的form中加上{% csrf_token %}即可.
那麼, 在Pyramid要如何做CSRF的保謢呢?
- 要使用Session
- 每個form要加上csrf token
- 利用subscriber檢查每個POST request的csrf token
使用Session
首先, 我們要使用session, 可以用Pyramid提供的UnencryptedCookieSessionFactoryConfig, v1.5版後使用SignedCookieSessionFactory, 或是使用pyramid_beaker.
在__init__.py的程式碼大概如下:
from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.session import UnencryptedCookieSessionFactoryConfig
def main(global_config, **settings):
...
session_factory = UnencryptedCookieSessionFactoryConfig(
secret=settings['session.secret'],
)
authn_policy = SessionAuthenticationPolicy()
config = Configurator(
settings=settings,
authentication_policy=authn_policy,
session_factory=session_factory,
)
...
其中宣告session_factory的session.secret是被定義在development.ini及production.ini中,設定碼如下:
session.secret = '-#20@aio_a=12$m9'
session.secret這個字串是隨意值, 用來加密, 如果這個字串改了, 使用者必須重新登入驗證.
form加上csrf token
設定好session後, 就可以用request.session.get_csrf_token()來取得session的csrf標記. 我們在每一個POST的form中, 加上隱藏的欄位”_csrf”:
<form method="post" action="...">
<input type="hidden" name="csrf_token" value="{{ request.session.get_csrf_token() }}">
...
</form>
利用subscriber檢查csrf token
接著, 我們要針對每個是POST的request, 做CSRF的檢測. 如果CSRF檢測失敗, 就發出HTTPUnauthorized的錯誤例外.
我們在project的目錄建立一個subcribers.py的檔案, 利用Pyramid的subscriber來檢查每一個request.
subcribers.py:
from pyramid.httpexceptions import HTTPUnauthorized
from pyramid.events import (
subscriber,
NewRequest,
)
@subscriber(NewRequest)
def csrf_validation(event):
if event.request.environ.get('paste.testing'): #如果是測試, 無需檢查
return
if event.request.method == 'POST':
token = event.request.POST.get('_csrf')
if token is None or token != event.request.session.get_csrf_token():
raise HTTPUnauthorized('CSRF validation failed.')
判斷如果request的method為POST, 就檢查_csrf的值是否與request.session.get_csrf_token()一樣. 如果不一樣, 表示驗證失敗.
更新:
pyramid.session.check_csrf_token()提供檢查request.params.get(‘csrf_token’)或request.headers.get(header)與session中的csrf_token的一致性. 驗證失敗會丢出pyramid.exceptions.BadCSRFToken錯誤例外.
所以上面的程式可以改寫如下:
subscribers.py:
from pyramid.events import (
subscriber,
NewRequest,
)
from pyramid.session import check_csrf_token
@subscriber(NewRequest)
def csrf_validation(event):
if event.request.environ.get('paste.testing'):
return
if event.request.method == 'POST':
check_csrf_token(event.request)
如此就完成了CSRF Protection.