Opened 6 weeks ago

Last modified 6 weeks ago

#28699 new Bug

Problem with CSRF in Django 1.11.6

Reported by: stephanm Owned by: nobody
Component: CSRF Version: 1.11
Severity: Release blocker Keywords:
Cc: Florian Apolloner Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I have a problem with csrf protection starting with django 1.11.6
(django 1.11.5 has not this problem).

I am doing all time exactly what is explained in
https://docs.djangoproject.com/en/1.11/howto/auth-remote-user/

My settings:

     MIDDLEWARE = [
        "django.contrib.sessions.middleware.SessionMiddleware",
        "django.middleware.locale.LocaleMiddleware",
        "django.middleware.common.CommonMiddleware",
        "django.middleware.csrf.CsrfViewMiddleware",
        "django.contrib.auth.middleware.AuthenticationMiddleware",
        # "django.contrib.auth.middleware.RemoteUserMiddleware",
        # own middleware because behind proxy we get HTTP_REMOTE_USER 
        # instead of REMOTE_USER
        "lib.auth.middleware.RemoteUserMiddlewareProxy",
        "django.contrib.messages.middleware.MessageMiddleware",
        "django.middleware.clickjacking.XFrameOptionsMiddleware",
    ]
    
    AUTHENTICATION_BACKENDS = [
        # "django.contrib.auth.backends.RemoteUserBackend",
        "lib.auth.backends.RemoteUserBackendTooling",
        # default is:
        "django.contrib.auth.backends.ModelBackend",
    ]
# content of lib.auth.middleware.RemoteUserMiddlewareProxy
from django.contrib.auth.middleware import RemoteUserMiddleware


class RemoteUserMiddlewareProxy(RemoteUserMiddleware):
    header = "HTTP_REMOTE_USER"
# content of lib.auth.backends.RemoteUserBackendTooling
from django.contrib.auth.backends import RemoteUserBackend


class RemoteUserBackendTooling(RemoteUserBackend):

    create_unknown_user = False

    def clean_username(self, username):
        """
        Performs any cleaning on the "username" prior to using it to get or
        create the user object.  Returns the cleaned username.

        By default, returns the username unchanged.
        """
        if username.startswith("IT\\"):
            username = username[3:]
        return username
"

My C# Application does a login by using the normal
"django.contrib.auth.backends.ModelBackend", not using the REMOTE_USER !
It calls a function in my views.py:

    def auth_login_json(request):
        # code...
        # POST data with user & password

Now the csrf protection fails with an http error 403
(only with django 1.11.6, ... django 1.11.5 works)

I found two possibilities to make it work again:

  1. In MIDDLEWARE, comment out "lib.auth.middleware.RemoteUserMiddlewareProxy" but the other remote user login functionality is gone.
  2. I add @csrf_exempt to auth_login_json function like this:
    @csrf_exempt
    def auth_login_json(request):
        # code...
        # POST data with user & password

Reading the changelog https://docs.djangoproject.com/en/1.11/releases/1.11.6/
I suppose this behaviour change comes with https://code.djangoproject.com/ticket/28488

My question: Was I wrong all the years or is this a bug?

Change History (13)

comment:1 Changed 6 weeks ago by Tim Graham

Cc: Florian Apolloner added
Component: UncategorizedCSRF
Type: UncategorizedBug

comment:2 Changed 6 weeks ago by Florian Apolloner

Can your share your code/setup? I do not see anything obvious -- your C# app should always have gotten an CSRF error, or did it include a csrf token?

comment:3 Changed 6 weeks ago by Florian Apolloner

Actually I might have an idea, can you check if commenting out https://github.com/django/django/blob/4d60261b2a77460b4c127c3d832518b95e11a0ac/django/contrib/auth/__init__.py#L128 fixes the issue? This seems to be caused by the auth.login call from the RemoteUserMiddleware which then resets tokens :/

comment:4 Changed 6 weeks ago by stephanm

Hi Florian,

just commented out the "rotate_token(request)" line in login as you told me.

Now it works again.

