/
security.py
76 lines (62 loc) · 2.6 KB
/
security.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# -*- coding: utf-8 -*-
# rdiffweb, A web interface to rdiff-backup repositories
# Copyright (C) 2012-2021 rdiffweb contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import http.cookies
import logging
import cherrypy
from cherrypy._cptools import HandlerTool
# Define the logger
logger = logging.getLogger(__name__)
#
# Patch Morsel prior to 3.8
# Allow SameSite attribute to be define on the cookie.
#
if not http.cookies.Morsel().isReservedKey("samesite"):
http.cookies.Morsel._reserved['samesite'] = 'SameSite'
class CsrfAuth(HandlerTool):
"""
This tool provide CSRF mitigation.
* Define X-Frame-Options = DENY
* Define Cookies SameSite=Lax
* Validate `Origin` and `Referer` on POST, PUT, PATCH, DELETE
Ref.:
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html
"""
def __init__(self):
HandlerTool.__init__(self, self.run, name='csrf')
# Make sure to run before authform (priority 71)
self._priority = 71
def _setup(self):
cherrypy.request.hooks.attach('before_finalize', self._set_headers)
return super()._setup()
def _set_headers(self):
response = cherrypy.serving.response
# Define X-Frame-Options to avoid Clickjacking
response.headers['X-Frame-Options'] = 'DENY'
# Awaiting bug fix in cherrypy
# https://github.com/cherrypy/cherrypy/issues/1767
# Force SameSite to Lax
cookie = response.cookie.get('session_id', None)
if cookie:
cookie['samesite'] = 'Lax'
def run(self):
if cherrypy.request.method in ['POST', 'PUT', 'PATCH', 'DELETE']:
# Check if Origin matches our target.
origin = cherrypy.request.headers.get('Origin', None)
if origin and not origin.startswith(cherrypy.request.base):
raise cherrypy.HTTPError(403, 'Unexpected Origin header')
cherrypy.tools.csrf = CsrfAuth()