Pyramid使用CSRF保護

CSRF (Cross Site Request Forgery)跨站請求偽造, 基本上就是利用使用者session還有效的情況下, 外部網站對本網站假造提出請求 (可能是某個連結騙使用者去點擊), 於是網站就以為是使用者下達請求, 而被攻擊.

在Django中, 所有POST的request都會被Django csrf middleware檢查所保護, 只要在template的form中加上{% csrf_token %}即可.

那麼, 在Pyramid要如何做CSRF的保謢呢?

  1. 要使用Session
  2. 每個form要加上csrf token
  3. 利用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.

本篇發表於 Pyramid。將永久鏈結加入書籤。

發表留言