Skip to content
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

CSRF verification fails when running linkding behind a proxy such as nginx #340

Closed
sissbruecker opened this issue Sep 12, 2022 · 9 comments · Fixed by #349
Closed

CSRF verification fails when running linkding behind a proxy such as nginx #340

sissbruecker opened this issue Sep 12, 2022 · 9 comments · Fixed by #349

Comments

@sissbruecker
Copy link
Owner

Since version 1.15.0 linkding uses Django 4.1, which introduces new restrictions to CSRF handling. This can cause CSRF verification to fail (for example during login) if the app is running behind a proxy and is not properly configured for it.

This needs investigation if the app can provide some default configuration out of the box, otherwise this requires documentation on how to properly configure CSRF when using a proxy.

@Glide01
Copy link

Glide01 commented Sep 13, 2022

same here, I'm using apache.

@sissbruecker
Copy link
Owner Author

sissbruecker commented Sep 13, 2022

Edit: This has been solved with #349

The README now provides instructions on how to configure Nginx, alternatively a new setting (LD_CSRF_TRUSTED_ORIGINS) can be used to configure trusted origins which should work regardless of which reverse proxy is used.

Workarounds for the moment:

  • Use version 1.14.0. Docker tag is sissbruecker/linkding:1.14.0. You will not miss much, there are no critical fixes in the latest release. You can also downgrade, there are no DB migrations between the two versions.
  • Configure your domain as trusted origin:
    • Create a custom.py file
    • Add this line to the file: CSRF_TRUSTED_ORIGINS = ['https://linkding.domain.com'], make sure to use the correct protocol (http or https) and domain
    • Mount the file into the Docker container by adding an additional volume to the Docker run command: -v ./custom.py:/etc/linkding/siteroot/settings/custom.py
    • Create a new container
  • For Nginx, configure the reverse proxy so that it forwards the correct host header instead of rewriting it: CSRF verification fails when running linkding behind a proxy such as nginx #340 (comment)

@sissbruecker sissbruecker pinned this issue Sep 13, 2022
@sissbruecker
Copy link
Owner Author

The new CSRF check in Django 4 requires that the values of the host and origin request headers match. A valid combination would be for example:

Host: linkding.domain.com
Origin: https://linkding.domain.com

From some testing it seems that Nginx changes the value of the host request header to whatever is used in the proxy_pass directive. So if you use proxy_pass http://localhost:9090, then the host header is changed to localhost:9090, which causes the CSRF check to fail. The same does not happen when using Caddy, which just relays the original host header.

So another solution is to configure Nginx to relay the original host header:

    location / {
        proxy_pass http://localhost:9090;
        proxy_set_header Host $http_host;
    }

@0x9394
Copy link

0x9394 commented Sep 15, 2022

nginx subpath config,

  • custom.py method works with latest image.
  • proxy_set_header Host $http_host; option makes no different.
    • with custom.py subpath url will work
    • without custom.py subpath url will goes to 403 forbidden

@sissbruecker
Copy link
Owner Author

Tested forwarding the host header with a sub path in Nginx, works fine for me. Using a context path doesn't make any difference for the CSRF check, as neither the Host or Origin header contain the path, and the proxy_set_header Host $http_host; directive will also not add the path.

I'm testing with the following docker-compose setup:

version: '3'

services:
  linkding:
    ports:
      - 9090
    environment:
      LD_CONTEXT_PATH: linkding/
      LD_SUPERUSER_NAME: admin
      LD_SUPERUSER_PASSWORD: admin
    image: sissbruecker/linkding:latest
  nginx:
    image: nginx:1.22.0
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - '80:80'
    depends_on:
      - linkding

And this is nginx.conf:

server {
    listen 80;
    server_name _;

    location /linkding/ {
        proxy_pass http://linkding:9090;
        proxy_set_header Host $http_host;
    }
}

@schube
Copy link

schube commented Sep 19, 2022

Hi!

Thanks for the project. I tried to set it up with docker and NGinx reverse proxy, but I always get

Verboten (403)
CSRF-Verifizierung fehlgeschlagen. Anfrage abgebrochen.

Mehr Information ist verfügbar mit DEBUG=True.

This is my NGinx config and it does NOT work.

server {
    listen 443 ssl;
    server_name bookmarks.mydomain.com;
    server_tokens off;
    ssl_certificate /etc/letsencrypt/live/bookmarks.mydomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/bookmarks.mydomain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        set $upstream linkding;
        resolver 127.0.0.11 valid=30s;
        #proxy_set_header Host $host;
        proxy_set_header Host $http_host;
        proxy_pass http://$upstream:9090;
    }

}

I usually use proxy_set_header Host $host for my domains but didn't not work here, so I replaced it with proxy_set_header Host $http_host as mentioned above, but I face still same problem.

Any help is welcome! Thank you!

@schube
Copy link

schube commented Sep 19, 2022

For me, it works now. I added proxy_set_header X-Forwarded-Proto $scheme; after reading https://stackoverflow.com/questions/38391376/nginx-gunicorn-django-1-9-csrf-verification-failed

@kytta
Copy link

kytta commented Sep 22, 2022

I have this error when using the API. It makes it impossible to use the extension. Even if I add the internal UUID of the extension to the allowed origins, it now returns:

{"detail":"CSRF Failed: CSRF token missing."}

For some reason, just using a very basic console request works flawlessly and without needing to allow extra origins or to edit nginx config:

$ https example.com/linkding/api/bookmarks/ "Authorization:Token REDACTED" url='https://www.kytta.dev/' 
HTTP/1.1 201 Created
[...]

{
  ...
}

For information, I am using the latest Docker container (haven't tried downgrading yet) running on YunoHost 11.0.9.14 (as Redirect app) with their default Nginx config. I run it on a context path DOMAIN_NAME/linkding/. The linkding's frontend works flawlessly by the way, I get no errors with CSRF there, only the extension makes problems...

UPDATE: Removing the cookies for SSOwat (SSOwAuthUser=NAME; SSOwAuthHash=HASH; SSOwAuthExpire=TIMESTAMP) apparently fixes the issue. I guess my problem is relevant for YunoHost users only

UPDATE 2: I gave up and reconfigured the app to use a subdomain. Works like a charm 🤷‍♂️

@sissbruecker
Copy link
Owner Author

I have this error when using the API. It makes it impossible to use the extension. Even if I add the internal UUID of the extension to the allowed origins, it now returns:

{"detail":"CSRF Failed: CSRF token missing."}

That's actually a different error. All reports so far are for POST requests in the UI, where a valid CSRF token is passed, but then verification fails because host and origin don't match.
Your error indicates that no token at all was passed, but the app still wants to verify one. Which is weird, because the REST API is configured to use token authentication which does not require a CSRF token. It kind of sounds like either:

  • the request for saving the bookmark from the extension did not end up at a REST API endpoint
  • or the auth token was stripped from the request, which would cause the API to fall back to session authentication, which requires a CSRF token

Not quite sure what's going on here. I just did a basic test with mis-configured CSRF in Nginx, and I can still add bookmarks through the extension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants