mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-26 18:18:22 +03:00
Ensure that arbitrary reads / writes can't be executed from ffmpeg (#22607)
This commit is contained in:
parent
de593c8e3f
commit
334245bd3c
@ -46,6 +46,7 @@ from frigate.record.export import (
|
|||||||
DEFAULT_TIME_LAPSE_FFMPEG_ARGS,
|
DEFAULT_TIME_LAPSE_FFMPEG_ARGS,
|
||||||
PlaybackSourceEnum,
|
PlaybackSourceEnum,
|
||||||
RecordingExporter,
|
RecordingExporter,
|
||||||
|
validate_ffmpeg_args,
|
||||||
)
|
)
|
||||||
from frigate.util.time import is_current_hour
|
from frigate.util.time import is_current_hour
|
||||||
|
|
||||||
@ -547,6 +548,24 @@ def export_recording_custom(
|
|||||||
|
|
||||||
export_id = f"{camera_name}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
|
export_id = f"{camera_name}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
|
||||||
|
|
||||||
|
# Validate user-provided ffmpeg args to prevent injection
|
||||||
|
for args_label, args_value in [
|
||||||
|
("input", ffmpeg_input_args),
|
||||||
|
("output", ffmpeg_output_args),
|
||||||
|
]:
|
||||||
|
if args_value is not None:
|
||||||
|
valid, message = validate_ffmpeg_args(args_value)
|
||||||
|
if not valid:
|
||||||
|
return JSONResponse(
|
||||||
|
content=(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"message": f"Invalid ffmpeg {args_label} arguments: {message}",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
# Set default values if not provided (timelapse defaults)
|
# Set default values if not provided (timelapse defaults)
|
||||||
if ffmpeg_input_args is None:
|
if ffmpeg_input_args is None:
|
||||||
ffmpeg_input_args = ""
|
ffmpeg_input_args = ""
|
||||||
|
|||||||
@ -36,6 +36,54 @@ logger = logging.getLogger(__name__)
|
|||||||
DEFAULT_TIME_LAPSE_FFMPEG_ARGS = "-vf setpts=0.04*PTS -r 30"
|
DEFAULT_TIME_LAPSE_FFMPEG_ARGS = "-vf setpts=0.04*PTS -r 30"
|
||||||
TIMELAPSE_DATA_INPUT_ARGS = "-an -skip_frame nokey"
|
TIMELAPSE_DATA_INPUT_ARGS = "-an -skip_frame nokey"
|
||||||
|
|
||||||
|
# ffmpeg flags that can read from or write to arbitrary files
|
||||||
|
BLOCKED_FFMPEG_ARGS = frozenset(
|
||||||
|
{
|
||||||
|
"-i",
|
||||||
|
"-filter_script",
|
||||||
|
"-vstats_file",
|
||||||
|
"-passlogfile",
|
||||||
|
"-sdp_file",
|
||||||
|
"-dump_attachment",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ffmpeg_args(args: str) -> tuple[bool, str]:
|
||||||
|
"""Validate that user-provided ffmpeg args don't allow input/output injection.
|
||||||
|
|
||||||
|
Blocks:
|
||||||
|
- The -i flag and other flags that read/write arbitrary files
|
||||||
|
- Absolute/relative file paths (potential extra outputs)
|
||||||
|
- URLs and ffmpeg protocol references (data exfiltration)
|
||||||
|
"""
|
||||||
|
if not args or not args.strip():
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
tokens = args.split()
|
||||||
|
for token in tokens:
|
||||||
|
# Block flags that could inject inputs or write to arbitrary files
|
||||||
|
if token.lower() in BLOCKED_FFMPEG_ARGS:
|
||||||
|
return False, f"Forbidden ffmpeg argument: {token}"
|
||||||
|
|
||||||
|
# Block tokens that look like file paths (potential output injection)
|
||||||
|
if (
|
||||||
|
token.startswith("/")
|
||||||
|
or token.startswith("./")
|
||||||
|
or token.startswith("../")
|
||||||
|
or token.startswith("~")
|
||||||
|
):
|
||||||
|
return False, "File paths are not allowed in custom ffmpeg arguments"
|
||||||
|
|
||||||
|
# Block URLs and ffmpeg protocol references (e.g. http://, tcp://, pipe:, file:)
|
||||||
|
if "://" in token or token.startswith("pipe:") or token.startswith("file:"):
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
"Protocol references are not allowed in custom ffmpeg arguments",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
def lower_priority():
|
def lower_priority():
|
||||||
os.nice(PROCESS_PRIORITY_LOW)
|
os.nice(PROCESS_PRIORITY_LOW)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user