feat(auth): Durchgang durch Anmeldung und Sitzungen nach OWASP, v1.8.0
CI / pruefen (push) Canceled after 0s
CI / pruefen (push) Canceled after 0s
Geprueft gegen ASVS und die Cheat Sheets zu Authentication, Session Management und Password Storage. Das meiste stand schon gut da – Token mit 256 Bit und nur als Hash in der Datenbank, serverseitige Pruefung bei jeder Anfrage, Sitzungen weg beim Passwortwechsel, TOTP mit Replay-Schutz. Fuenf Punkte blieben. **Aufwand beim Hashen.** N war 2^15 bei p=1. Das Password Storage Cheat Sheet nennt 2^17/p=1 als Minimum und listet als gleichwertig unter anderem 2^16/p=2 und 2^15/p=3 – mit p=1 lag 2^15 unter *allen* zulaessigen Kombinationen und war rund dreimal billiger zu knacken als vorgesehen. Jetzt 2^17/p=1, gemessen 0,5 s und 128 MiB je Pruefung. Der Speicher ist gedeckelt: Node rechnet scrypt im libuv-Threadpool, also hoechstens vier parallel. **Neuhashen bei der Anmeldung.** Ohne das blieben bestehende Passwoerter fuer immer auf den alten Parametern – anheben laesst er sich nur dort, wo das Klartextpasswort vorliegt. needsRehash vergleicht den Gesamtaufwand N*r*p, nicht Feld fuer Feld, damit gleichwertige Kombinationen nicht grundlos neu gehasht werden. **Aufzaehlbarkeit.** Drei Zweige beantworteten sich *vor* der Passwort- pruefung und verrieten damit die Existenz eines Kontos: gesperrt (423), deaktiviert (403) und "kein Passwort hinterlegt, nimm einen Passkey" (401). Der einheitliche Text GENERIC war damit wirkungslos. Jetzt wird zuerst das Passwort geprueft; Klartext gibt es erst danach. Reine Passkey-Konten verbrennen dieselbe Rechenzeit, damit sich der Fall auch nicht an der Dauer zeigt. Der Preis ist der verlorene Hinweis "nimm den Passkey" – bewusst hingenommen. **Leerlauffrist.** lastSeenAt wurde gefuehrt, aber nie ausgewertet: ein abgegriffener Cookie galt bis zu 30 Tage, egal ob das Konto benutzt wurde. ASVS 3.3.2 will beide Grenzen. Acht Stunden, ueber SESSION_IDLE_HOURS einstellbar. Das Schreibintervall fuer lastSeenAt haengt jetzt an der Frist – bei fester Stunde haette eine kuerzere Frist sich selbst ausgehebelt. **Cookies.** SameSite Strict statt Lax, und bei https der Praefix "__Host-". Der laesst den Browser erzwingen, dass kein Domain-Attribut gesetzt ist – eine andere Subdomain kann den Cookie dann nicht ueberschreiben. Beides kostet einmalig eine neue Anmeldung. Dazu die Passwortpruefung: Wiederholungen, durchlaufende Zeichenfolgen und Passwoerter, die den Benutzernamen enthalten, werden abgelehnt. Zusammensetzungsregeln bleiben bewusst aus – ASVS 2.1.9 raet davon ab. Geprueft: 232 Unit-Tests (davon 12 neue zu Aufwand, Neuhashen und Mustern) und 30 E2E-Tests, darunter einer, der die Antwort auf ein stillgelegtes Konto gegen die auf ein erfundenes haelt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-2
@@ -22,8 +22,19 @@ sonst würden Konten ausgesperrt. Nach der Einrichtung ist `/setup` dauerhaft ge
|
||||
|
||||
### 0.2 Anmeldung
|
||||
|
||||
- **Benutzername und Passwort.** Mindestens 10 Zeichen, offensichtliche Passwörter werden
|
||||
abgelehnt. Hashing mit scrypt.
|
||||
- **Benutzername und Passwort.** Mindestens 10 Zeichen. Abgelehnt werden naheliegende
|
||||
Passwörter, reine Ziffernfolgen, zu wenig verschiedene Zeichen (`abababab`),
|
||||
durchlaufende Folgen (`abcdefgh`) und alles, was den eigenen Benutzernamen enthält.
|
||||
**Keine Zusammensetzungsregeln** — ASVS 2.1.9 rät ausdrücklich davon ab, weil sie zu
|
||||
`Passwort1!` führen statt zu Entropie.
|
||||
- **Hashing mit scrypt**, `N = 2^17, r = 8, p = 1` nach dem OWASP Password Storage Cheat
|
||||
Sheet (rund 0,5 s und 128 MiB je Prüfung). Die Parameter stehen im Hash, ältere bleiben
|
||||
also lesbar — und werden bei der nächsten Anmeldung still auf den aktuellen Stand
|
||||
gehoben. Das ist der einzige Moment, in dem das Klartextpasswort vorliegt.
|
||||
- **Gleiche Antwort für alles Fehlgeschlagene.** Ob ein Konto existiert, gesperrt,
|
||||
deaktiviert oder reines Passkey-Konto ist, sagt die Anmeldung erst, wenn das Passwort
|
||||
stimmt. Davor gibt es genau einen Text und eine vergleichbare Antwortzeit
|
||||
(`fakeVerifyDelay`), damit sich Benutzernamen nicht durchprobieren lassen.
|
||||
- **Passkey (WebAuthn).** Fingerabdruck, Gesichtserkennung oder Sicherheitsschlüssel. Die
|
||||
Anmeldung läuft über auffindbare Credentials, ein Benutzername ist also nicht nötig.
|
||||
Mehrere Passkeys pro Konto, benennbar und einzeln entfernbar.
|
||||
@@ -760,6 +771,16 @@ nicht verloren gehen. Indizes auf Status, Verbindung und Änderungsdatum.
|
||||
`GIT_BLOCK_PRIVATE` und `GIT_ALLOWED_HOSTS`.
|
||||
- **Rate-Limiting** für Anmeldung (je IP und je Konto), ausgehende Git-Aufrufe und
|
||||
WebAuthn-Optionen.
|
||||
- **Sitzungen**: Zufallstoken mit 256 Bit, in der Datenbank nur als SHA-256-Hash — wer die
|
||||
Datenbank liest, kann keine Sitzung übernehmen. Geprüft wird bei jeder Anfrage, Abmelden
|
||||
und Sperren wirken also sofort. Zwei Fristen nach ASVS 3.3.2: **absolut** 30 Tage ab
|
||||
Anmeldung (`SESSION_TTL_DAYS`) und **im Leerlauf** 8 Stunden (`SESSION_IDLE_HOURS`).
|
||||
Ein Passwortwechsel entwertet alle Sitzungen und stellt für das eigene Gerät eine neue
|
||||
aus.
|
||||
- **Cookies**: `HttpOnly`, `SameSite=Strict`, `Secure` sobald die App über HTTPS läuft, und
|
||||
dann zusätzlich mit dem Präfix `__Host-`. Der Präfix lässt den Browser selbst erzwingen,
|
||||
dass kein `Domain` gesetzt ist — eine andere Subdomain kann den Cookie damit nicht
|
||||
überschreiben und keine Sitzung unterschieben.
|
||||
- Access Tokens verschlüsselt mit **AES-256-GCM**, Schlüssel per **scrypt** aus
|
||||
`APP_SECRET` abgeleitet, Chiffrat versioniert (`v1.<iv>.<tag>.<data>`).
|
||||
- Tokens werden nie an den Browser gesendet — alle API-Antworten nutzen eine Feldauswahl
|
||||
|
||||
Reference in New Issue
Block a user