[Django] #34855: Documenting CSRF_TRUSTED_ORIGINS relation to SECURE_PROXY_SSL_HEADER could prevent misconfiguration when proxying HTTPs over HTTP

22 views
Skip to first unread message

Django

unread,
Sep 20, 2023, 2:52:13 AM9/20/23
to django-...@googlegroups.com
#34855: Documenting CSRF_TRUSTED_ORIGINS relation to SECURE_PROXY_SSL_HEADER could
prevent misconfiguration when proxying HTTPs over HTTP
------------------------------------------------+------------------------
Reporter: jeroenmuller | Owner: nobody
Type: Cleanup/optimization | Status: new
Component: Documentation | Version: 4.2
Severity: Normal | Keywords: CSRF
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 1
UI/UX: 0 |
------------------------------------------------+------------------------
When the CSRF origin check fails, the documentation points in the
direction of adding that origin to CSRF_ALLOWED_ORIGINS. However, as far
as I understand this should only be neccessary if there are actually
cross-origin requests. In practice the CSRF origin check may also fail
when is proxying HTTPS traffic over HTTP without setting up
SECURE_PROXY_SSL_HEADER accordingly. This problem may occur suddenly when
upgrading to Django 4.0, because this version turns on the origin
verification in the CSRF middleware. In that case the Django release notes
point in the direction of adding the failing origin to
CSRF_ALLOWED_ORIGINS: https://docs.djangoproject.com/en/4.2/releases/4.0
/#csrf-trusted-origins-changes-4-0. But this is not the optimal solution,
and may weaken CSRF protection if a user ends up just adding a pattern
matching all subdomains or the complete ALLOWED_HOSTS lists to
CSRF_ALLOWED_ORIGINS.

It might be useful to add a note or warning below
https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-
CSRF_TRUSTED_ORIGINS explaining that it should only be neccessary to
configure this if you are actually making requests across subdomains, and
in other cases setting up SECURE_PROXY_SSL_HEADER might be a more
appropriate solution (as long as the proxy correctly sets a header like X
-Forwarded-Proto).

I ran into this problem when I updated a project hosted behind AWS ELB
load balancer and gunicorn to Django 4.0, and the CSRF origin checking for
the Django admin stopped working in a staging environment, with the
message `CSRF verification failed. Request aborted.`. I could not figure
out what was going on using the documentation and ended up finding the
root cause through a stackoverflow answer which pointed out that the
origin header is compared against a string constructed of the host,
prefixed by either http or https based on the result of
''request.is_secure()''

- https://stackoverflow.com/a/71482883

In my case, the proxy setup was forwarding requests over HTTP, so
is_secure() was false and CSRF was comparing the origin `https://subdomain
.my-domain.com` against "host" `http://subdomain.my-domain.com`. It seems
like the best solution here is to use SECURE_PROXY_SSL_HEADER to allow
Django to determine if the client is using HTTPS or not, and use the
"host" string `https://subdomain.my-domain.com` . Looking at the
referenced stackoverflow thread, this is a recurring problem for users
updating to Django 4.0 and many end up with the sub-optimal solution of
adding the origin with https to CSRF_ALLOWED_ORIGINS explicitly.

--
Ticket URL: <https://code.djangoproject.com/ticket/34855>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Reply all
Reply to author
Forward
0 new messages