-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Fixed #24496 -- Checked CSRF Referer against CSRF_COOKIE_DOMAIN. #4337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,14 +14,17 @@ | |
from django.utils.cache import patch_vary_headers | ||
from django.utils.crypto import constant_time_compare, get_random_string | ||
from django.utils.encoding import force_text | ||
from django.utils.http import same_origin | ||
from django.utils.http import is_same_domain | ||
from django.utils.six.moves.urllib.parse import urlparse | ||
|
||
logger = logging.getLogger('django.request') | ||
|
||
REASON_NO_REFERER = "Referer checking failed - no Referer." | ||
REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." | ||
REASON_NO_CSRF_COOKIE = "CSRF cookie not set." | ||
REASON_BAD_TOKEN = "CSRF token missing or incorrect." | ||
REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed." | ||
REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while host is secure." | ||
|
||
CSRF_KEY_LENGTH = 32 | ||
|
||
|
@@ -154,9 +157,28 @@ def process_view(self, request, callback, callback_args, callback_kwargs): | |
if referer is None: | ||
return self._reject(request, REASON_NO_REFERER) | ||
|
||
# Note that request.get_host() includes the port. | ||
good_referer = 'https://%s/' % request.get_host() | ||
if not same_origin(referer, good_referer): | ||
referer = urlparse(referer) | ||
|
||
# Make sure we have a valid URL for Referer. | ||
if '' in (referer.scheme, referer.netloc): | ||
return self._reject(request, REASON_MALFORMED_REFERER) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing tests for the two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For lols, I added expanded the tests for the |
||
|
||
# Ensure that our Referer is also secure. | ||
if referer.scheme != 'https': | ||
return self._reject(request, REASON_INSECURE_REFERER) | ||
|
||
# If there isn't a CSRF_COOKIE_DOMAIN, assume we need an exact | ||
# match on host:port. If not, obey the cookie rules. | ||
if settings.CSRF_COOKIE_DOMAIN is None: | ||
# request.get_host() includes the port. | ||
good_referer = request.get_host() | ||
else: | ||
good_referer = settings.CSRF_COOKIE_DOMAIN | ||
server_port = request.META['SERVER_PORT'] | ||
if server_port not in ('443', '80'): | ||
good_referer = '%s:%s' % (good_referer, server_port) | ||
|
||
if not is_same_domain(referer.netloc, good_referer): | ||
reason = REASON_BAD_REFERER % (referer, good_referer) | ||
return self._reject(request, reason) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -257,9 +257,14 @@ The CSRF protection is based on the following things: | |
due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted | ||
by clients that are talking to a site under HTTPS. (Referer checking is not | ||
done for HTTP requests because the presence of the Referer header is not | ||
reliable enough under HTTP.) | ||
|
||
This ensures that only forms that have originated from your Web site can be used | ||
reliable enough under HTTP.) The referer is compared against the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one space between sentences please |
||
:setting:`CSRF_COOKIE_DOMAIN` setting. The :setting:`CSRF_COOKIE_DOMAIN` | ||
setting supports subdomains. For example, ".example.com" will allow | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you could add double backticks around the URL strings, that'll fix the spelling error for "api". |
||
post requests from "www.example.com" and "api.example.com." If | ||
:setting:`CSRF_COOKIE_DOMAIN` is not set, the referer must match the | ||
``Host`` header. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need a ``.. versionchanged:: 1.9` block below this paragraph to describe what changed as well as a mention in the 1.9 release notes. |
||
|
||
This ensures that only forms that have originated from trusted domains can be used | ||
to POST data back. | ||
|
||
It deliberately ignores GET requests (and other requests that are defined as | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting:
same_origin
was literally only used inside here, for all of the codebase.But since it's technically sorta part of the public API of the
django.utils.http
package, I left it. Not sure if it's just safe to remove this function or not.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be fine to remove as it's not documented and GitHub search didn't reveal any uses in the wild.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some uses on GH: https://github.com/search?q=from+django.utils.http+import+same_origin&ref=reposearch&type=Code&utf8=%E2%9C%93
But it's definitely not documented in the Django docs.
I assume we can remove it and it'd be worth adding a note into the documentation that it was removed and was undocumented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I looked, all those uses are copies of Django itself.