Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #437 from thecontrarycat/samesite-none
Browse files Browse the repository at this point in the history
Support SameSite=None Correctly
  • Loading branch information
fox-rose authored Mar 30, 2021
2 parents 69a5301 + 0488c3c commit 92641c6
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 22 deletions.
26 changes: 18 additions & 8 deletions httpobs/scanner/analyzer/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') ->
cookies-without-secure-flag-but-protected-by-hsts: Cookies don't have secure, but site uses HSTS
cookies-session-without-secure-flag-but-protected-by-hsts: Same, but session cookie
cookies-without-secure-flag: Cookies set without secure flag
cookies-samesite-flag-invalid: Cookies set with invalid SameSite value (must be either unset, Strict, or Lax)
cookies-samesite-flag-invalid: Cookies set with invalid SameSite value (must be either unset, Strict, Lax or None)
cookies-session-without-secure-flag: Session cookies lack the Secure flag
cookies-session-without-httponly-flag: Session cookies lack the HttpOnly flag
cookies-not-found: No cookies found in HTTP requests
Expand All @@ -290,6 +290,9 @@ def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') ->
expectation: test expectation
pass: whether the site's configuration met its expectation
result: short string describing the result of the test
sameSite: True if all session cookies have a valid SameSite attribute
False if any session cookie has an invalid or missing SameSite attribute
None if there are no session cookies
"""

output = {
Expand Down Expand Up @@ -335,12 +338,13 @@ def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') ->
if key.lower() == 'httponly' and getattr(cookie, 'httponly') is False:
cookie.httponly = True
elif key.lower() == 'samesite' and getattr(cookie, 'samesite') is False:
if cookie._rest[key] in (None, True) or cookie._rest[key].strip().lower() == 'lax':
samesiteVal = '' if cookie._rest[key] == None else str(cookie._rest[key])
if samesiteVal.strip().lower() == 'lax':
cookie.samesite = 'Lax'
output['sameSite'] = True
elif cookie._rest[key].strip().lower() == 'strict':
elif samesiteVal.strip().lower() == 'strict':
cookie.samesite = 'Strict'
output['sameSite'] = True
elif samesiteVal.strip().lower() == 'none':
cookie.samesite = 'None'
else:
output['result'] = only_if_worse('cookies-samesite-flag-invalid',
output['result'],
Expand All @@ -354,6 +358,11 @@ def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') ->
sessionid = any(i in cookie.name.lower() for i in ('login', 'sess'))
anticsrf = True if 'csrf' in cookie.name.lower() else False

if not cookie.secure and cookie.samesite == 'None':
output['result'] = only_if_worse('cookies-samesite-flag-invalid',
output['result'],
goodness)

if not cookie.secure and hsts:
output['result'] = only_if_worse('cookies-without-secure-flag-but-protected-by-hsts',
output['result'],
Expand Down Expand Up @@ -388,11 +397,12 @@ def cookies(reqs: dict, expectation='cookies-secure-with-httponly-sessions') ->

# Store whether or not we saw SameSite cookies, if cookies were set
if output['result'] is None:
if output['sameSite']:
output['result'] = 'cookies-secure-with-httponly-sessions-and-samesite'
else:
if any(c for c in session.cookies if c.samesite == False):
output['result'] = 'cookies-secure-with-httponly-sessions'
output['sameSite'] = False
else:
output['result'] = 'cookies-secure-with-httponly-sessions-and-samesite'
output['sameSite'] = True

# Save the cookie jar
output['data'] = jar if len(str(jar)) < 32768 else {}
Expand Down
172 changes: 158 additions & 14 deletions httpobs/tests/unittests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def test_secure_with_httponly_sessions_and_samesite(self):
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

cookie = Cookie(name='SESSIONID_SAMESITE_LAX_TRUE',
cookie = Cookie(name='SESSIONID_SAMESITE_LAX',
comment=None,
comment_url=None,
discard=False,
Expand All @@ -433,7 +433,101 @@ def test_secure_with_httponly_sessions_and_samesite(self):
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True, 'SameSite': True},
rest={'HttpOnly': True, 'SameSite': 'Lax'},
secure=True,
version=1,
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

cookie = Cookie(name='SESSIONID_SAMESITE_NONE',
comment=None,
comment_url=None,
discard=False,
domain='mozilla.com',
domain_initial_dot=False,
domain_specified='mozilla.com',
expires=None,
path='/',
path_specified='/',
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True, 'SameSite': 'None'},
secure=True,
version=1,
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

result = cookies(self.reqs)

self.assertEquals('cookies-secure-with-httponly-sessions-and-samesite', result['result'])
self.assertEquals({
'SESSIONID_SAMESITE_STRICT': {
'domain': 'mozilla.com',
'expires': None,
'httponly': True,
'max-age': None,
'path': '/',
'port': 443,
'samesite': 'Strict',
'secure': True},
'SESSIONID_SAMESITE_LAX': {
'domain': 'mozilla.com',
'expires': None,
'httponly': True,
'max-age': None,
'path': '/',
'port': 443,
'samesite': 'Lax',
'secure': True},
'SESSIONID_SAMESITE_NONE': {
'domain': 'mozilla.com',
'expires': None,
'httponly': True,
'max-age': None,
'path': '/',
'port': 443,
'samesite': 'None',
'secure': True}
},
result['data'])
self.assertTrue(result['pass'])
self.assertTrue(result['sameSite'])

def test_secure_with_httponly_sessions_and_samesite_not_awarded_if_not_all_cookies_samesite(self):
cookie = Cookie(name='SESSIONID_SAMESITE_STRICT',
comment=None,
comment_url=None,
discard=False,
domain='mozilla.com',
domain_initial_dot=False,
domain_specified='mozilla.com',
expires=None,
path='/',
path_specified='/',
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True, 'SameSite': 'Strict'},
secure=True,
version=1,
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

cookie = Cookie(name='SESSIONID_NO_SAMESITE',
comment=None,
comment_url=None,
discard=False,
domain='mozilla.com',
domain_initial_dot=False,
domain_specified='mozilla.com',
expires=None,
path='/',
path_specified='/',
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True},
secure=True,
version=1,
value='bar')
Expand All @@ -458,7 +552,7 @@ def test_secure_with_httponly_sessions_and_samesite(self):
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

cookie = Cookie(name='SESSIONID_SAMESITE_LAX_NONE',
cookie = Cookie(name='SESSIONID_SAMESITE_NONE',
comment=None,
comment_url=None,
discard=False,
Expand All @@ -471,15 +565,15 @@ def test_secure_with_httponly_sessions_and_samesite(self):
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True, 'SameSite': None},
rest={'HttpOnly': True, 'SameSite': 'None'},
secure=True,
version=1,
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

result = cookies(self.reqs)

self.assertEquals('cookies-secure-with-httponly-sessions-and-samesite', result['result'])
self.assertEquals('cookies-secure-with-httponly-sessions', result['result'])
self.assertEquals({
'SESSIONID_SAMESITE_STRICT': {
'domain': 'mozilla.com',
Expand All @@ -489,16 +583,15 @@ def test_secure_with_httponly_sessions_and_samesite(self):
'path': '/',
'port': 443,
'samesite': 'Strict',
'secure': True
},
'SESSIONID_SAMESITE_LAX_TRUE': {
'secure': True},
'SESSIONID_NO_SAMESITE': {
'domain': 'mozilla.com',
'expires': None,
'httponly': True,
'max-age': None,
'path': '/',
'port': 443,
'samesite': 'Lax',
'samesite': False,
'secure': True},
'SESSIONID_SAMESITE_LAX': {
'domain': 'mozilla.com',
Expand All @@ -508,21 +601,20 @@ def test_secure_with_httponly_sessions_and_samesite(self):
'path': '/',
'port': 443,
'samesite': 'Lax',
'secure': True
},
'SESSIONID_SAMESITE_LAX_NONE': {
'secure': True},
'SESSIONID_SAMESITE_NONE': {
'domain': 'mozilla.com',
'expires': None,
'httponly': True,
'max-age': None,
'path': '/',
'port': 443,
'samesite': 'Lax',
'samesite': 'None',
'secure': True}
},
result['data'])
self.assertTrue(result['pass'])
self.assertTrue(result['sameSite'])
self.assertFalse(result['sameSite'])

def test_anticsrf_without_samesite(self):
cookie = Cookie(name='CSRFTOKEN',
Expand Down Expand Up @@ -550,6 +642,58 @@ def test_anticsrf_without_samesite(self):
self.assertFalse(result['pass'])
self.assertFalse(result['sameSite'])

def test_samesite_invalid_empty(self):
cookie = Cookie(name='SESSIONID',
comment=None,
comment_url=None,
discard=False,
domain='mozilla.com',
domain_initial_dot=False,
domain_specified='mozilla.com',
expires=None,
path='/',
path_specified='/',
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True, 'SameSite': None},
secure=True,
version=1,
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

result = cookies(self.reqs)

self.assertEquals('cookies-samesite-flag-invalid', result['result'])
self.assertFalse(result['pass'])
self.assertIsNone(result['sameSite'])

def test_samesite_invalid_true(self):
cookie = Cookie(name='SESSIONID',
comment=None,
comment_url=None,
discard=False,
domain='mozilla.com',
domain_initial_dot=False,
domain_specified='mozilla.com',
expires=None,
path='/',
path_specified='/',
port=443,
port_specified=443,
rfc2109=False,
rest={'HttpOnly': True, 'SameSite': True},
secure=True,
version=1,
value='bar')
self.reqs['session'].cookies.set_cookie(cookie)

result = cookies(self.reqs)

self.assertEquals('cookies-samesite-flag-invalid', result['result'])
self.assertFalse(result['pass'])
self.assertIsNone(result['sameSite'])

def test_samesite_invalid(self):
cookie = Cookie(name='SESSIONID',
comment=None,
Expand Down

0 comments on commit 92641c6

Please sign in to comment.