django: SECURE_PROXY_SSL_HEADER requires referer - django

We have a django app accessed via SSL (i.e. with https). When we went
to the admin site and it was redirected to admin/login/?next=/admin/
because we were not logged in, the https was not carried over and the
request failed. I added
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
to my settings and then the admin redirect worked. But we have some
clients that access the site with curl or python requests and after
adding that all their existing code broke. They now all have to add a
referer to all their requests for them to work.
My question is, is there a way to make the admin redirect work but not
require all the other requests to have a referer? A non redirected
request from the browser works, and that doesn't have a referer, so
why is it required on the curl requests?

I found this:
https://code.djangoproject.com/ticket/16870
which I guess somewhat answers my question.

Related

Why Heroku redirect my site on HTTPS with no SSL setup?

I've deploy my django website on heroku. After adding my custom domain, when i try to access to http://www.example.com it redirect me to https://www.example.com and i get this message
ERR_SSL_UNRECOGNIZED_NAME_ALERT
I haven't add any SSL certificate, is it normal that i'm redirect to HTTPS?
Likely, that your browser redirects you to https, not Heroku. Try to use Google Chrome in guest mode or some other browser.
Also, you can check response of your site via python package requests or smth. It should return 200 code if your site works correctly.
import requests
resp = requests.get('http://www.example.com')
print(f"{resp.status_code=}")

Django's SECURE_SSL_REDIRECT and Heroku causes redirected Rest Framework POST requests to become GET requests

I have Django project hosted on Heroku
There's a web-application and a Django REST API.
I'm using SECURE_SSL_REDIRECT and SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') to redirect all http traffic to https.
The web-application is happy. If I try to use a http link, it redirects to https, and I can use forms, etc.
For the Django REST API, if I try a http link and a POST request, it redirects successfully to https but the method becomes a GET and the API requests the request (because it is meant to be a POST).
I do not know if this is expected behaviour, or if this is a Heroku specific thing. If it's expected for redirects, I'd appreciate to understand why (from a technical point).
And or if there is a solution.

redirect http to https safely for Heroku app

I am trying to redirect my Django + Heroku app from http to https, but I am surprised that I did not find any safe and straightforward way.
According to Heroku:
Issue
You have configured an SSL endpoint and now you want your application
to use https for all requests.
Resolution
Redirects need to be performed at the application level as the Heroku
router does not provide this functionality. You should code the
redirect logic into your application.
Under the hood, Heroku router (over)writes the X-Forwarded-Proto and
the X-Forwarded-Port request headers. The app checks X-Forwarded-Proto
and respond with a redirect response when it is not https but http.
...
Django
Set SECURE_SSL_REDIRECT to True.
So it must be done at Django. This is the most complete answer I found, and this one is also similar.
Django 1.8 will have core support for non-HTTPS redirect (integrated
from
django-secure):
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
In order for SECURE_SSL_REDIRECT to be handled you have to use the
SecurityMiddleware:
MIDDLEWARE = [
...
'django.middleware.security.SecurityMiddleware',
]
Note that both use
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
It seems that without this setting, it is not working on Heroku. And now comes the interesting/scary part. As explained in the docs:
SECURE_SSL_REDIRECT
...
If turning this to True causes infinite redirects, it probably means
your site is running behind a proxy and can’t tell which requests are
secure and which are not. Your proxy likely sets a header to indicate
secure requests; you can correct the problem by finding out what that
header is and configuring the SECURE_PROXY_SSL_HEADER setting
accordingly.
Then, checking about SECURE_PROXY_SSL_HEADER:
Warning
You will probably open security holes in your site if you set this
without knowing what you’re doing. And if you fail to set it when you
should. Seriously.
Which makes me want to find a safer solution... In this other question it says it should be fine, but I don't find it convincing enough to ignore such a warning.
Has Django really not any other solution that is safe to implement?
I am using version 1.11
Update:
I found the django-sslify package, but it also requires setting SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'), so I guess it doesn't make a difference in terms of potential security holes. Please, correct me if this assumption is wrong.
I think HTTP_X_FORWARDED_PROTO is dangerous if you use it blindly because it makes Django think you are receiving an HTTPS request (even if HTTP_X_FORWARDED_PROTO is, in fact, spoofed).
But, if you are behind a properly functioning load balance/proxy (like AWS) then you can be confident that HTTP_X_FORWARDED_PROTO is being set correctly. In this case, HTTP_X_FORWARDED_PROTO serves to tell Django that it's fine, don't worry about it (because you are trusting the proxy to not allow through a spoofed header), and stop trying to continuously redirect to SSL.
Finally, HTTP_X_FORWARDED_PROTO behind a proxy/load balancer is necessary even if you aren't using SECURE_SSL_REDIRECT = True (e.g. if the redirection is happening in a properly configured web server before getting to Django), because it also affects the is_secure() function on the request, which would always be false if your proxy is swallowing the original request (e.g. it's common to go from HTTPS between the client and your proxy/load balancer to HTTP between your proxy/load balancer and a web server).
Source: Django docs: SECURE_PROXY_SSL_HEADER

DjangoCMS: disable login via http, force https

