I understand why the Referrer check for secure requests is in place. What is currently preventing cross-domain API requests is that the check is not configurable. I'm talking specifically about requests when `request.is_secure()` returns `True` and an unsafe but specifically cross-origin request is being made.

What currently exists is a simple referrer check.

```
good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    return self._reject(request, reason)
```

Let's discuss two domains, `api.project.com` and `dashboard.project.com`. Both are served over HTTPS. Both are fronted with a reverse proxy that performs TLS termination.

Inside the Django settings normally one would configure:

```python
# Read the special header set by the reverse proxy that indicates a secure request
SECURE_PROXY_SSL_HEADER = ("HTTP_FORWARDED", "https")

# Set the cookies on the root domain, allows api-dev.project.com to be configured
CSRF_COOKIE_DOMAIN = ".project.com"
SESSION_COOKIE_DOMAIN = ".project.com"

# Configure hosts we allow
ALLOWED_HOSTS = [
        "api.project.com"
]
```

Now a `POST` request comes in to the API from a user on `dashboard.project.com/posts/new`. Django sees the following:

* `POST not in ('GET', 'HEAD', 'OPTIONS', 'TRACE')`, this is an "unsafe" request
 * `request.is_secure()` is `True`
 * `Host` header is `api.project.com`
 * Referrer is `dashboard.project.com/posts/new`

At this point, the `same_origin(referer, good_referer)` check fails on [middleware/csrf.py:159](https://github.com/django/django/blob/8047e3666b0b50bb04e6f16c2a4fb21ddfd5713f/django/middleware/csrf.py#L159) and the entire request breaks. This check is not configurable and does not allow any hooks to bypass or alter behavior short of turning CSRF protection off entirely. This check is not present on non-secure requests and can cause surprise and confusion as to this new behavior when secure requests are enabled.

### What are some possible solutions?

Why not configure the Host header when sending the request?

Causes all absolute reversed URLs to use that Host header which is obviously incorrect.

What about serving behind the same reverse proxy and provide path based routing?

Path based routing is a separate discussion but it introduces cache problems and configuration problems. Nevertheless, Django should not be required to serve on the same `Host` as the originator with zero configuration.

Why not write some custom middleware that does your own CSRF checks?

Not DRY, complicated, prone to failure. Would prefer to not roll my own security code for obvious reasons.

What are you doing now to solve this issue?

Added a small piece of middleware the changes the `Referer` header to a "`good_referer`".

### Suggested Solution

Allow specific referrer hosts to be accepted via the settings. The change could be as simple as:

```
good_referers = [ "https://%s/"; % host for host in settings.ALLOWED_HOSTS ] if not any([same_origin(referer, good_referer) for good_referer in good_referers]):
        ...
```

Alternatively introduce a new setting that bypasses this secure referer check. I've outlined the common scenario where this check fails to provide any usefulness. While I believe this check provides additional security under ideal and simple use cases I don't believe Django's security should be relegated to the simple case.

I'd greatly welcome any comments or feedback on this matter, including alternative suggestions to resolving the issue that don't require making changes in Django core.

Best,

-Josh

***
[me](http://kehn.us) | [@joshkehn](https://twitter.com/joshkehn)

--
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 [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/F09FAEDA-3FF6-4339-9CC4-EF89649F31A5%40gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to