Skip to content

Commit

Permalink
Add more secure headers: Cache-control, Referrer-Policy, X-Content-Ty…
Browse files Browse the repository at this point in the history
…pe-Options, X-XSS-Protection
  • Loading branch information
ikus060 committed Sep 27, 2022
1 parent 626cca1 commit 2406780
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 17 deletions.
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -107,6 +107,12 @@ Professional support for Rdiffweb is available by contacting [IKUS Soft](https:/

# Changelog

## 2.4.9 (2002-09-28)

This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.

* Add `Cache-Control` and other security headers [CVE-2022-3292](https://nvd.nist.gov/vuln/detail/CVE-2022-3292)

## 2.4.8 (2022-09-26)

This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.
Expand Down
9 changes: 1 addition & 8 deletions rdiffweb/controller/dispatch.py
Expand Up @@ -30,14 +30,6 @@
from rdiffweb.core.rdw_helpers import unquote_url


def empty():
@cherrypy.expose
def handler():
return None

return handler


def poppath(*args, **kwargs):
"""
A decorator for _cp_dispatch
Expand Down Expand Up @@ -122,6 +114,7 @@ def static(path):
@cherrypy.expose
@cherrypy.tools.auth_form(on=False)
@cherrypy.tools.sessions(on=False)
@cherrypy.tools.secure_headers(on=False)
def handler(*args, **kwargs):
if cherrypy.request.method not in ('GET', 'HEAD'):
return None
Expand Down
66 changes: 66 additions & 0 deletions rdiffweb/controller/tests/test_secure_headers.py
Expand Up @@ -115,3 +115,69 @@ def test_clickjacking_defense(self):
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue('X-Frame-Options', 'DENY')

def test_no_cache(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/')
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue('Cache-control', 'no-cache')
self.assertHeaderItemValue('Cache-control', 'no-store')
self.assertHeaderItemValue('Cache-control', 'must-revalidate')
self.assertHeaderItemValue('Cache-control', 'max-age=0')
self.assertHeaderItemValue('Pragma', 'no-cache')
self.assertHeaderItemValue('Expires', '0')

def test_no_cache_with_static(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/static/default.css')
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertNoHeader('Cache-control')
self.assertNoHeader('Pragma')
self.assertNoHeader('Expires')

def test_referrer_policy(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/')
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue('Referrer-Policy', 'same-origin')

def test_nosniff(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/')
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue('X-Content-Type-Options', 'nosniff')

def test_xss_protection(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/')
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue('X-XSS-Protection', '1; mode=block')

def test_content_security_policy(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/')
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue(
'Content-Security-Policy',
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'",
)

def test_strict_transport_security(self):
# Given a POST request made to rdiffweb
# When the request is made without an origin
self.getPage('/', headers=[('X-Forwarded-Proto', 'https')])
# Then the request is accepted with 200 OK
self.assertStatus(200)
self.assertHeaderItemValue('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
57 changes: 48 additions & 9 deletions rdiffweb/tools/secure_headers.py
Expand Up @@ -31,28 +31,43 @@
http.cookies.Morsel._reserved['samesite'] = 'SameSite'


def set_headers():
def set_headers(
xfo='DENY',
no_cache=True,
referrer='same-origin',
nosniff=True,
xxp='1; mode=block',
csp="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'",
):
"""
This tool provide CSRF mitigation.
* Define X-Frame-Options = DENY
* Define Cookies SameSite=Lax
* Define Cookies Secure when https is detected
* Validate `Origin` and `Referer` on POST, PUT, PATCH, DELETE
* Define Cache-Control by default
* Define Referrer-Policy to 'same-origin'
Ref.:
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html
"""
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):
request = cherrypy.request
response = cherrypy.serving.response

# Check if Origin matches our target.
if request.method in ['POST', 'PUT', 'PATCH', 'DELETE']:
origin = request.headers.get('Origin', None)
if origin and not origin.startswith(request.base):
raise cherrypy.HTTPError(403, 'Unexpected Origin header')

response = cherrypy.serving.response
# Check if https is enabled
https = request.base.startswith('https')

# Define X-Frame-Options to avoid Clickjacking
response.headers['X-Frame-Options'] = 'DENY'
if xfo:
response.headers['X-Frame-Options'] = xfo

# Enforce security on cookies
cookie = response.cookie.get('session_id', None)
Expand All @@ -61,10 +76,34 @@ def set_headers():
# https://github.com/cherrypy/cherrypy/issues/1767
# Force SameSite to Lax
cookie['samesite'] = 'Lax'
# Check if https is enabled
https = cherrypy.request.base.startswith('https')
if https:
cookie['secure'] = 1

# Add Cache-Control to avoid storing sensible information in Browser cache.
if no_cache:
response.headers['Cache-control'] = 'no-cache, no-store, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'

# Add Referrer-Policy
if referrer:
response.headers['Referrer-Policy'] = referrer

# Add X-Content-Type-Options to avoid browser to "sniff" to content-type
if nosniff:
response.headers['X-Content-Type-Options'] = 'nosniff'

# Add X-XSS-Protection to enabled XSS protection
if xxp:
response.headers['X-XSS-Protection'] = xxp

# Add Content-Security-Policy
if csp:
response.headers['Content-Security-Policy'] = csp

# Add Strict-Transport-Security to force https use.
if https:
response.headers['Strict-Transport-Security'] = "max-age=31536000; includeSubDomains"


cherrypy.tools.secure_headers = cherrypy.Tool('before_request_body', set_headers, priority=71)

0 comments on commit 2406780

Please sign in to comment.