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

HTTPS: Hostname is not an accepted origin, even on same-origin requests #1501

Closed
mtlynch opened this issue Mar 18, 2021 · 5 comments
Closed
Assignees
Labels

Comments

@mtlynch
Copy link

mtlynch commented Mar 18, 2021

Describe the bug

I'm accessing my Flask-SocketIO endpoint through an nginx reverse proxy. It works correctly over plaintext HTTP, but it fails over HTTPS with:

<hostname> is not an accepted origin.

I know that one option is cors_allowed_origins, but that's not a great fit for my scenario. This is a server that users self-host, so they want to be able to rename it, access it by IP, or access it via a client-side host alias.

The behavior I'm observing doesn't seem to match the docs:

If an incoming HTTP or WebSocket request includes the Origin header, this header must match the scheme and host of the connection URL. In case of a mismatch, a 400 status code response is returned and the connection is rejected.

I've captured HTTP traffic between nginx and my Flask-SocketIO endpoint under different nginx configurations:

nginx configuration Host Origin Result HTTP Log
Standard nginx HTTP config http://tinypilot.local tinypilot.local HTTP/1.1 101 Switching Protocols Log
Standard nginx HTTPS config https://tinypilot.local tinypilot.local HTTP/1.1 400 BAD REQUEST "https://tinypilot.local is not an accepted origin." Log
proxy_set_header Origin http://$http_host; tinypilot.local tinypilot.local HTTP/1.1 400 BAD REQUEST tinypilot.local is not an accepted origin. Log
proxy_set_header Origin ""; absent tinypilot.local HTTP/1.1 101 Switching Protocols Log

The last configuration works, but it exposes a CSRF vulnerability because it would make all requests look like same-origin.

What is Flask-SocketIO's recommended configuration for communicating through a TLS proxy?

To Reproduce

Server

  • Flask==1.1.1
  • Flask-SocketIO==5.0.1
  • python-engineio==4.0.1
  • python-socketio==5.1.0

Client

  • socket.io==3.1.3

Steps to reproduce the behavior:

  1. Use the recommended nginx config, but add TLS support
  2. Create a Flask-SocketIO server with the default values (socketio = flask_socketio.SocketIO())
  3. Create a socket.io JS client with the default values (const socket = io();)
  4. Connection fails over HTTPS, but works over plaintext HTTP

Expected behavior

Flask-SocketIO accepts the connection because it's a same-origin request.

Logs

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Mar 18, 2021

Are you using something like the ProxyFix middleware in your Python application? This takes the proxy_params injected by nginx and puts them in the correct places in the WSGI environment dictionary.

https://werkzeug.palletsprojects.com/en/1.0.x/middleware/proxy_fix/

@mtlynch
Copy link
Author

mtlynch commented Mar 18, 2021

Thanks for the quick response, @miguelgrinberg!

Are you using something like the ProxyFix middleware in your Python application?

Oh, I'm not. Is that Flask-SocketIO's recommended solution for accessing a Flask-SocketIO endpoint through nginx?

From reading the source more carefully, I realized that this nginx configuration gets things working under HTTPS and seems fairly clean:

location /socket.io {
      proxy_pass http://localhost:5000/socket.io;
      proxy_http_version 1.1;
      proxy_buffering off;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
      proxy_set_header Host $http_host;
      proxy_set_header X-Forwarded-Host $http_host;
      proxy_set_header X-Forwarded-Proto $scheme;
    }

@miguelgrinberg
Copy link
Owner

All these headers that you added manually are included in the proxy_params configuration that is shown in the nginx example in the documentation. No need to add all this stuff manually.

If you are using a relatively new build of the python-engineio package than this is all it takes. I mentioned the ProxyFix because that used to be required in the past. You indicated that you were using the same nginx configuration that is shown in the documentation, so I assumed you already had the proxy params defined.

@mtlynch
Copy link
Author

mtlynch commented Mar 18, 2021

The proxy_params line is not sufficient to establish the connection on my system.

It looks like Flask-SocketIO is looking for the X-Forwarded-Host header, which isn't part of proxy_params in my nginx install:

$ cat /etc/nginx/proxy_params
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
$ nginx -V
nginx version: nginx/1.14.2
built with OpenSSL 1.1.1d  10 Sep 2019
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-dPHYpN/nginx-1.14.2=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module --add-dynamic-module=/build/nginx-dPHYpN/nginx-1.14.2/debian/modules/http-auth-pam --add-dynamic-module=/build/nginx-dPHYpN/nginx-1.14.2/debian/modules/http-dav-ext --add-dynamic-module=/build/nginx-dPHYpN/nginx-1.14.2/debian/modules/http-echo --add-dynamic-module=/build/nginx-dPHYpN/nginx-1.14.2/debian/modules/http-upstream-fair --add-dynamic-module=/build/nginx-dPHYpN/nginx-1.14.2/debian/modules/http-subs-filter

@miguelgrinberg
Copy link
Owner

Oh, okay, I see the problem now. It's a bug. The proxy_params script puts the host in the Host header, and the scheme in the X-Forwarded-Proto. My logic uses the X-Forwarded headers for both. I'll fix it.

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

No branches or pull requests

2 participants