diff --git a/web/serve.py b/web/serve.py index fa2d725..ac85d90 100755 --- a/web/serve.py +++ b/web/serve.py @@ -28,20 +28,54 @@ def find_free_port(start_port=9000): class Handler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=DIRECTORY, **kwargs) + + def _validate_template_path(self, path): + """Validiert, dass der Pfad auf Schritte Beschränkt ist und innerhalb von ROOT_DIR/templates/ oder DIRECTORY liegt.""" + # /templates.json ist ein Sonderfall - exakter Pfad im web/-Verzeichnis + if path == '/templates.json': + return os.path.join(DIRECTORY, 'templates.json') + + # Nur /templates/* Pfade erlauben + if not path.startswith('/templates/'): + return None + + # Extrahiere den relativen Pfad nach /templates/ + rel_path = path[len('/templates/'):] # 'system/test.json' oder '../etc/passwd' + + # 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(ROOT_DIR, 'templates', normalized_rel) + + # Explizite Prüfung, dass der Pfad innerhalb von ROOT_DIR/templates/ liegt + templates_base = os.path.abspath(os.path.join(ROOT_DIR, 'templates')) + full_path_abs = os.path.abspath(full_path) + + if not full_path_abs.startswith(templates_base + os.sep) and full_path_abs != templates_base: + return None + + return full_path_abs def do_PUT(self): # Nur PUT auf /templates/* Pfade erlauben - if not self.path.startswith('/templates'): - self.send_error(403, "Method not allowed for this path") + file_path = self._validate_template_path(self.path) + if file_path is None: + self.send_error(403, "Forbidden: Invalid path") return - # /templates.json liegt im web/-Verzeichnis (Katalog), alles andere unter ROOT - if self.path == '/templates.json': - file_path = os.path.join(DIRECTORY, 'templates.json') - else: - rel_path = self.path[1:] # '/templates/system/test.json' → 'templates/system/test.json' - file_path = os.path.join(ROOT_DIR, rel_path) - # Verzeichnis prüfen - muss existieren file_dir = os.path.dirname(file_path) if not os.path.exists(file_dir) or not os.path.isdir(file_dir): @@ -81,15 +115,8 @@ class Handler(http.server.SimpleHTTPRequestHandler): return super().do_GET() # Anfragen für /templates.json oder /templates/* umleiten - if self.path.startswith('/templates'): - # /templates.json ist der Katalog und liegt im web/-Verzeichnis, - # /templates/... verweist auf Template-Dateien im Projekt-Root - if self.path == '/templates.json': - file_path = os.path.join(DIRECTORY, 'templates.json') - else: - rel_path = self.path[1:] # '/templates/system/x.json' → 'templates/system/x.json' - file_path = os.path.join(ROOT_DIR, rel_path) - + file_path = self._validate_template_path(self.path) + if file_path is not None: if os.path.exists(file_path) and not os.path.isdir(file_path): try: with open(file_path, 'rb') as f: