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

Commit

Permalink
Remove X-XSS-Protection grading entirely.
Browse files Browse the repository at this point in the history
This addresses mozilla/http-observatory-website#254

To quote https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection :

> Warning: Even though this feature can protect users of older web browsers that don't yet support CSP, in some cases, XSS protection can create XSS vulnerabilities in otherwise safe websites. See the section below for more information.

The cited "section below" provides a concrete example of how the XSS filter can be harmful: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection#vulnerabilities_caused_by_xss_filtering

This PR is a very simple strawperson. It might be worth doing one of the following instead:

- Recognizing possible header values without giving them a score.
- Recognizing certain header values as reasonable, such as `block`.
- Not grading the header, but putting some sort of notice if the header was observed, e.g. "The Mozilla TLS Observatory used to grade the `X-XSS-Protection` header, but this is no longer the case. For details, see: [link here]"
  • Loading branch information
lgarron committed Nov 17, 2022
1 parent 6ac246a commit 2a2b395
Show file tree
Hide file tree
Showing 6 changed files with 0 additions and 192 deletions.
11 changes: 0 additions & 11 deletions httpobs/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,17 +483,6 @@ Example:
"result": "x-frame-options-sameorigin-or-deny",
"score_description": "X-Frame-Options (XFO) header set to SAMEORIGIN or DENY",
"score_modifier": 0
},
"x-xss-protection": {
"expectation": "x-xss-protection-1-mode-block",
"name": "x-xss-protection",
"output": {
"data": "1; mode=block"
},
"pass": true,
"result": "x-xss-protection-enabled-mode-block",
"score_description": "X-XSS-Protection header set to \"1; mode=block\"",
"score_modifier": 0
}
}
```
9 changes: 0 additions & 9 deletions httpobs/docs/scoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,3 @@ x-frame-options-sameorigin-or-deny | `X-Frame-Options` (XFO) header set to `SAME
x-frame-options-not-implemented | `X-Frame-Options` (XFO) header not implemented | -20
x-frame-options-header-invalid | `X-Frame-Options` (XFO) header cannot be recognized | -20
<br>

