Weblate: CSRF error on all POST requests

Created on 20 Mar 2018  路  13Comments  路  Source: WeblateOrg/weblate

I have set up weblate using the documentation without docker. It is running from a virtualenv, served by gunicorn with an nginx in front of it. HTTPS is set up in front (I am using correct.example.com here for the correct HTTPS hostname), the following settings are changed from the default settings:

DEBUG = False
ADMINS = ...
DATABASES { ... 'ENGINE': 'django.db.backends.mysql' }
BASE_DIR = ...
TIME_ZONE = 'Europe/Berlin'
SECRET_KEY =  ...
ENABLE_HTTPS = True
ALLOWED_HOSTS = ['correct.example.com']
LOGIN_REQUIRED_URLS (uncommented example)
LOGIN_REQUIRED_URLS_EXCEPTIONS (uncommented example)

Expected behaviour

I can login

Actual behaviour

When I attempt a login: "CSRF verification failed. Request aborted."

I have observed the following:

  • Every time I reload, the CSRF token in the HTML changes even when I have CSRF_USE_SESSIONS = True. The session cookie stays identical.
  • On a single response, the "Set-cookie: csrftoken" header and the CSRF token in the HTML is different when I have CSRF_USE_SESSIONS = False
  • ENABLE_HTTPS = False or CSRF_COOKIE_SECURE = False do not help
  • With debugging enabled, I can see the following log messages:
    lib/python2.7/site-packages/django/template/defaulttags.py:66: UserWarning: A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext. as well as Forbidden (Referer checking failed - https://correct.example.com/ does not match any trusted origins.): /accounts/register/

Server configuration

Please paste the output of command ./manage.py list_versions over here

  • Weblate 2.19.1
  • Python 2.7.5
  • Django 1.11.11
  • six 1.11.0
  • social-auth-core 1.7.0
  • social-auth-app-django 2.1.0
  • django-appconf 1.0.2
  • Translate Toolkit 2.3.0
  • Whoosh 2.7.4
  • defusedxml 0.5.0
  • Git 1.8.3.1
  • Pillow (PIL) 1.1.7
  • dateutil 2.7.0
  • lxml 4.2.0
  • django-crispy-forms 1.7.2
  • compressor 2.2
  • djangorestframework 3.7.7
  • user-agents 1.1.0
  • pytz 2018.3
  • pyuca N/A
  • python-bidi 0.4.0
  • pyLibravatar N/A
  • PyYAML 3.12
  • Database backends: django.db.backends.mysql


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

question

All 13 comments

FYI: I am seeing the weblate site rendered properly, so weblate migrate and django-admin collectstatic --no-input worked ok. I also ran django-admin changesite --set-name correct.example.com

What is your setting for MIDDLEWARE? Does it match the example one?

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'weblate.accounts.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'social_django.middleware.SocialAuthExceptionMiddleware',
    'weblate.accounts.middleware.RequireLoginMiddleware',
    'weblate.middleware.SecurityMiddleware',
    'weblate.wladmin.middleware.ConfigurationErrorsMiddleware',
]

The MIDDLEWARE looks correct.

Just to clarify - the token in the form should change with every request.

Can you try running with DEBUG = True, that should give you more detailed description of what went wrong. What might be causing this as well is that your browser is not sending referrer, see https://docs.djangoproject.com/en/2.0/ref/csrf/#how-it-works

Thank you for poining out the referrer issue. I had to manually add my domain as
CSRF_TRUSTED_ORIGINS = [".example.com"]

This is somewhat surprising to me, as Django/Weblate knows the URL of the site, e.g. the links in confirmation emails it sens contain the correct hostname.

I'll close the issue for now, but it if this setting is actually necessary to operate correctly, you'd may want to add it to the example configuration.

For what it's worth, my actual hostname is a fourth-level subdomain (translate.internal.project.org).

It really should not be needed, I don't have it set on any Weblate instance I'm running. As for CSRF it AFAIK compares just Host header with Referrer...

To say that I am a Django newbie would be an offence to Django newbies everywhere but this issue saved my skin. I had to do exactly what @rbu suggested and add my own domain as trusted. As soon as I did, everything worked like a charm.

Hello,
today I faced the same error with my Weblate 3.1.1 instance running inside of kubernetes cluster. I still can access the login page but when I try to login then I get the message "CSRF verification failed. Request aborted." inside the user interface.

All worked fine until today. So after researching I could not find out what changed to cause this issue now. I know that something in the K8 cluster must cause this issue but I'm not so familiar with Web development and CSRF. So any tips are highly welcome, which can help me fixing this :)
Any ideas?

Expected behaviour
I can login

Actual behaviour
Error message "CSRF verification failed. Request aborted."

I have observed the following:
I activated Weblate debug mode and this is the error I see in the logs:

