fix(auth): enumeration, verrou, deconnexion en GET, redirection ouverte
Quatre constats de la liste des gains rapides, tous sur auth.py. SEC-017 -- enumeration de comptes Le formulaire repondait "il vous reste 3 tentative(s)" a un compte connu et "verifiez le nom d utilisateur et le mot de passe" a un inconnu. Le decompte lui-meme etait la fuite : la meme information, etalee sur cinq requetes. S y ajoutait un ecart de temps de reponse, check_password n etant appele que si la ligne existait -- scrypt est cher, l ecart est mesurable. Un seul message pour tous les echecs, et la verification s execute desormais sur les deux branches : contre un hachage aleatoire tire une fois par processus quand l identifiant n existe pas. SEC-018 -- verrou de compte declenchable par un tiers Cinq mauvaises reponses mettaient un compte connu hors service pendant quinze minutes, indefiniment renouvelables. Sur un compte president, c est toute l administration, et aucun ecran ne permettait de defaire. Le compteur et la fenetre restent -- ce sont la trace qu un administrateur lit quand un compte est pilonne, et la fenetre double jusqu a un plafond. Ce qui change : de bons identifiants passent, fenetre ouverte ou non, et remettent le compteur a zero. Le proprietaire du compte ne peut plus etre bloque par un tiers. Ce que cela coute, dit franchement : un verrou dur n arretait de toute facon pas un attaquant ayant trouve le mot de passe -- il lui suffisait d attendre. Le debit de tentatives reste borne par la limite de 10/minute par IP. Une limite par couple (compte, IP) demanderait un stockage dedie ; elle attend Alembic. L evenement account.locked devient account.throttled : "locked" affirmait plus que ce qui se passe. SEC-019 -- deconnexion en GET /auth/logout n avait pas de methods, donc GET, donc hors protection CSRF : n importe quelle page pouvait deconnecter un visiteur avec une balise img. La route passe en POST et l entree de navigation devient un formulaire avec jeton. Le style suit -- les regles .nav-links visaient les liens seuls. SEC-020 -- validation de redirection is_safe_url interrogeait urlparse().netloc. urlparse lit /\evil.com comme un chemin, sans netloc ; plusieurs navigateurs normalisent l antislash en barre oblique avant de resoudre, ce qui en fait //evil.com. La fonction refuse maintenant antislash et caracteres de controle, exige un chemin enracine, et compare l origine explicitement. Le xfail(strict) qui documentait SEC-017 est leve. 40 tests dans test_auth_session.py, dont la table des cibles refusees. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -138,7 +138,10 @@ a:hover { color: var(--primary-dark); }
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.nav-links li a {
|
||||
/* Logging out is a POST, so its nav entry is a button inside a form
|
||||
rather than a link. It has to read as one of the entries above it. */
|
||||
.nav-links li a,
|
||||
.nav-links li .nav-form button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -148,7 +151,17 @@ a:hover { color: var(--primary-dark); }
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.nav-links li a:hover, .nav-links li a.active {
|
||||
.nav-links li .nav-form button {
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: 0;
|
||||
font-family: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-links li a:hover, .nav-links li a.active,
|
||||
.nav-links li .nav-form button:hover {
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: white;
|
||||
}
|
||||
@@ -158,7 +171,8 @@ a:hover { color: var(--primary-dark); }
|
||||
padding-left: 17px;
|
||||
}
|
||||
|
||||
.nav-links li a i { width: 20px; text-align: center; font-size: 1.1rem; }
|
||||
.nav-links li a i,
|
||||
.nav-links li .nav-form button i { width: 20px; text-align: center; font-size: 1.1rem; }
|
||||
|
||||
.nav-divider {
|
||||
height: 1px;
|
||||
|
||||
Reference in New Issue
Block a user