Hello, I've updated this 10 year old patch but some more changes are
needed. I'll target it for Django 4.0.
https://code.djangoproject.com/ticket/16010
https://github.com/django/django/pull/13829
Here are a few design decisions and questions that have come up:
1. It seems the main reason this wasn't merged 10 years ago is because the
patch didn't consider cross-domain POSTs. At the time, there was only
CSRF_COOKIE_DOMAIN to consider.
These days referer checking allows cross-domain POSTs by considering
SESSION_COOKIE_DOMAIN, CSRF_COOKIE_DOMAIN, and CSRF_TRUSTED_ORIGINS (along
with the request's host) [0]. Unfortunately, these settings only include
the domain or a wildcard for all subdomains like '*.example.com'. However,
origin checking requires including the scheme and port (if non-default).
We could add another setting CSRF_ALLOWED_ORIGINS (taking naming
inspiration from CORS_ALLOWED_ORIGINS in django-cors-headers [1]) which
would be a list of hosts, including the schema and port. For example:
CSRF_ALLOWED_ORIGINS = [
"https://example.com";,
"https://sub.example.com";,
"http://localhost:8080";,
"http://127.0.0.1:9000";,
]
Unfortunately such a name is very similar and not well differentiated from the
already existing CSRF_TRUSTED_ORIGINS setting. That setting could possibly
be deprecated as netlocs for referer checking could be parsed from
CSRF_ALLOWED_ORIGINS.
(Another possibility would be to have a Django 4.0 upgrade step be
modifying the hosts in CSRF_TRUSTED_ORIGINS to include the scheme. This
would be backward incompatible if trying to run older versions of Django
concurrently though.)
Following the pattern of django-cors-headers, another setting may be needed
to support all subdomains.
CSRF_ALLOWED_ORIGIN_REGEXES = [
r"^https://\w+\.example\.com$";,
]
However, it's less straightforward (if possible at all) to extra netlocs
from arbitrary regular expressions. I'm not sure that full regular
expression support is really needed. Perhaps it would be enough to support
asterisks in CSRF_ALLOWED_ORIGINS (e.g. '"https://*.example.com";). urlparse()
can handle that case.
2. There's also a question of backward compatibility. Since
CSRF_ALLOWED_ORIGINS would be empty by default, only same-origin requests
will be allowed unless the new settings are set. I can't think of a useful
deprecation path here, but perhaps a system check to flag an empty
CSRF_ALLOWED_ORIGINS if CSRF_TRUSTED_ORIGINS isn't empty (or if
CSRF_COOKIE_DOMAIN
or SESSION_COOKIE_DOMAIN are used) could be helpful in giving a heads up.
3. OWASP Cheat Sheet Series [2] says, "If the Origin header is not present,
verify the hostname in the Referer header matches the target origin." which
suggests to me that referer checking can be skipped if the origin header
can be verified. Agreed?
4. OWASP Cheat Sheet also has some discussion of when 'Origin' is 'null'.
I'm not sure if Django's checking needs to consider this. Maybe it would be
enough to discard a null header and fall back to referer checking.
Thanks for any feedback.
[0]
https://github.com/django/django/blob/407d3cf39cd6a62f7277e401d646a4c7e8446879/django/middleware/csrf.py#L257-L281
[1] https://github.com/adamchainz/django-cors-headers
[2]
https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#checking-the-referer-header
--
You received this message because you are subscribed to the Google Groups
"Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-developers/953b044e-655c-4edc-a4ca-31bd82bacf77n%40googlegroups.com.