81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
#!/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 -> <web_dir>/templates.json
|
|
/templates/system/foo.json -> <root_dir>/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
|