Kodeord: Det svageste led

Vi ved alle sammen at den eneste forsvarlige måde at opbevare kodeord er ved at salte og hashe dem. Men hvor mange har egentlig sat sig ned og tænkt over sikkerheden?

Den normale metode er at vælge en tilfældig salt af den rette længde og så beregner man:

hash = H(<salt> + <password>)

I databasen over kodeord gemmer man så paret bestående af "salt" og "hash". Når man skal validere et kodeord henter man "salt" fra databasen, beregner "hash" og sammenligner med den værdi man har i databasen. Hvis de to værdier er ens er kodeordet korrekt.

Hvad er det svageste led?

For at gennemføre valideringen skal kodeordet overføres fra klienten til serveren. Et simpelt angreb består simpelthen i at lytte med her. Det kan enten ske ved at sniffe netværket eller ved at hooke ind i enten serveren eller klienten. Hermed kan vi opsamle nok information til for fremtiden at kunne udgive os for at være brugeren.

Vi kan nogenlunde beskytte os mod at nogen lytter med på netværket med TLS, men de fleste webapplikationer er hullet som en si og client-side håndtering af en HTML formular tør jeg slet ikke stole på. (HTTP basic auth er på godt og ondt noget bedre).

Et andet angreb er at hente hele databasen med kodeord. Alt for ofte er databasen bare en flad fil eller en simpel tabel i en relationel database med fuld læseadgang til. Herefter kan vi i ro og mag sidde og gætte kodeord. Det lyder omstændigt, men computere er rigtig gode til det især hvis de får lidt hjælp af en smart programmør.

Det er her salting kommer ind. Uden et salt ville jeg kunne tagen hashen af mit gæt og med det samme se hvormange der brugte det som kodeord. Ved at tilføje et salt skal jeg tjekke individuelt for hver enkelt salt. Bemærk at hvis jeg kun skal bryde et enkelt kodeord, så har salting ingen effekt. Salting øger kun besværligheden med det antal kodeord jeg forsøger at bryde.

Jo mere beregningskraft en angriber har, jo mere ligegyldig er salting. Det kan ikke løses ved bare at øge længden af sine salt, det er antallet af forskellige salts der har betydning og det er besværligt at gøre større end antallet af brugere. Med den simple metode beskrevet ovenover er øges antallet af effektive salts i øvrigt ikke ud over to gange blokstørelsen af ens hash-algortime.

I teorien betragter jeg netværkslaget som det svageste led, men i praksis er mange angreb foretaget ved at man får adgang til hele databasen over hashes og salts. I dag er der nok beregningskraft til at man ved kvalificeret gætning kan bryde mange kodeord på overkommelig tid.

Luk hullet

Den mest nærliggende løsning er at isolere al håndtering af kodeord i et system der er overkommeligt at gøre sikkert. Det kunne for sekemple være i en relationel database hvor webapplikationen ikke har læseadgang, men kun har adgang til to stored procedures: validate_password(password) og change_password(old_password, new_password).

Hermed kan vi reelt forhindre at en angriber bare downloader hele databasen og angriber den i ro og mag.

Tænk uden for kassen

Men hvis vi nu har sikret vores database så listen af salts og hashes er utilgængelig, hvorfor så ikke tænke uden for kassen og gøre op med hele ideen om at det er den eneste forsvarlige måde at gemme kodeord?

Ved at gemme det ikke-hashede kodeord på serveren kan vi implementere en gensidig challenge-respons protokol. I stedet for at lade serveren generere én salt, skal både serveren og klienten generere en challenge hver gang vi skal validere et kodeord. Klienten beviser så at den kender kodeordet ved at beregne:

hash = H( <server challenge> + <client challenge> + <password>)

og serveren beviser at den kender kodeordet ved at beregne:

hash = H(<client challenge> + <server challenge> + <password>)

Hermed kan både serveren og klienten sikre sig at den anden part kender kodeordet uden at selve kodeordet bliver sendt mellem server og klient. Dermed har vi mindsket effekten af en kompromiteret server.

Der er bare en lille besværlig detalje ved HTTP, som nok er den mest udbredte protokol. HTTP indeholder ikke en tilstand der gør at serveren let kan holde styr på hvilken challenge den har sendt til hvilken klient. Som udgangspunkt kan en angriber bare komme og sige "Du gav mig denne challenge og jeg har fået det til denne hash" og genbruge værdier man har sniffet på netværket.

Løsningen er at vælge vores challenge med omhu på en måde så vi kan genkende dem. En klassisk metode er at vælge noget der består af noget tilfældigt, et tidsstempel og noget der identificere klienten og så signere det kryptografisk. Når en klient så påstår at den har fået en given challenge kan vi tjekke at vi virkelig har givet den challenge og at vi har gjort det for nyligt. Dette reducere kraftigt muligheden for at en angriber kan genbruge de informationer han har sniffet på netværket.

Lige som i det tidligere eksempel skal al kode der håndtere challenge og kodeord isoleres i en service der er overkommelig at gøre siker. Det vil sigen enten som stored procedures i en sikker database eller som en selvstændig webservice. På denne måde forhindrer vi at en angriber bare kan hooke sig ind i webapplikationen der kører på serveren. En angriber kan dog stadigvæk installere en bagdør, men denne virker kun sålænge den ikke bliver fjernet.

Det svageste led er stadigvæk at det er forholdsvist nemt at gætte et kodeord, givet et sæt challenges og en hash. Men vi har reduceret det til at kun give adgang til brugere der logger ind i en bestemt periode.

Løsningen er at gøre det mere besværligt at beregne hashen for et enkelt kodeord. Ikke så besværligt at det tager for længe bare at beregne et hashen for et enkelt kodeord, men så besværligt at det er uoverkommeligt at beregne hashen for tusinder af kodeord. De mest udbredte hash-algoritmer er SHA-1 og SHA-2. Ironisk nok er et af designkriterierne for disse algoritmer at de skal være lette at implementere effektivt. En nærliggende mulighed er at vælge nogle andre og mindre effektive algoritmer. Men jeg kender ikke nogle alternativer der både er garantret langsomme og garanteret sikre.

En anden mulighed er at tage brugerens kodeord der er let at gætte og på en besværlig måde transformere det til noget der uigenkendeligt ligner 512 bits støj. Dette kan gøres ved at bruge hash-algoritmen tusindevis af gange efter et bestemt mønster, for eksempel som bekrevet af PBKDFv2-algortimen.

Fremtiden?

Ved mange af de angreb vi ser i vore dage har folk brugt salting og hashing. Det er min overbevisning at vi efterhånden har nået enden for at salting og hashing yder en tilstrækkelig beskyttelse i sig selv. Derimod skal vi fokusere mere på at beskytte selve databasen med kodeord og beskytte mod at kompromiterede applikationer i stilhed kan sniffe ubeskyttede kodeord. Der er to udfordringer der skal løses: En teknisk og en holdningsmæssig.

Den tekniske løsning er at meget at den kode der i dag håndtere kodeord på klientsiden er utroværdig javascript-kode. Fremtidens browser må indeholde et HTML-baseret password-felt der implementere en ordentlig challenge-response protokol uden at kunne kompromiteres af tilfældig javascript-kode der inkluderes på siden. Mig bekendt er der ikke tiltag til dette i HTML5, men jeg hører gerne om det hvis jeg tager fejl.

Det holdningsmæssige spørgsmål er at vi skal ud over refleksspørgsmålet om en service salter og hasher kodeord før de bliver gemt på serveren. For det første er det ikke længere nok, selv med saltning og hashing skal kodeord behandles med stor sikkerhed. For det andet er der nogle muligheder for at forøge sikkerheden, hvis vi tillader serveren at gemme mere end bare de saltede og hashede kodeord. Det er dog en klar forudsætning at ens database er sikret bedre end hvad standarden er i dag.

Kommentarer (20)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
#2 Morten Jensen

En nærliggende mulighed er at vælge nogle andre og mindre effektive algoritmer. Men jeg kender ikke nogle alternativer der både er garantret langsomme og garanteret sikre.

Tjek Bcrypt: http://en.wikipedia.org/wiki/Bcrypt

Designet til at være langsom og sikker, og du kan indstille en load factor, så du kan bestemme hastigheden i nogle halvgrove trin. Så kan du selv bestemme om det skal tage nano-, mikro- eller milisekunder at logge ind ;)

  • 2
  • 0
#7 Michael Olesen

(prøver lige igen)

Når man skal validere et kodeord henter man "salt" fra databasen, beregner "hash" og sammenligner med den værdi man har i databasen.

Hvis man gemmer sin salt i databasen, så kunne man (næsten) lige så godt gemme kodeordene uden salt, hvis man bruger en af de "normal" hash-algoritmer.

  • 0
  • 1
#11 Bjarke Walling

Jeg har set nogen tilføje et "pepper", som er gemt i applikationskoden – saltet er i databasen. Det leder frem til spørgsmålet om det er marginalt mindre sandsynligt at applikationskoden og databasen bliver lækket samtidigt end hver for sig. I mange tilfælde er det nok ikke, hvis det hele er hostet på én maskine.

  • 0
  • 0
#12 Jens Christian Hillerup

Hvis man gemmer sin salt i databasen, så kunne man (næsten) lige så godt gemme kodeordene uden salt, hvis man bruger en af de "normal" hash-algoritmer.

Det passer ikke. Hvis man ikke gemmer saltet, så kan man ikke verificere passwordet. Og det er ingen risiko at alle og enhver kender saltet, da dets eneste formål er at vinde over angreb som code books (i mindre grad fordi de sjældent er hukommelsesmæssigt "feasible") og rainbow tables. Det beskytter ikke mod brute force-angreb.

Men du har ret i at en H(salt + password)-model er mindre sikkert end hvis man fx vælger en key derivation function som PBKDF2, bcrypt eller scrypt. Men sikkerheden kommer i, at det er lynhurtigt at udregne det første, mens det tager faktor flere tusind længere at udregne en PBKDF2. Denne faktor er negligibel når man bare skal tjekke ét password, men det bliver noget sværere at tjekke millionvis af passwordkandidater i et brute-force-angreb.

  • 0
  • 0
#19 Peter Makholm Blogger

Der var lige nogen der delte A Penetration Tester's Guide to IPMI and BMCs. Et af de mere "interessante" angreb på IPMI version 2.0 bliver beskrevet således:

In short, the authentication process for IPMI 2.0 mandates that the server send a salted SHA1 or MD5 hash of the requested user's password to the client, prior to the client authenticating. You heard that right - the BMC will tell you the password hash for any valid user account you request. This password hash can broken using an offline bruteforce or dictionary attack. Since this issue is a key part of the IPMI specification, there is no easy path to fix the problem, short of isolating all BMCs into a separate network.

Så spørger jeg bare, hvad tænker man når man designer sådan en feature ind i en protokol‽

  • 1
  • 0
Log ind eller Opret konto for at kommentere