19

Update 7-18:

Here is my nginx config for the proxy server:

server {
    listen 80;
    server_name blah.com; # the blah is intentional

    access_log /home/cheng/logs/access.log;     
    error_log /home/cheng/logs/error.log;       

    location / {
        proxy_pass http://127.0.0.1:8001;         
    }

    location /static {
        alias /home/cheng/diandi/staticfiles;  
    }

    location /images {
        alias /home/cheng/diandi/images;
    }

    client_max_body_size 10M;
}

Here is nginx.conf:

user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events {
        worker_connections 768;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip_disable "msie6";

        # Enable Gzip compressed.
        gzip on;

        # Enable compression both for HTTP/1.0 and HTTP/1.1.
        gzip_http_version  1.1;

        # Compression level (1-9).
        # 5 is a perfect compromise between size and cpu usage, offering about
        # 75% reduction for most ascii files (almost identical to level 9).
        gzip_comp_level    5;

        # Don't compress anything that's already small and unlikely to shrink much
        # if at all (the default is 20 bytes, which is bad as that usually leads to
        # larger files after gzipping).
        gzip_min_length    256;

        # Compress data even for clients that are connecting to us via proxies,
        # identified by the "Via" header (required for CloudFront).
        gzip_proxied       any;

        # Tell proxies to cache both the gzipped and regular version of a resource
        # whenever the client's Accept-Encoding capabilities header varies;
        # Avoids the issue where a non-gzip capable client (which is extremely rare
        # today) would display gibberish if their proxy gave them the gzipped version.
        gzip_vary          on;

        # Compress all output labeled with one of the following MIME-types.
        gzip_types
                application/atom+xml
                application/javascript
                application/json
                application/rss+xml
                application/vnd.ms-fontobject
                application/x-font-ttf
                application/x-web-app-manifest+json
                application/xhtml+xml
                application/xml
                application/x-javascript
                font/opentype
                image/svg+xml
                image/x-icon
                text/css
                text/plain
                text/javascript
                text/js
                text/x-component;

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

Update 7-15:

When copying code to the linux machines, I simply replaced the original source code file but didn't delete the old .pyc files which I don't think will cause trouble right?


Here is the view code:

from django.contrib.auth import authenticate, login
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.shortcuts import render

def login_view(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(username=username, password=password)
        next_url = request.POST['next']
        if user is not None:
            if user.is_active:
                login(request, user)
                if next_url:
                    return HttpResponseRedirect(next_url)
                return HttpResponseRedirect(reverse('diandi:list'))
        else:
            form = {'errors': True}
            return render(request, 'registration/login.html', {'form': form})

    else:
        form = {'errors': False}
        return render(request, 'registration/login.html', {'form': form})

I got one of those CSRF cookie not set error from Django, but this is not because I forgot to include the {% csrf_token %} in my template.

Here is what I observed:

Access login page #1 try

Inside the Request Header, the cookie value is:

csrftoken=yNG8ZmSI4tr2xTLoE9bys8JbSuu9SD34;

In the template:

<input type="hidden" name="csrfmiddlewaretoken" value="9CVlFSxOo0xiYykIxRmvbWyN5iEUHnPB">

In a cookie plugin that I installed on chrome, the actual csrf cookie value is set to:

9CVlFSxOo0xiYykIxRmvbWyN5iEUHnPB

Access login page #2 try:

Inside the Request Header, the cookie value is:

csrftoken=9CVlFSxOo0xiYykIxRmvbWyN5iEUHnPB;

In the template:

<input type="hidden" name="csrfmiddlewaretoken" value="Y534sU40S8iTubSVGjjh9KQl0FXesVsC">

In a cookie plugin that I installed on chrome, the actual csrf cookie value is set to:

Y534sU40S8iTubSVGjjh9KQl0FXesVsC

The pattern

As you can see from the examples above, the cookie value inside the Request Header differs from the actual csrfmiddlewaretoken in the form and the actual cookie value being set.

The cookie value of the current request matches the next request header's cookie value.


To help debugging, here is a portion of my `settings.py:

DJANGO_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

THIRD_PARTY_APPS = (
    'compressor',
    'crispy_forms',
    'django_extensions',
    'floppyforms',
    'multiselectfield',
    'admin_highcharts',
)

LOCAL_APPS = (
    'diandi_project',
    'uer_application',
)

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

MIDDLEWARE_CLASSES = (
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [str(ROOT_DIR.path('templates'))],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.media',
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

I am using Django 1.9.5 and python 2.7.10.

One "solution"

I have encountered this problem before, I can clear all my browser cookies and the site will function properly. But this problem will eventually come up again, so I am really hoping someone can help me out (I probably just made a really dumb mistake somewhere).

Update

Originally, I thought I made some mistakes while overriding the django.contrib.auth.view page, so I wrote my own login page handler and it still causes the issue.

Here is the core part of my login template:

{% block content %}
...

                <form method="post" action="{% url 'login' %}">
                    {% csrf_token %}

                    <div class="form-group">
                        <label for="username">username</label>
                        <input type="text" class="form-control" id="id_username" name="username">
                    </div>
                    <div class="form-group">
                        <label for="password">password</label>
                        <input type="password" class="form-control" id="id_password" name="password">
                    </div>

                    <input type="submit" class="btn btn-default" value="login" />
                    <input type="hidden" id="next" name="next" value="" />
                </form>

...

{% endblock %}

On the Linux machines, I have a nginx server setup as a reverse proxy which direct request on port 80 to 8001, and I am running the server using ./manage runserver localhost:8001 This is the only difference I can think of in terms of setup. Otherwise, all of the source code and settings file are identical.


I started deleting cookies but not all of them, this is what I see before deleting them:

enter image description here

I deleted all the cookies other than djdt and csrftoken, then the page worked. Could the deleted cookies somehow go over some limit which prevent the csrftoken which is further down the list from being set?

Here is the cookie value of the image above in the request header:

Cookie:PSTM=1466561622; BIDUPSID=6D0DDB8084625F2CEB7B9D0F14F93391; BAIDUID=326150BF5A6DFC69B6CFEBD67CA7A18B:FG=1; BDSFRCVID=Fm8sJeC62leqR8bRqWS1u8KOKg9JUZOTH6ao6BQjXAcTew_mbPF_EG0PJOlQpYD-hEb5ogKK0mOTHvbP; H_BDCLCKID_SF=tJPqoCtKtCvbfP0k-tcH244HqxbXq-r8fT7Z0lOnMp05EnnjKl5M3qKOqJraJJ585Gbb5tOhaKj-VDO_e6u-e55LjaRh2PcM2TPXQ458K4__Hn7zep0aqJtpbt-qJjbOfmQBbfoDQCTDfho5b63JyTLqLq5nBT5Ka26WVpQEQM5c8hje-4bMXPkkQN3T-TJQL6RkKTCyyx3cDn3oyToVXp0njGoTqj-eJbA8_CtQbPoHHnvNKCTV-JDthlbLetJyaR3lWCnbWJ5TMCo1bJQCe-DwKJJgJRLOW2Oi0KTFQxccShPC-tP-Ll_qW-Q2LPQfXKjabpQ73l02VhcOhhQ2Wf3DM-oat4RMW20jWl7mWPQDVKcnK4-Xj533DHjP; BDUSS=5TNmRvZnh2eUFXZDA5WXI5UG1HaXYwbzItaWt3SW5adjE1Nn5XbUVoWHZuYXBYQVFBQUFBJCQAAAAAAAAAAAEAAAC0JtydAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO8Qg1fvEINXSU; Hm_lvt_a7708f393bfa27123a1551fef4551f7a=1468229606; Hm_lpvt_a7708f393bfa27123a1551fef4551f7a=1468229739; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; BDRCVFR[dG2JNJb_ajR]=mk3SLVN4HKm; BDRCVFR[-pGxjrCMryR]=mk3SLVN4HKm; cflag=15%3A3; H_PS_PSSID=1424_20515_13289_20536_20416_19861_14994_11792; csrftoken=xUgSHybzHeIwusN0GvMgB1ATeRrPgcV1

Since the site functions now, all I have are five cookies instead of 14 like the image above:

enter image description here

Community
  • 1
  • 1
Cheng
  • 16,824
  • 23
  • 74
  • 104
  • Normally, overriding a view shouldn't be a problem, I've done it multiple times and never encountered that problem. Are you using django's built-in `runserver` debug server or are you using another (e.g. Apache/Nginx based) configuration? In the 2nd case, do you get the same issue with runserver? – raphv Jul 11 '16 at 09:44
  • Runserver works on my mac but produce this problem on a linux machine – Cheng Jul 11 '16 at 09:53
  • Are you using any form of caching on that linux machine? This may explain why the csrftoken shown in the form is either an obsolete one or one produced for another user. See https://docs.djangoproject.com/en/1.9/ref/csrf/#s-caching – raphv Jul 11 '16 at 10:05
  • No cache is used. Not in the code and not in `settings.py`. If I clear all the cookies for this site, the error goes aways so I am not even sure what is causing it. – Cheng Jul 11 '16 at 10:11
  • 2
    Could you please add the view's code? – trinchet Jul 13 '16 at 13:42
  • 4
    It is correct and to be expected that the *Request* header contains a CSRF token different from the one in the *Response*'s `Set-Cookie` header and the form. Django's CSRF implementation works by comparing the Cookie that the client transmitted to the value contained in the form, based on the assumption that an attacker attempting CSRF would not be able to control or obtain the cookie's value. (That is, the "pattern" which you observe is perfectly normal. Your issue lies somewhere else.) – Phillip Jul 13 '16 at 13:44
  • @Cheng if you can replicate the sequence above, what happens if you refresh the browser between step one and two (i.e., trigger a resync of the cookie)? Sometimes this happens to me when I have an error that breaks a request, so the cookie doesn't get reset correctly and then they get out of sync like you have here. – YPCrumble Jul 13 '16 at 17:50
  • @Cheng also, which specific error are you getting out of [these errors](https://github.com/django/django/blob/master/django/middleware/csrf.py#L25)? Is it actually the `REASON_NO_CSRF_COOKIE` error and always this one? Or is it a different one? Always? – YPCrumble Jul 13 '16 at 17:52
  • @YPCrumble `REASON_NO_CSRF_COOKIE = "CSRF cookie not set."` this is the error I got consistently – Cheng Jul 14 '16 at 06:40
  • That error is specifically reserved for the case where the Cookie is not set at all... I think you need to be looking more closely at your browser. What is it? – Peter Brittain Jul 14 '16 at 17:54
  • In fact, you probably want to look at http://stackoverflow.com/questions/5381526/what-are-the-current-cookie-limits-in-modern-browsers – Peter Brittain Jul 14 '16 at 17:56
  • @PeterBrittain I have seen this happens on both chrome and safari – Cheng Jul 15 '16 at 02:38
  • @PeterBrittain I thought about the limits but by adding up the cookie size in the screenshot, it is nowhere close to 4096 bytes – Cheng Jul 15 '16 at 02:43
  • OK, so looking at the django error (which says it definitely didn't receive the cookie) and your browser output (which implies it always sends a cookie), that points the finger at the nginx reverse proxy. Are you seeing any errors on that server and what configuration are you using? – Peter Brittain Jul 15 '16 at 09:11
  • Don't know if this is gonna help, but .. what version of python are you using? If you are using python 2.7 under 2.7.10 try to update to 2.7.10+, in 2.7.10 there is a [cookie related bug](https://mail.python.org/pipermail/python-dev/2015-May/140135.html) which got fixed. – Todor Jul 15 '16 at 18:24
  • @Todor I am using 2.7.10 – Cheng Jul 16 '16 at 07:05
  • @PeterBrittain that's a good point. Since I have delete the cookie on my browser side, I am having a hard time to reproduce the bug. Everytime I update the code on the server side, do I have to do a `nginx -s reload`? I will post my nginx config next Monday. – Cheng Jul 16 '16 at 07:07
  • I don't see why you should have to restart nginx. As for reproducing the issue... Why not create a simple page on your server that generates some random extra cookies every timeyou visit it? – Peter Brittain Jul 17 '16 at 16:59
  • @Cheng Please post your solution as a formal answer. Editing solutions into your question goes against the editorial practices of Stack Overflow. – Louis Jul 18 '16 at 11:32
  • @Louis Can I earn my own bounty then? – Cheng Jul 18 '16 at 12:02
  • @Cheng No, you cannot. – Louis Jul 18 '16 at 12:09
  • @Louis I would appreciate if you gave me the chance to copy and paste what I had in the original post. So that I don't have to re-type everything. – Cheng Jul 18 '16 at 12:17
  • @Cheng You can find the markdown by going into the [revisions](http://stackoverflow.com/posts/38302058/revisions) of your question, going down to revision 14, clicking the "side-by-side markdown" option and copying the text that it shows has been removed. You could then paste it into an answer. – Louis Jul 18 '16 at 12:22

1 Answers1

17

Here is the issue: You cannot have a cookie which key contains either the character '[' or ']'

I discovered the solution following @Todor's link, then I found out about this SO post. Basically there was a bug in python 2.7.x that does not parse cookies with ']' in the value. The bug was fixed in 2.7.10.

I thought it would be good to just confirm this issue. So I dug through all of the cookies and found one with the following key/value:

key: BDRCVFR[feWj1Vr5u3D]
val: I67x6TjHwwYf0

So I inserted the following cookie locally and submitted to the server:

key: test
val: BDRCVFR[feWj1Vr5u3D]

The login page worked, which means 2.7.10 indeed fixed the bug.

But then I realized that the square brackets are actually in the key name not in the value, so I did the following tests:

key: [
val: I67x6TjHwwYf0

and

key:]
val: I67x6TjHwwYf0

Both cookies break the login process and django displays:

CSRF cookie not set

So either django or a python library it relies on cannot parse cookies with square brackets in names properly. If anybody knows where I should submit this bug please let me know (django or python).

I would like to thank everybody who left a comment in the OP: @raphv, @trinchet, @Phillip, @YPCrumble, @PeterBrittain and @Todor. Thank you guys so much for debugging with me!


Update: July 20, 2016

This bug is fixed in Django 1.10, just have to wait for the release

Update: July 19, 2016

I filed a bug report to Django as the result of this post. We will see if it will be fixed in future releases.

Community
  • 1
  • 1
Cheng
  • 16,824
  • 23
  • 74
  • 104