Our DjangoCMS site is accessible via http and https.
Anonymous usage via http is ok. But I want to disable logins via http.
Is there a way to force the usage of https as soon as the user wants to login?
Even the login-page (with username and password fields) should not be available via http.
Background: I don't want the password to go over the wire unencrypted.
Update: The site gets hosted on an apache web server.
As I already mentioned it in the comments I strongly suggest you to NOT only serve the login page via https.
Doing so just hides the fact that for example session information and authentication data is still transfered on the other requests unencrypted via http. Your site will not be secure at all.
You're just pseudo-securing stuff so it's fancy to somebodys eye. It's just like using the password 12345.
So please serve your website over https to the user. A small guide, for nginx or apache2, on how to redirect your traffic from http to https can be found here:
Redirecting to SSL using nginx
Force redirect to SSL for all pages apart from one
As you've accepted in the comments, pushing all traffic to HTTPS is the best solution these days. If you only authenticate via SSL, you'll need to consider encryption/security in everything you write moving forward rather than it just being the default.
So in your server config, force all traffic to port 443. Assuming you're using apache you'd do this;
<VirtualHost *:80>
ServerName www.example.com
Redirect / https://www.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName www.example.com
# ... SSL configuration goes here
</VirtualHost>
Then in your Django settings turn on secure cookies;
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
As a side note, Django has a setting to redirect HTTP traffic to HTTPS which is SECURE_SSL_REDIRECT, but if you're doing this at the apache/nginx level you don't need to worry. For some further reading on SSL/HTTPS have a look here; https://docs.djangoproject.com/en/1.11/topics/security/#ssl-https
Django request has a is_secure()
You can check it in the view and redirect if not is secure:
if not request.is_secure():
return redirect("https://www.yourdomain.com/login")
#user1 has the logic right and you should serve your site through https in order to have it safe.
But in order to answer your exact question:
As shown here: How to know using Django if server is secure (uses https) and as #Zartch mentions in his answer, you should use the is_secure() HTTPRequest method to check if your request is coming through https.
In order to use is_secure() you need to check an incoming request
to your views:
def my_login_view(request):
if request.is_secure():
Do loggin
else:
Don't login
Now you can protect your other views with the login_required decorator:
#login_required(login_url='/your/login/url')
def protected_view(request):
...
Personal suggestion: I use Django Rest Framework extensively and I would suggest it, in your case, because it has an isAuthenticatedOrReadOnly permission class which you will like:
class MyLoginView(ObtainAuthToken):
"""
Login view for token-based authentication
"""
def post(self, request, *args, **kwargs):
if request.is_secure():
super().post(request, *args, **kwargs)
else:
Probably some redirect goes here...
Then in any other view or class-based view:
class MyOtherView(generics.GenericAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
...
The above will ensure that your users can login only through https and if they are not logged in they will only see a read-only view.
Give it a try if you like.
You didn't accept an answer yet, so I thought, that the answers might not match your question well enough.
Anonymous usage via http is ok. But I want to disable logins via http.
As I understand, you have the following use case:
Some person visits your website as anonymous user, the used protocol is http.
This person opens the login form and logs in, the form is sent through https.
The logged in user continues browsing your website through https.
If the login form is routed to the secure version your user will continue browsing your website in https mode (as long as all other links are agnostic to the used protocol).
<form action="https://your.domain/path/to/login/" method="post">
If the application you're using to authenticate users is sanely written, you can configure your settings.LOGIN_URL, else you'll probably need to do it in the template form.
I recommend allowing session cookies to be sent only through https, using settings.SESSION_COOKIE_SECURE.
With this setting, the user will still be able to make http requests, but the session cookie will not be sent (and the user will be treated as an anonymous user).
Please, make sure that your login view accepts only secure post requests (or else the user may send the password through non https protocol).
This can be achieved either in the Django code (like John Moutafis suggests with his MyLoginView) or at production server level by rejecting non https post requests for the login URL.
--
Could you provide access to the repository with the code?
You may use one or a combination of:
htaccess file, take a look here
webserver level, depend of server type: Apache, Nginx, IIS ?...
django server level, take a look here, and host header validation or SSL/HTTPS

what is the difference between allowed_hosts and cors_origin_regex_whitelist in django?

What is the difference between this two django settings :
ALLOWED_HOSTS
CORS_ORIGIN_REGEX_WHITELIST
ALLOWED_HOSTS
A list of strings representing the host/domain names that this Django site can serve. This is a security measure to prevent HTTP Host header attacks, which are possible even under many seemingly-safe web server configurations
CORS_ORIGIN_REGEX_WHITELIST
This is actually a variable expecting a third party package django-cors-headers.So the thing is when a browser starts a request through javascript to another domain (cross domain), browser will send a OPTIONS request first to get to know whether server is allowing the domain to accept request by checking Access-Control-Allow-Origin header.
Note
There are some other headers also using this like Access-Control-Allow-Headers , etc.
ALLOWED_HOSTS as the docs say is
A list of strings representing the host/domain names that this Django
site can serve.
So, if ALLOWED_HOSTS = ['www.mysite.com', 'mysite.com'] and you try to access from your browser blog.mysite.com then Django will be unable to serve this page. You should ALLOWED_HOSTS = ['www.mysite.com', 'mysite.com', 'blog.mysite.com'] Read the docs related to ALLOWED_HOSTS. They are pretty straightforward!
CORS_ORIGIN_REGEX_WHITELIST as the docs say (again):
A list of regexes that match origin regex list of origin hostnames
that are authorized to make cross-site HTTP requests.
This setting is most used when you have an API that exposes some endpoints for clients to consume. It allows browser's requests to these URLs patterns. So, if CORS_ORIGIN_REGEX_WHITELIST = [r'images.mysite.com', ] then the <img src="http://images.mysite.com/image.jpg />" will load successfully, but if <img src="http://big-images.mysite.com/image.jpg />" then it would fail.
More about CORS in Mozilla MDN.