New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forbidden 403 status with csrf check (REASON_BAD_REFERER) via HTTPS #839

Closed
avkonst opened this Issue May 15, 2013 · 8 comments

Comments

Projects
None yet
7 participants
@avkonst

avkonst commented May 15, 2013

Hi,

I have got an API running over http and https. The server is configured to check for csrf token on unsafe requests, like POST.
I have got a client which does POST request supplying with necessary sessionid and csrf tokens. If it runs over HTTP, everything works. If it runs over HTTPS, it does not. The server returns: {"detail": "CSRF Failed: Referer checking failed - http://localhost:8800/jstest/ does not match https://my.api.server.com/."}

I have found the place where the request is rejected in rest framework code (file compat.py):

            if request.is_secure(): # <--- TRUE IN CASE OF HTTPS
                # Suppose user visits http://example.com/
                # An active network attacker,(man-in-the-middle, MITM) sends a
                # POST form which targets https://example.com/detonate-bomb/ and
                # submits it via javascript.
                #
                # The attacker will need to provide a CSRF cookie and token, but
                # that is no problem for a MITM and the session independent
                # nonce we are using. So the MITM can circumvent the CSRF
                # protection. This is true for any HTTP connection, but anyone
                # using HTTPS expects better!  For this reason, for
                # https://example.com/ we need additional protection that treats
                # http://example.com/ as completely untrusted.  Under HTTPS,
                # Barth et al. found that the Referer header is missing for
                # same-domain requests in only about 0.2% of cases or less, so
                # we can use strict Referer checking.
                referer = request.META.get('HTTP_REFERER')
                if referer is None:
                    logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path),
                        extra={
                            'status_code': 403,
                            'request': request,
                        }
                    )
                    return self._reject(request, REASON_NO_REFERER)

                # Note that request.get_host() includes the port
                good_referer = 'https://%s/' % request.get_host()
                if not same_origin(referer, good_referer):
                    reason = REASON_BAD_REFERER % (referer, good_referer)
                    logger.warning('Forbidden (%s): %s' % (reason, request.path),
                        extra={
                            'status_code': 403,
                            'request': request,
                        }
                    )
                    return self._reject(request, reason)

This code tells me that clients should have a referral to the API host (https://my.api.server.com/), and I think it is not right, because JavaScript client can be hosted anywhere. In case of a client in Python language, there is no even a referral to host, it is empty...

Could you explain this issue? What should I check, understand?

@tomchristie

This comment has been minimized.

Show comment
Hide comment
@tomchristie

tomchristie May 16, 2013

Member

Session authentication is only appropriate for javascript based clients that are running in the same site as the API.

Other clients such as python based client, and javascript clients on a different site that interact with the API should use a standard token-based auth, such as OAuth2.

Hope that helps clarify things a little! :)

Member

tomchristie commented May 16, 2013

Session authentication is only appropriate for javascript based clients that are running in the same site as the API.

Other clients such as python based client, and javascript clients on a different site that interact with the API should use a standard token-based auth, such as OAuth2.

Hope that helps clarify things a little! :)

@bpipat

This comment has been minimized.

Show comment
Hide comment
@bpipat

bpipat Aug 23, 2014

Hi @tomchristie,

First of all thanks for the amazing work on DRF :) it is such a great framework to work with !

I'm having the same error on a Cordova-packaged AngularJS app interacting with with DRF API with TokenAuthentication. My POST requests work over http but when I switch to https I get:

{"detail": "CSRF Failed: Referer checking failed - no Referer."}

Should csrf even be used on mobile-client/server communication? (I'm having quite a lot of trouble making auth work on mobile clients with the REST api..)

Cheers :)

Benjamin

bpipat commented Aug 23, 2014

Hi @tomchristie,

First of all thanks for the amazing work on DRF :) it is such a great framework to work with !

I'm having the same error on a Cordova-packaged AngularJS app interacting with with DRF API with TokenAuthentication. My POST requests work over http but when I switch to https I get:

{"detail": "CSRF Failed: Referer checking failed - no Referer."}

Should csrf even be used on mobile-client/server communication? (I'm having quite a lot of trouble making auth work on mobile clients with the REST api..)

Cheers :)

Benjamin

@jpadilla

This comment has been minimized.

Show comment
Hide comment
@jpadilla

jpadilla Aug 23, 2014

Member

@bpipat if you're using TokenAuthentication it doesn't make much sense to use CSRF. A CSRF attack involves only the browser.

Member

jpadilla commented Aug 23, 2014

@bpipat if you're using TokenAuthentication it doesn't make much sense to use CSRF. A CSRF attack involves only the browser.

@kevin-brown

This comment has been minimized.

Show comment
Hide comment
@kevin-brown

kevin-brown Aug 23, 2014

Member

@bpipat if you're using TokenAuthentication it doesn't make much sense to use CSRF. A CSRF attack involves only the browser.

In order to get your token for TokenAuthentication, you typically have to hit the DRF view and provide the user's credentials.

Should csrf even be used on mobile-client/server communication?

In your case, I wouldn't recommend it.