Forbidden (Referer checking failed - https://weblate.example.net/ does not match any trusted origins.): /accounts/logout/
/usr/local/lib/python3.5/dist-packages/django/template/defaulttags.py:63: UserWarning: A {% csrf_token %} was used in a template, but the context did not provide the value.  This is usually caused by not using RequestContext.
  "A {% csrf_token %} was used in a template, but the context "
[pid: 140|app: 0|req: 1/4] 172.16.44.151 () {76 vars in 2021 bytes} [Fri Mar 27 13:56:34 2020] POST /accounts/logout/ => generated 6163 bytes in 627 msecs (HTTP/1.1 403) 7 headers in 215 bytes (1 switches on core 0)
172.16.44.151 - - [27/Mar/2020:12:56:35 +0000] "POST /accounts/logout/ HTTP/1.1" 403 1981 "https://weblate.example.net/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15"

Additional information:
I set the environment variable "WEBLATE_ALLOWED_HOSTS" to allow two urls because my Weblate instance is available from two different urls. (There is an ingress in front of Weblate which does the https termination and adds a basic authentication when using one of the urls. The other does not use basic authentication)

Server configuration
Please paste the output of command ./manage.py list_versions over here

 * Weblate 3.1.1
 * Python 3.5.3
 * Django 2.0.8
 * six 1.10.0
 * social-auth-core 1.7.0
 * social-auth-app-django 2.1.0
 * django-appconf 1.0.2
 * translate-toolkit 2.3.0
 * Whoosh 2.7.4
 * defusedxml 0.5.0
 * Git 2.11.0
 * Pillow 4.0.0
 * python-dateutil 2.5.3
 * lxml 3.7.1
 * django-crispy-forms 1.7.2
 * django_compressor 2.2
 * djangorestframework 3.8.2
 * user-agents 1.1.0
 * jellyfish 0.6.1
 * pytz 2018.5
 * pyuca 1.2
 * PyYAML 3.12
 * tesserocr 2.3.1
 * Mercurial 4.0
 * git-svn 2.11.0
 * Database backends: django.db.backends.postgresql
 * Cache backends: default:RedisCache, avatar:FileBasedCache
 * Platform: Linux 5.0.0-1035-azure (x86_64)

From settings.py:
# Middleware
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'weblate.accounts.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'social_django.middleware.SocialAuthExceptionMiddleware',
    'weblate.accounts.middleware.RequireLoginMiddleware',
    'weblate.middleware.SecurityMiddleware',
    'weblate.wladmin.middleware.ConfigurationErrorsMiddleware',
]

# Make CSRF cookie HttpOnly, see documentation for more details:
# https://docs.djangoproject.com/en/1.11/ref/settings/#csrf-cookie-httponly
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = ENABLE_HTTPS
# Store CSRF token in session (since Django 1.11)
CSRF_USE_SESSIONS = True
SESSION_COOKIE_SECURE = ENABLE_HTTPS
# SSL redirect
SECURE_SSL_REDIRECT = ENABLE_HTTPS
# Session cookie age (in seconds)
SESSION_COOKIE_AGE = 1209600

# Some security headers
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
SECURE_CONTENT_TYPE_NOSNIFF = True

# Optionally enable HSTS
SECURE_HSTS_SECONDS = 0
SECURE_HSTS_PRELOAD = False
SECURE_HSTS_INCLUDE_SUBDOMAINS = False

@KarinBerg My guess is that your ingres has changed and doesn't properly pass some headers (Host maybe).

@nijel: Thanks for your answer. It seems that the HTTP_HOST header is there but have a different value than HTTP_ORIGIN and HTTP_REFERER. Could that lead to the CSFR error?

@nijel: ok, I think I understood now the problem in my case.
The HTTP_HOST header did change due to integration of Istio in our K8 cluster.
So now HTTP_HOST is pointing to the K8 service internal host name but HTTP_REFERER has still the external URL (https://weblate.example.net)
It seems that Django offers now two options:

  • CSRF_TRUSTED_ORIGINS
    Expands the accepted referers beyond the current host or cookie domain
  • Set USE_X_FORWARDED_HOST to true
    A boolean that specifies whether to use the X-Forwarded-Host header in preference to the Host header. This should only be enabled if a proxy which sets this header is in use.

@nijel: I'm not sure what side-effects USE_X_FORWARDED_HOST would introduce. So I think I would try to use CSRF_TRUSTED_ORIGINS but I would like to have your opinion on that :)
Also would it be a nice idea to introduce a new WEBLATE environment variable for that?

Python newbie question: Is there a way without rebuilding the weblate docker image to modify the CSRF_TRUSTED_ORIGINS or USE_X_FORWARDED_HOST setting?

You don't have to rebuild it, just add custom configuration file, see https://docs.weblate.org/en/latest/admin/install/docker.html#custom-configuration-files

Was this page helpful?
0 / 5 - 0 ratings