Security Nice-to-Have: Dockerfile, Magic-Bytes, Path-Traversal, TABLE_MAP, Deps
- Dockerfile: non-root user appuser, chown /data + /app - media_utils: validate_upload() Magic-Byte-Check (JPEG/PNG/GIF/WebP/MP4/WebM) - media_utils: safe_media_path() Path-Traversal-Schutz beim Löschen - diary/health/dogs: safe_media_path() statt os.path.join + lstrip - diary: validate_upload() vor jedem Medien-Upload - forum: _LIKE_TABLE dict statt dynamischer String-Interpolation - requirements: uvicorn 0.34, PyJWT 2.10.1, pydantic 2.10.6, bcrypt 4.3, httpx 0.28.1, anthropic 0.49 - SW by-v319, APP_VER 307
This commit is contained in:
parent
15f854d96c
commit
71e588a240
9 changed files with 100 additions and 29 deletions
|
|
@ -7,6 +7,62 @@ from typing import Tuple
|
|||
_HEIC_EXTS = {".heic", ".heif"}
|
||||
_VIDEO_EXTS = {".mov", ".avi", ".m4v"}
|
||||
|
||||
# Magic-Byte-Signaturen erlaubter Medientypen
|
||||
_IMAGE_MAGIC = [
|
||||
b'\xff\xd8\xff', # JPEG
|
||||
b'\x89PNG\r\n', # PNG
|
||||
b'GIF87a', # GIF87
|
||||
b'GIF89a', # GIF89
|
||||
]
|
||||
_VIDEO_MAGIC = [
|
||||
b'\x1a\x45\xdf\xa3', # WebM / MKV
|
||||
]
|
||||
|
||||
def validate_upload(data: bytes, filename: str) -> None:
|
||||
"""
|
||||
Prüft Magic Bytes des Upload-Inhalts gegen die erwartete Dateitype.
|
||||
Wirft ValueError bei Mismatch.
|
||||
Bilder (JPEG/PNG/GIF) und Videos (MP4/WebM/MOV) werden geprüft.
|
||||
HEIC/HEIF und reine Text-/JSON-Dateien werden übersprungen (Pillow/FFmpeg prüfen selbst).
|
||||
"""
|
||||
ext = os.path.splitext(filename or "")[1].lower()
|
||||
if not data:
|
||||
raise ValueError("Leere Datei.")
|
||||
|
||||
if ext in (".jpg", ".jpeg"):
|
||||
if not data[:3] == b'\xff\xd8\xff':
|
||||
raise ValueError("Datei ist kein gültiges JPEG.")
|
||||
elif ext == ".png":
|
||||
if not data[:6] == b'\x89PNG\r\n':
|
||||
raise ValueError("Datei ist kein gültiges PNG.")
|
||||
elif ext in (".gif",):
|
||||
if not (data[:6] in (b'GIF87a', b'GIF89a')):
|
||||
raise ValueError("Datei ist kein gültiges GIF.")
|
||||
elif ext in (".webp",):
|
||||
if not (data[:4] == b'RIFF' and data[8:12] == b'WEBP'):
|
||||
raise ValueError("Datei ist kein gültiges WebP.")
|
||||
elif ext in (".mp4",):
|
||||
# MP4: 4-Byte-Größe gefolgt von 'ftyp' oder 'mdat'
|
||||
if not (len(data) >= 8 and data[4:8] in (b'ftyp', b'mdat', b'moov', b'free')):
|
||||
raise ValueError("Datei ist kein gültiges MP4.")
|
||||
elif ext in (".webm",):
|
||||
if not data[:4] == b'\x1a\x45\xdf\xa3':
|
||||
raise ValueError("Datei ist kein gültiges WebM.")
|
||||
# HEIC, MOV, AVI, M4V: Pillow/FFmpeg prüfen beim Konvertieren
|
||||
|
||||
|
||||
def safe_media_path(media_dir: str, url: str) -> str | None:
|
||||
"""
|
||||
Konstruiert einen sicheren Dateipfad aus einer gespeicherten URL.
|
||||
Gibt None zurück wenn der Pfad außerhalb von media_dir liegt (Path-Traversal-Schutz).
|
||||
"""
|
||||
relative = url.lstrip("/media/").lstrip("/")
|
||||
candidate = os.path.realpath(os.path.join(media_dir, relative))
|
||||
real_base = os.path.realpath(media_dir)
|
||||
if not candidate.startswith(real_base + os.sep) and candidate != real_base:
|
||||
return None
|
||||
return candidate
|
||||
|
||||
|
||||
def to_jpeg_if_heic(data: bytes, filename: str) -> Tuple[bytes, str]:
|
||||
"""Convert HEIC/HEIF to JPEG; return (data, ext) unchanged for all other types."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue