Direct naar inhoud

Onveilige opslag van wachtwoorden

CWE-916CWE-759CWE-257OWASP A02:2021Bijgewerkt 3 september 20265 min leestijd

Wachtwoorden horen te worden opgeslagen met een hashfunctie die bewust traag is en per gebruiker een eigen salt gebruikt: Argon2id, scrypt of bcrypt. Snelle hashes als MD5 en SHA-256 zijn met moderne hardware massaal terug te rekenen, en versleuteling is ongeschikt omdat de sleutel altijd ergens bereikbaar is.

Van alle gegevens in uw database is er één categorie die u zelf nooit hoeft te kunnen lezen: wachtwoorden. U hoeft alleen te kunnen vaststellen of iemand het juiste heeft ingevoerd. Dat onderscheid, verifiëren in plaats van terughalen, bepaalt hoe de opslag eruit moet zien. In dit artikel leest u waarom dat zo vaak misgaat en hoe u het herstelt zonder uw gebruikers ermee lastig te vallen.

Wat is onveilige wachtwoordopslag?

Onveilige opslag van wachtwoorden betekent dat de opgeslagen vorm het oorspronkelijke wachtwoord onvoldoende beschermt op het moment dat een aanvaller de database in handen krijgt. Dat is de aanname waarvan u moet uitgaan: niet óf de database ooit uitlekt, maar wat er dan gebeurt.

Er zijn drie varianten die in de praktijk voorkomen. De ernstigste is opslag in leesbare vorm, waarbij het wachtwoord letterlijk in een kolom staat. Daarna komt versleuteling, wat beter lijkt maar het niet is: versleuteling is omkeerbaar, dus er bestaat ergens een sleutel, en die staat in de praktijk op dezelfde server of in dezelfde configuratie. Verder ten slotte is er de meest voorkomende: hashen met een algoritme dat daarvoor niet geschikt is, zoals MD5, SHA-1 of SHA-256, met of zonder salt.

Dat laatste vraagt om uitleg, want die functies zijn niet “kapot”. Ze zijn ontworpen om snel te zijn, en dat is voor het controleren van bestandsintegriteit een deugd. Voor wachtwoorden is het een gebrek: een aanvaller met een gestolen tabel kan er met een gewone grafische kaart miljarden kandidaten per seconde doorheen jagen. Een wachtwoordhash moet juist traag zijn, traag genoeg om nauwelijks merkbaar te zijn bij één inlogpoging, en verlammend bij miljarden.

Hoe wordt onveilige wachtwoordopslag misbruikt?

Kwetsbaar:

// Snelle hash, vaste of ontbrekende salt
const crypto = require('node:crypto');

function bewaarWachtwoord(gebruikerId, wachtwoord) {
  const hash = crypto.createHash('sha256').update(wachtwoord).digest('hex');
  return db.query('UPDATE gebruikers SET hash = ? WHERE id = ?', [hash, gebruikerId]);
}

Twee problemen tegelijk. SHA-256 is te snel, en er is geen salt, waardoor identieke wachtwoorden identieke hashes krijgen. Dat laatste betekent dat een aanvaller in één blik ziet welke gebruikers hetzelfde wachtwoord hebben, en dat kant-en-klare tabellen met vooraf berekende hashes direct bruikbaar zijn. Een lijst van tien miljoen veelgebruikte wachtwoorden is op deze manier binnen enkele seconden tegen de hele database te vergelijken.

Veilig:

const argon2 = require('argon2');

// Parameters afstemmen op uw hardware: mikken op ongeveer 0,5 seconde per hash
const OPTIES = {
  type: argon2.argon2id,
  memoryCost: 19456,   // 19 MiB
  timeCost: 2,
  parallelism: 1,
};

async function bewaarWachtwoord(gebruikerId, wachtwoord) {
  const hash = await argon2.hash(wachtwoord, OPTIES);   // salt zit in de uitvoer
  await db.query('UPDATE gebruikers SET hash = ? WHERE id = ?', [hash, gebruikerId]);
}

async function controleer(gebruiker, wachtwoord) {
  if (!await argon2.verify(gebruiker.hash, wachtwoord)) return false;

  // Meteen meeliften op de gelegenheid om te migreren of te verzwaren
  if (argon2.needsRehash(gebruiker.hash, OPTIES)) {
    await bewaarWachtwoord(gebruiker.id, wachtwoord);
  }
  return true;
}

Argon2id is bewust traag én geheugenintensief, wat het aanvallen met grafische kaarten aanzienlijk duurder maakt. De salt wordt automatisch gegenereerd en in de uitvoerreeks opgenomen, dus u hoeft er geen aparte kolom voor bij te houden. De controle met needsRehash is het detail dat migraties pijnloos maakt: bij elke geslaagde inlogpoging wordt gekeken of de opgeslagen hash nog aan de huidige parameters voldoet, en zo niet, dan wordt hij stilzwijgend vervangen.

Is Argon2id niet beschikbaar, dan zijn scrypt en bcrypt goede alternatieven. Bij bcrypt geldt één aandachtspunt: invoer langer dan 72 bytes wordt afgekapt, dus hash lange wachtwoordzinnen eerst met SHA-256 voordat u ze aan bcrypt aanbiedt.

Slaat u wachtwoorden op in leesbare vorm of versleuteld, ga er dan van uit dat elk wachtwoord in de database gecompromitteerd is zodra die uitlekt. Omdat mensen wachtwoorden hergebruiken, raakt zo’n lek ook accounts van uw gebruikers bij andere diensten. Dat maakt het meldingsplichtig en zet uw organisatie in de positie dat u anderen schade hebt berokkend.

