mirror of https://github.com/zulip/zulip.git
0935d388f0
Django has a `SECURE_PROXY_SSL_HEADER` setting[^1] which controls if it examines a header, usually provided by upstream proxies, to allow it to treat requests as "secure" even if the proximal HTTP connection was not encrypted. This header is usually the `X-Forwarded-Proto` header, and the Django configuration has large warnings about ensuring that this setting is not enabled unless `X-Forwarded-Proto` is explicitly controlled by the proxy, and cannot be supplied by the end-user. In the absence of this setting, Django checks the `wsgi.url_scheme` property of the WSGI environment[^2]. Zulip did not control the value of the `X-Forwarded-Proto` header, because it did not set the `SECURE_PROXY_SSL_HEADER` setting (though see below). However, uwsgi has undocumented code which silently overrides the `wsgi.url_scheme` property based on the `HTTP_X_FORWARDED_PROTO` property[^3] (and hence the `X-Forwarded-Proto` header), thus doing the same as enabling the Django `SECURE_PROXY_SSL_HEADER` setting, but in a way that cannot be disabled. It also sets `wsgi.url_scheme` to `https` if the `X-Forwarded-SSL` header is set to `on` or `1`[^4], providing an alternate route to deceive to Django. These combine to make Zulip always trust `X-Forwarded-Proto` or ``X-Forwarded-SSL` headers from external sources, and thus able to trick Django into thinking a request is "secure" when it is not. However, Zulip is not accessible via unencrypted channels, since it redirects all `http` requests to `https` at the nginx level; this mitigates the vulnerability. Regardless, we harden Zulip against this vulnerability provided by the undocumented uwsgi feature, by stripping off `X-Forwarded-SSL` headers before they reach uwsgi, and setting `X-Forwarded-Proto` only if the request was received directly from a trusted proxy. Tornado, because it does not use uwsgi, is an entirely separate codepath. It uses the `proxy_set_header` values from `puppet/zulip/files/nginx/zulip-include-common/proxy`, which set `X-Forwarded-Proto` to the scheme that nginx received the request over. As such, `SECURE_PROXY_SSL_HEADER` was set in Tornado, and only Tornado; since the header was always set in nginx, this was safe. However, it was also _incorrect_ in cases where nginx did not do SSL termination, but an upstream proxy did -- it would mark those requests as insecure when they were actually secure. We adjust the `proxy_set_header X-Forwarded-Proto` used to talk to Tornado to respect the proxy if it is trusted, or the local scheme if not. [^1]: https://docs.djangoproject.com/en/4.2/ref/settings/#secure-proxy-ssl-header [^2]: https://wsgi.readthedocs.io/en/latest/definitions.html#envvar-wsgi.url_scheme [^3]: |
||
---|---|---|
.. | ||
zulip | ||
zulip_ops | ||
deps.yaml |