#!/usr/bin/env python3 """ Pfad-Validierung für Template-Dateien. Verhindert Path-Traversal-Angriffe und validiert, dass angeforderte Pfade innerhalb des erlaubten Verzeichnisses liegen. """ import os from typing import Optional from urllib.parse import unquote, urlparse class PathValidator: """Validiert und auflöst URL-Pfade zu Dateisystem-Pfaden.""" def __init__(self, root_dir: str, web_dir: str): """ Args: root_dir: Projekt-Root-Verzeichnis (enthält templates/) web_dir: web/-Verzeichnis (enthält templates.json) """ self.root_dir = root_dir self.web_dir = web_dir def resolve_template_path(self, path: str) -> Optional[str]: """ Resolve einen Request-Pfad zu einem Dateisystem-Pfad. Gibt None zurück, falls der Pfad ungültig oder nicht autorisiert ist. Beispiele: /templates.json -> /templates.json /templates/system/foo.json -> /templates/system/foo.json """ parsed = urlparse(path).path path = parsed # /templates.json ist ein Sonderfall - exakter Pfad im web/-Verzeichnis if path == "/templates.json": return os.path.join(self.web_dir, "templates.json") # Nur /templates/* Pfade erlauben if not path.startswith("/templates/"): return None # Extrahiere den relativen Pfad nach /templates/ rel_path = path[len("/templates/") :] rel_path = unquote(rel_path) # Ablehnen bei leeren Pfaden oder absoluten Pfaden if not rel_path or os.path.isabs(rel_path): return None # Ablehnen bei '..' Sequenzen (vor der Normalisierung!) if ".." in rel_path.split(os.sep): return None # Pfad normalisieren normalized_rel = os.path.normpath(rel_path) # Nach Normalisierung nochmal prüfen if normalized_rel.startswith("..") or os.path.isabs(normalized_rel): return None # Vollständigen Pfad konstruieren full_path = os.path.join(self.root_dir, "templates", normalized_rel) templates_base = os.path.realpath(os.path.join(self.root_dir, "templates")) full_path_resolved = os.path.realpath(full_path) if ( not full_path_resolved.startswith(templates_base + os.sep) and full_path_resolved != templates_base ): return None if os.path.islink(full_path_resolved): return None return full_path_resolved