Wat is de impact van onveilige wachtwoordopslag?

De ernst is hoog tot kritiek, en dat onderscheidt deze bevinding van de meeste andere in deze categorie. De reden is dat het gevolg zich pas voordoet bij een ander incident, maar dan wel volledig is.

Lekt de database (via SQL-injectie, een verkeerd ingestelde back-up, een gestolen laptop of een medewerker met te ruime rechten) dan bepaalt uitsluitend de opslagvorm wat een aanvaller eraan heeft. Bij Argon2id met verstandige parameters kost het kraken van één sterk wachtwoord onbetaalbaar veel rekentijd. Bij MD5 zonder salt is de hele tabel binnen een dag omgezet naar leesbare wachtwoorden.

De schade blijft bovendien niet bij u. Omdat wachtwoordhergebruik wijdverbreid is, geven gekraakte wachtwoorden toegang tot e-mailaccounts, webwinkels en zakelijke systemen van diezelfde gebruikers. Een lek bij uw organisatie wordt daarmee een probleem voor derden, met bijbehorende meldplicht onder de AVG en een reputatieschade die verder reikt dan het incident zelf. Anders dan bij veel kwetsbaarheden is er achteraf ook weinig aan te doen: een gelekte hash kunt u niet terugnemen.

Hoe spoor je onveilige wachtwoordopslag op?

Dit is een bevinding die zelden van buitenaf zichtbaar is; ze komt aan het licht bij een broncode-review, een configuratiebeoordeling of een gesprek met het ontwikkelteam. Daarom hoort de vraag naar het gebruikte algoritme standaard in de scope van een beveiligingsonderzoek.

Er zijn wel indirecte signalen. Een applicatie die een maximumlengte van bijvoorbeeld zestien tekens hanteert of bijzondere tekens weigert, verwerkt het wachtwoord waarschijnlijk niet als ondoorzichtige invoer. Een wachtwoordherstelfunctie die het oude wachtwoord per e-mail toestuurt, bewijst dat het terugleesbaar is opgeslagen: een direct aantoonbare bevinding. Ook een opvallend snelle respons op een inlogpoging kan erop wijzen dat er geen kostbare hashfunctie wordt uitgevoerd.

Bij een review wordt daarnaast gekeken naar de parameters: een bcrypt met een te lage kostenfactor of een Argon2 met minimale geheugeninstellingen biedt veel minder dan het lijkt. En er wordt gecontroleerd of de vergelijking timingveilig gebeurt en of er een migratiepad bestaat naar zwaardere parameters. AssistSec neemt deze punten mee in een code review of configuratiebeoordeling, omdat een penetratietest van buitenaf hier per definitie beperkt zicht op heeft.

Hoe voorkom je onveilige wachtwoordopslag?

  • Gebruik Argon2id voor het hashen van wachtwoorden, met scrypt of bcrypt als alternatief.
  • Stem de parameters af op uw hardware; mik op ongeveer een halve seconde rekentijd per hash.
  • Gebruik een unieke, willekeurige salt per wachtwoord; moderne functies regelen dat zelf.
  • Sla wachtwoorden nooit leesbaar of versleuteld op, ook niet “tijdelijk” of in logbestanden.
  • Hash lange invoer eerst met SHA-256 wanneer u bcrypt gebruikt, vanwege de grens van 72 bytes.
  • Herbereken de hash bij elke geslaagde inlogpoging zodra de parameters of het algoritme zijn verzwaard.
  • Overweeg een peper in een sleutelkluis als extra laag bovenop de hash.
  • Verstuur nooit een bestaand wachtwoord per e-mail; werk uitsluitend met een kortlevende herstellink.
  • Vergelijk hashes met een timingveilige functie en log nooit de ingevoerde waarde bij een mislukte poging.

Bronnen

Veelgestelde vragen

Waarom is een snelle hash zoals SHA-256 ongeschikt?

Juist omdat hij snel is. Moderne grafische kaarten berekenen er miljarden per seconde, waardoor een aanvaller met een gestolen database enorme aantallen kandidaten kan uitproberen. Een wachtwoordhash hoort bewust traag en geheugenintensief te zijn, zodat elke poging meetbaar kost.

Moet ik de salt geheim houden?

Nee, een salt hoeft niet geheim te zijn; hij moet uniek zijn per wachtwoord. Zijn doel is voorkomen dat identieke wachtwoorden dezelfde hash krijgen en dat vooraf berekende tabellen bruikbaar zijn. Moderne functies als bcrypt en Argon2id nemen de salt gewoon op in de uitvoerreeks.

Hoe migreer ik van een verouderd algoritme?

Zonder gebruikers te hinderen: bereken bij de eerstvolgende geslaagde inlogpoging een nieuwe hash met het moderne algoritme en vervang de oude. Voor accounts die niet terugkomen kunt u de oude hash na een periode ongeldig maken en een wachtwoordherstel afdwingen.

Wat is een peper en heb ik die nodig?

Een peper is een geheime waarde die u aan het wachtwoord toevoegt vóór het hashen, en die u niet in de database bewaart maar in een sleutelkluis of omgevingsvariabele. Bij een lek van alleen de database zijn de hashes daarmee onbruikbaar. Het is een nuttige extra laag, geen vervanging van een goede hashfunctie.

Verwante artikelen

Druk op / om te zoeken · Esc