mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-12 00:23:01 +03:00
Reject cross-origin websocket connections (#24714)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* reject cross-origin websocket handshakes Neither /ws nor the jsmpeg websockets checked Origin, and the internal port grants admin to everyone, so any page a LAN user visited could open ws://frigate:5000/ws and send commands or read streams. Both servers now reject a browser handshake whose Origin host matches neither Host nor X-Forwarded-Host, and allow handshakes without an Origin for non-browser clients. nginx now forwards an outer proxy's X-Forwarded-Host instead of overwriting it, so proxies that rewrite Host and the Home Assistant integration's proxy still match. * apply runtime role changes to jsmpeg streams The output process never subscribed to config/auth, so role edits from the Roles UI (requires_restart 0) did not reach it and jsmpeg kept streaming cameras that had been removed from a role until restart. The output process now replaces its auth config on config/auth updates, which the camera and birdseye broadcast threads read through the shared config. * tidy origin check docstring and auth update comment * reject cross-origin go2rtc stream requests in /auth The go2rtc websockets and the webrtc offer POST still accepted any Origin, since go2rtc runs with origin "*" and /auth grants admin on the internal port, so a page on another site could pull live video through a LAN user's browser. nginx now passes Origin, Host, X-Forwarded-Host and Upgrade to /auth, which rejects a websocket upgrade or go2rtc stream request with a foreign Origin before the internal-port early return. The hostname rule moves to frigate/util/origin.py so /auth and the ws4py servers share it. * set Host in the nginx proxy doc examples * drain all queued auth updates in the output process * note that cards must stream through the integration proxy * strip the query before matching go2rtc paths in the origin check
This commit is contained in:
+27
-1
@@ -39,6 +39,7 @@ from frigate.config import AuthConfig, ProxyConfig
|
||||
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
||||
from frigate.models import User
|
||||
from frigate.notices import raise_notice
|
||||
from frigate.util.origin import origin_allowed
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -718,6 +719,17 @@ def auth(request: Request):
|
||||
|
||||
success_response = Response("", status_code=202)
|
||||
|
||||
original_url = request.headers.get("x-original-url")
|
||||
|
||||
if _is_cross_origin_stream_request(original_url, request):
|
||||
logger.warning(
|
||||
"Rejected cross-origin request: origin=%s, host=%s, x-forwarded-host=%s",
|
||||
request.headers.get("origin"),
|
||||
request.headers.get("x-original-host"),
|
||||
request.headers.get("x-forwarded-host"),
|
||||
)
|
||||
return Response("", status_code=403)
|
||||
|
||||
# dont require auth if the request is on the internal port
|
||||
# this header is set by Frigate's nginx proxy, so it cant be spoofed.
|
||||
# the port is the boot-time snapshot rather than the live config value:
|
||||
@@ -741,7 +753,6 @@ def auth(request: Request):
|
||||
logger.debug("X-Proxy-Secret header does not match configured secret value")
|
||||
return fail_response
|
||||
|
||||
original_url = request.headers.get("x-original-url")
|
||||
frigate_config = request.app.frigate_config
|
||||
|
||||
# if auth is disabled, just apply the proxy header map and return success
|
||||
@@ -1307,6 +1318,21 @@ def _is_go2rtc_stream_proxy_request(path: str, request: Request) -> bool:
|
||||
return any(normalized.startswith(p) for p in GO2RTC_STREAM_PROXY_PATHS)
|
||||
|
||||
|
||||
def _is_cross_origin_stream_request(original_url: str | None, request: Request) -> bool:
|
||||
# the internal port grants admin, so a browser on another site must be
|
||||
# stopped before that early return
|
||||
upgrade = request.headers.get("x-original-upgrade", "").lower()
|
||||
path = split_request_uri(original_url or "")[0]
|
||||
if upgrade != "websocket" and not _is_go2rtc_stream_proxy_request(path, request):
|
||||
return False
|
||||
|
||||
return not origin_allowed(
|
||||
request.headers.get("origin"),
|
||||
request.headers.get("x-original-host"),
|
||||
request.headers.get("x-forwarded-host"),
|
||||
)
|
||||
|
||||
|
||||
def deny_response_for_go2rtc_stream(
|
||||
original_url: str | None, role: str | None, request: Request
|
||||
) -> int | None:
|
||||
|
||||
Reference in New Issue
Block a user