Perhaps I am doing something wrong too, I didn't understand exactly the
csrf workflow.
I use Apache on Windows with a plugin which allows me to use NTLM as Single Sign On.
My django runs as reverse proxy and gets the remote_user from apache,
which is intended for the normal users which come with their browsers.

But my c# application does a normal login.

Is there some howto explainig how an external program c#
should login, showing when and how the csrf tokens
appears in the cookies during the HTTP conversation
and what of them should be taken?

Thanks.

comment:5 Changed 6 weeks ago by Florian Apolloner

Severity: NormalRelease blocker
Triage Stage: UnreviewedAccepted

Ok, thanks -- given this I can reproduce it. This is a bug in Django (kinda), but probably a hard one to fix :(

comment:6 Changed 6 weeks ago by Florian Apolloner

Actually I am still not sure what and why is happening here. How does your C# app login exactly? Ie where from does it get the csrf token and is the C# app affected by the single sign on stuff?

comment:7 Changed 6 weeks ago by Florian Apolloner

Please restore the original Django 1.11.6 and move

       "django.contrib.auth.middleware.AuthenticationMiddleware",
        "lib.auth.middleware.RemoteUserMiddlewareProxy",

before the CSRF middleware. The issue should be gone then, which will probably mean that the fix will just be a documentation fix.

Last edited 6 weeks ago by Florian Apolloner (previous) (diff)

comment:8 Changed 6 weeks ago by stephanm

Hi,

I restored the original Django 1.11.6 and moved the lines you mentioned
*before* before the CSRF middleware and I can confirm that it works now!

Concerning my C# Application, It calls the following
function in my views.py with a GET call to
get the carf_token:

def auth_get_csrf_token_json(request):
    token = csrf(request)
    csrf_token = str(token["csrf_token"])  # ab django 1.5
    response = JsonResponse({"dataType": "csrf", "data": {"csrftoken": csrf_token}})
    # I set the cookie in the past but it seems not necessary
    ##response.set_cookie("csrftoken", csrf_token)
    return response

Note:

  • I send back the csrf token in as json data but in my C# app I use the csrf token which is in the cookie.
  • One strange thing I didn't understand: the csrf token in the returned json data and in the cookie are different

Honestly I was never sure where to get this initial csrf token
to be able to POST my login data.
So I did my experiments until I found this solution which worked for me
(some times ago).

comment:9 Changed 6 weeks ago by Florian Apolloner

One strange thing I didn't understand: the csrf token in the returned json data and in the cookie are different

Yes, the token changes every request to account for BREACH style attacks. you have to take the first half of it and xor it to the second one (basically) to get the constant "secret" behind it which is reused during the requests.

As for your code:

from django.middleware.csrf import get_token
get_token(request)

in your view should be enough, Django will take care of setting the cookie etc accordingly.

comment:10 in reply to:  7 Changed 6 weeks ago by stephanm

Replying to Florian Apolloner:

...
before the CSRF middleware. The issue should be gone then, which will probably mean that the fix will just be a documentation fix.

If the documentation fix is about to place AuthenticationMiddleware
before the CsrfViewMiddleware in the MIDDLEWARE setting then you
will have to do more than only changing the docs:

  • You will have to change: django-admin startproject so that it generates the appropriate middleware ordering.
  • You will have to tell everybody that their settings.MIDDLEWARE has to be modified, otherwise some functionality may be broken
  • modify perhaps some other places in the docs i missed ...

Is it really only a documentation fix?

comment:11 Changed 6 weeks ago by Florian Apolloner

It only affects the RemoteUserMiddleware, which is not enabled by default.

comment:12 Changed 6 weeks ago by stephanm

Aha ... so, the fix will be whats mentioned in comment:7,
the move of django.contrib.auth.middleware.AuthenticationMiddleware
and the RemoteUserMiddleware... ? (plus fixes in the docs of course)

Right? Or do you plan to do other changes?

I ask this, so I could fix my code now.

comment:13 Changed 6 weeks ago by Florian Apolloner

Yes, something along those lines will be the final fix. I need to think about it a bit more though, cannot gurantee if or what I missed.

Note: See TracTickets for help on using tickets.
Back to Top