[X-XSS-Protection](https://infosec.mozilla.org/guidelines/web_security#x-xss-protection) | Description | Modifier
--- | --- | :---:
x-xss-protection-not-needed-due-to-csp | `X-XSS-Protection` header not needed due to strong Content Security Policy (CSP) header | 0
x-xss-protection-enabled-mode-block | `X-XSS-Protection` header set to `1; mode=block` | 0
x-xss-protection-enabled | `X-XSS-Protection` header set to `1` | 0
x-xss-protection-disabled | `X-XSS-Protection` header set to `0` (disabled) | -10
x-xss-protection-not-implemented | `X-XSS-Protection` header not implemented | -10
x-xss-protection-header-invalid | `X-XSS-Protection` header cannot be recognized | -10
86 changes: 0 additions & 86 deletions httpobs/scanner/analyzer/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,89 +833,3 @@ def x_frame_options(reqs: dict, expectation='x-frame-options-sameorigin-or-deny'

return output


@scored_test
def x_xss_protection(reqs: dict, expectation='x-xss-protection-1-mode-block') -> dict:
"""
:param reqs: dictionary containing all the request and response objects
:param expectation: test expectation
x-xss-protection-enabled-mode-block: X-XSS-Protection set to "1; block" [default]
x-xss-protection-enabled: X-XSS-Protection set to "1"
x-xss-protection-not-needed-due-to-csp: no X-XSS-Protection header, but CSP blocks inline nonsense
x-xss-protection-disabled: X-XSS-Protection set to "0" (disabled)
x-xss-protection-not-implemented: X-XSS-Protection header missing
x-xss-protection-header-invalid
:return: dictionary with:
data: the raw X-XSS-Protection header
expectation: test expectation
pass: whether the site's configuration met its expectation
result: short string describing the result of the test
"""
VALID_DIRECTIVES = ('0', '1', 'mode', 'report')
VALID_MODES = ('block',)

output = {
'data': None,
'expectation': expectation,
'pass': False,
'result': None,
}

enabled = False # XXSSP enabled or not
valid = True # XXSSP header valid or not
response = reqs['responses']['auto']
header = response.headers.get('X-XSS-Protection', '').strip()
xxssp = {}

if header:
output['data'] = header[0:256] # code defensively

# Parse out the X-XSS-Protection header
try:
if header[0] not in ('0', '1'):
raise ValueError

if header[0] == '1':
enabled = True

# {'1': None, 'mode': 'block', 'report': 'https://www.example.com/__reporturi__'}
for directive in header.lower().split(';'):
k, v = [d.strip() for d in directive.split('=')] if '=' in directive else (directive.strip(), None)

# An invalid directive, like foo=bar
if k not in VALID_DIRECTIVES:
raise ValueError

# An invalid mode, like mode=allow
if k == 'mode' and v not in VALID_MODES:
raise ValueError

# A repeated directive, such as 1; mode=block; mode=block
if k in xxssp:
raise ValueError

xxssp[k] = v
except:
output['result'] = 'x-xss-protection-header-invalid'
valid = False

if valid and enabled and xxssp.get('mode') == 'block':
output['result'] = 'x-xss-protection-enabled-mode-block'
output['pass'] = True
elif valid and enabled:
output['result'] = 'x-xss-protection-enabled'
output['pass'] = True
elif valid and not enabled:
output['result'] = 'x-xss-protection-disabled'

else:
output['result'] = 'x-xss-protection-not-implemented'

# Allow sites to skip out of having X-XSS-Protection if they implement a strong CSP policy
# Note that having an invalid XXSSP setting will still trigger, even with a good CSP policy
if valid and output['pass'] is False:
if content_security_policy(reqs)['pass']:
output['pass'] = True
output['result'] = 'x-xss-protection-not-needed-due-to-csp'

return output
26 changes: 0 additions & 26 deletions httpobs/scanner/grader/grade.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,32 +361,6 @@
'modifier': -20,
},

# X-XSS-Protection
'x-xss-protection-enabled-mode-block': {
'description': 'X-XSS-Protection header set to "1; mode=block"',
'modifier': 0,
},
'x-xss-protection-enabled': {
'description': 'X-XSS-Protection header set to "1"',
'modifier': 0,
},
'x-xss-protection-not-needed-due-to-csp': {
'description': 'X-XSS-Protection header not needed due to strong Content Security Policy (CSP) header',
'modifier': 0,
},
'x-xss-protection-disabled': {
'description': 'X-XSS-Protection header set to "0" (disabled)',
'modifier': -10,
},
'x-xss-protection-not-implemented': {
'description': 'X-XSS-Protection header not implemented',
'modifier': -10,
},
'x-xss-protection-header-invalid': {
'description': 'X-XSS-Protection header cannot be recognized',
'modifier': -10,
},

# Generic results
'html-not-parsable': {
'description': 'Claims to be html, but cannot be parsed',
Expand Down
59 changes: 0 additions & 59 deletions httpobs/tests/unittests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1349,62 +1349,3 @@ def test_enabled_via_csp(self):
self.assertEquals('x-frame-options-implemented-via-csp', result['result'])
self.assertTrue(result['pass'])


class TestXXSSProtection(TestCase):
def setUp(self):
self.reqs = empty_requests()

def tearDown(self):
self.reqs = None

def test_missing(self):
result = x_xss_protection(self.reqs)

self.assertEquals('x-xss-protection-not-implemented', result['result'])
self.assertFalse(result['pass'])

def test_header_invalid(self):
for value in ('whimsy',
'2; mode=block',
'1; mode=block; mode=block',
'1; mode=block, 1; mode=block'):
self.reqs['responses']['auto'].headers['X-XSS-Protection'] = value

result = x_xss_protection(self.reqs)

self.assertEquals('x-xss-protection-header-invalid', result['result'])
self.assertFalse(result['pass'])

def test_disabled(self):
self.reqs['responses']['auto'].headers['X-XSS-Protection'] = '0'

result = x_xss_protection(self.reqs)

self.assertEquals('x-xss-protection-disabled', result['result'])
self.assertFalse(result['pass'])

def test_enabled_noblock(self):
for value in ('1', '1 '):
self.reqs['responses']['auto'].headers['X-XSS-Protection'] = value

result = x_xss_protection(self.reqs)

self.assertEquals('x-xss-protection-enabled', result['result'])
self.assertTrue(result['pass'])

def test_enabled_block(self):
self.reqs['responses']['auto'].headers['X-XSS-Protection'] = '1; mode=block'

result = x_xss_protection(self.reqs)

self.assertEquals('x-xss-protection-enabled-mode-block', result['result'])
self.assertTrue(result['pass'])

def test_enabled_via_csp(self):
reqs = empty_requests()
set_header(reqs['responses']['auto'], 'Content-Security-Policy', "object-src 'none'; script-src 'none'")

result = x_xss_protection(reqs)

self.assertEquals('x-xss-protection-not-needed-due-to-csp', result['result'])
self.assertTrue(result['pass'])
1 change: 0 additions & 1 deletion httpobs/website/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def add_response_headers(headers=None, default_headers=None, cors=False):
'Strict-Transport-Security': 'max-age=63072000',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
}
headers.update(default_headers)

Expand Down

0 comments on commit 2a2b395

Please sign in to comment.