Automatischer Push: 19 Datei(en) geändert
- .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/rot_nachweis.py - .vault-tools/plan-kopie/welle_vergleich.py - .vault-tools/plan-kopie/welle_vergleich_treiber.py - .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/angriffe.py - .vault-tools/trading-plattform-pruefung/browserlauf.py - .vault-tools/trading-plattform-pruefung/browserlauf_zugang.py - .vault-tools/trading-plattform-pruefung/c_eigenlauf.py - .vault-tools/trading-plattform-pruefung/d_eigenlauf.py - .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py - .vault-tools/trading-plattform-pruefung/ntfy_probe_server.py - … und 7 weitere Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
371a1f5d2a
commit
395642c480
19 files changed
+1636
-32
No files matched your search
@@ -10,14 +10,25 @@ Geltungsbereich (Herkunft jeder Erwartung: Projektvorgabe, Spec Teil 10, Abschni
|
||||
Wiederholung eines TOTP-Codes, Abmeldung entwertet das Cookie, WebSocket ohne Cookie und mit
|
||||
fremdem Origin abgelehnt, Bremse (429 mit Retry-After, auch fuer die richtige Anmeldung),
|
||||
Anmeldeprotokoll ohne Passwort und Code, /api/paper mit Protokollpfad ausserhalb des Ordners.
|
||||
Was es nicht sieht: TLS und das Secure-Merkmal (das Probe-Backend spricht HTTP), DNS-Rebinding im
|
||||
echten Browser, Zeitverhalten der Passwortpruefung, alles hinter der Anmeldung ausser einem GET und
|
||||
einem POST je Fall, und ob die CSP die gebaute Oberflaeche wirklich traegt (dafuer der Browser-Lauf).
|
||||
- seit 2026-09-18 abhaengig vom Schema der Basis-URL (Herkunft: Projektvorgabe, Spec Teil 10 Teil B
|
||||
und trading/dashboard/sicherheit.py: secure=tls am Cookie, Strict-Transport-Security nur mit TLS):
|
||||
unter https traegt das Sitzungscookie Secure und jede Antwort Strict-Transport-Security mit
|
||||
positivem max-age, und eine Klartext-Anfrage an denselben Port liefert keine HTTP-Antwort 200;
|
||||
unter http traegt das Cookie kein Secure und keine Antwort Strict-Transport-Security.
|
||||
Was es nicht sieht: ob das Zertifikat taugt (es vertraut der mit --ca genannten Datei, bei einem
|
||||
selbstsignierten Zertifikat also dem Zertifikat selbst), Protokollversionen und Chiffren von TLS,
|
||||
TLS-Terminierung vor dem Dashboard (dann sieht die Anwendung HTTP und setzt weder Secure noch HSTS
|
||||
-- das meldet dieser Lauf unter https zu Recht als Befund), DNS-Rebinding im echten Browser,
|
||||
Zeitverhalten der Passwortpruefung, alles hinter der Anmeldung ausser einem GET und einem POST je
|
||||
Fall, und ob die CSP die gebaute Oberflaeche wirklich traegt (dafuer der Browser-Lauf).
|
||||
Regressionsnachweis: gegen das alte Probe-Backend ohne Anmeldung (start_probe_backend.py) muss der
|
||||
Lauf mit Exit 2 enden und die Schutzfaelle einzeln rot zeigen.
|
||||
Lauf mit Exit 2 enden und die Schutzfaelle einzeln rot zeigen. Fuer die https-Faelle: gegen ein
|
||||
TLS-Backend, dessen sicherheit.py weder Secure noch HSTS setzt (Mutant im Ziel), muessen genau
|
||||
diese Faelle rot sein.
|
||||
|
||||
Aufruf (in WSL, Venv des Projekts):
|
||||
python angriffe.py <basis-url> <ablageordner des probe-backends> [--ohne-bremse]
|
||||
python angriffe.py <basis-url> <ablageordner des probe-backends> [--ohne-bremse] [--ca <pem>]
|
||||
--ca nennt die Zertifikatsdatei, der bei https vertraut wird (selbstsigniertes Zertifikat).
|
||||
Exit 0 ohne Befund, 2 mit Befund, 3 wenn eine Voraussetzung fehlt (auch das ist ein Befund).
|
||||
"""
|
||||
|
||||
@@ -25,6 +36,8 @@ import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
@@ -66,6 +79,17 @@ def main() -> int:
|
||||
mit_bremse = "--ohne-bremse" not in sys.argv[3:]
|
||||
host = basis.split("://", 1)[1]
|
||||
origin = basis
|
||||
tls = basis.startswith("https://")
|
||||
|
||||
if "--ca" in sys.argv[3:]:
|
||||
stelle = sys.argv.index("--ca")
|
||||
if stelle + 1 >= len(sys.argv) or not Path(sys.argv[stelle + 1]).is_file():
|
||||
print("VORAUSSETZUNG FEHLT: --ca braucht eine vorhandene Zertifikatsdatei")
|
||||
return 3
|
||||
# requests liest REQUESTS_CA_BUNDLE, der WebSocket-Client baut seinen Kontext ueber
|
||||
# ssl.create_default_context(), das SSL_CERT_FILE liest. Pruefen statt abschalten.
|
||||
os.environ["REQUESTS_CA_BUNDLE"] = sys.argv[stelle + 1]
|
||||
os.environ["SSL_CERT_FILE"] = sys.argv[stelle + 1]
|
||||
|
||||
zugang_json = ablage / "zugang.json"
|
||||
if not zugang_json.is_file():
|
||||
@@ -133,6 +157,25 @@ def main() -> int:
|
||||
pruefe(f"{pfad}: nosniff", kopf.get("x-content-type-options") == "nosniff", str(kopf.get("x-content-type-options")))
|
||||
pruefe(f"{pfad}: Referrer-Policy", kopf.get("referrer-policy") == "no-referrer", str(kopf.get("referrer-policy")))
|
||||
pruefe(f"{pfad}: kein Server-Kopf", "server" not in kopf, str(kopf.get("server")))
|
||||
for pfad in ("/", "/api/gesund", "/api/uebersicht"):
|
||||
hsts = requests.get(f"{basis}{pfad}", timeout=5).headers.get("strict-transport-security")
|
||||
if tls:
|
||||
treffer = re.fullmatch(r"max-age=(\d+)(;.*)?", hsts or "")
|
||||
pruefe(
|
||||
f"{pfad}: Strict-Transport-Security mit positivem max-age (HTTPS)",
|
||||
treffer is not None and int(treffer.group(1)) > 0,
|
||||
str(hsts),
|
||||
)
|
||||
else:
|
||||
pruefe(f"{pfad}: kein Strict-Transport-Security (HTTP)", hsts is None, str(hsts))
|
||||
if tls:
|
||||
klartext = "http://" + host
|
||||
try:
|
||||
offen = requests.get(f"{klartext}/api/gesund", timeout=5)
|
||||
ergebnis_klartext = f"HTTP {offen.status_code}"
|
||||
except requests.RequestException as ausnahme:
|
||||
ergebnis_klartext = f"keine Antwort: {type(ausnahme).__name__}"
|
||||
pruefe("Klartext-Anfrage an den TLS-Port liefert kein 200", ergebnis_klartext != "HTTP 200", ergebnis_klartext)
|
||||
api_kopf = requests.get(f"{basis}/api/gesund", timeout=5).headers
|
||||
pruefe("/api/gesund: Cache-Control no-store", api_kopf.get("cache-control") == "no-store", str(api_kopf.get("cache-control")))
|
||||
|
||||
@@ -169,7 +212,11 @@ def main() -> int:
|
||||
pruefe("Cookie HttpOnly", "httponly" in klein, setzen[-80:])
|
||||
pruefe("Cookie SameSite=Strict", "samesite=strict" in klein, setzen[-80:])
|
||||
pruefe("Cookie Path=/", "path=/" in klein, setzen[-80:])
|
||||
pruefe("Cookie ohne Secure (HTTP)", "secure" not in klein, setzen[-80:])
|
||||
merkmale = [teil.strip() for teil in klein.split(";")]
|
||||
if tls:
|
||||
pruefe("Cookie mit Secure (HTTPS)", "secure" in merkmale, setzen[-80:])
|
||||
else:
|
||||
pruefe("Cookie ohne Secure (HTTP)", "secure" not in merkmale, setzen[-80:])
|
||||
|
||||
wieder = requests.post(f"{basis}/api/anmeldung", json={**rumpf, "code": code}, headers=kopf, timeout=10)
|
||||
pruefe("derselbe Code ein zweites Mal 401", wieder.status_code == 401, f"HTTP {wieder.status_code}")
|
||||
|
||||
Reference in new issue
Block a user