CSRF is only checked if you are using SessionAuthentication, and even then it is only checked when the user has already been logged in by the middleware (such that request.user already exists). Considering CSRF checks are being done, this usually suggests that you have some middleware that is authenticating users ahead of time, or the @csrf_exempt decorator is no longer on your view.

The decorator wraps the dispatch method, so if you have overridden the dispatch method at all you will need to re-wrap it in the csrf_exempt decorator.

Keep in mind that these checks happen using the Django CSRF middleware, so it's not a DRF-specific issue.

Member

kevin-brown commented Aug 23, 2014

@bpipat if you're using TokenAuthentication it doesn't make much sense to use CSRF. A CSRF attack involves only the browser.

In order to get your token for TokenAuthentication, you typically have to hit the DRF view and provide the user's credentials.

Should csrf even be used on mobile-client/server communication?

In your case, I wouldn't recommend it.

CSRF is only checked if you are using SessionAuthentication, and even then it is only checked when the user has already been logged in by the middleware (such that request.user already exists). Considering CSRF checks are being done, this usually suggests that you have some middleware that is authenticating users ahead of time, or the @csrf_exempt decorator is no longer on your view.

The decorator wraps the dispatch method, so if you have overridden the dispatch method at all you will need to re-wrap it in the csrf_exempt decorator.

Keep in mind that these checks happen using the Django CSRF middleware, so it's not a DRF-specific issue.

@bpipat

This comment has been minimized.

Show comment
Hide comment
@bpipat

bpipat Aug 25, 2014

@jpadilla @kevin-brown thank you for your answers.

CSRF is only checked if you are using SessionAuthentication

My API is used by both the mobile app (Token auth) and the webapp for which I kept the Session auth middleware (it seems to be required required, at least for the Django Admin).

Should I convert my app to a Token only authentication? If so, do I need to override the Django Auth to make the admin work?

Or should I have different endpoints for mobile and web clients?

Keep in mind that these checks happen using the Django CSRF middleware, so it's not a DRF-specific issue

Thanks, I'll have a further look into that. Although I realise my issue is more about correctly using DRF's Auth classes for different client types

bpipat commented Aug 25, 2014

@jpadilla @kevin-brown thank you for your answers.

CSRF is only checked if you are using SessionAuthentication

My API is used by both the mobile app (Token auth) and the webapp for which I kept the Session auth middleware (it seems to be required required, at least for the Django Admin).

Should I convert my app to a Token only authentication? If so, do I need to override the Django Auth to make the admin work?

Or should I have different endpoints for mobile and web clients?

Keep in mind that these checks happen using the Django CSRF middleware, so it's not a DRF-specific issue

Thanks, I'll have a further look into that. Although I realise my issue is more about correctly using DRF's Auth classes for different client types

@cho-is

This comment has been minimized.

Show comment
Hide comment
@cho-is

cho-is Oct 23, 2015

@bpipat how did you figure it out?

cho-is commented Oct 23, 2015

@bpipat how did you figure it out?

@bpipat

This comment has been minimized.

Show comment
Hide comment
@bpipat

bpipat Oct 26, 2015

@cho-is: I ended up switching to Token Auth on all platforms. The biggest inconvenient is that in the browser you can't reload without getting logged out... :/

bpipat commented Oct 26, 2015

@cho-is: I ended up switching to Token Auth on all platforms. The biggest inconvenient is that in the browser you can't reload without getting logged out... :/

@aymericderbois

This comment has been minimized.

Show comment
Hide comment
@aymericderbois

aymericderbois Nov 13, 2015

Contributor

@cho-is @bpipat : I don't know if it's the best way, but you maybe can create a custom authentication, that always return a AnonymousUser

class AnonAuthentication(BaseAuthentication):

    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if auth and auth[0].lower() == 'anon':
            return AnonymousUser(), ''

    def authenticate_header(self, request):
        return 'Anon'

On your mobile app, you just need to add "Authorize: Anon" in headers, for all page without token.
On your javascript client, you can to use SessionAuth

Why it works ?

  • For mobile app pages with Token auth, no change
  • For javascript client, no change
  • for mobile app on others pages, that failed because of SessionAuth. But if you use AnonAuth on this pages (with "Authorize: Anon" in headers), SessionAuth isn't called.

Is that a good solution ?

ps: Sorry for my bad english :)

Contributor

aymericderbois commented Nov 13, 2015

@cho-is @bpipat : I don't know if it's the best way, but you maybe can create a custom authentication, that always return a AnonymousUser

class AnonAuthentication(BaseAuthentication):

    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if auth and auth[0].lower() == 'anon':
            return AnonymousUser(), ''

    def authenticate_header(self, request):
        return 'Anon'

On your mobile app, you just need to add "Authorize: Anon" in headers, for all page without token.
On your javascript client, you can to use SessionAuth

Why it works ?

  • For mobile app pages with Token auth, no change
  • For javascript client, no change
  • for mobile app on others pages, that failed because of SessionAuth. But if you use AnonAuth on this pages (with "Authorize: Anon" in headers), SessionAuth isn't called.

Is that a good solution ?

ps: Sorry for my bad english :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment