Simovits

Cryptopals – del 5+0 av ?

(Del 1 Del “2” Del 3 (men egentligen 2) Del x (för något x i intervallet 2.5 ≤ x ≤ 4))

Det har till författarens kännedom inte skrivits en enda recension om denna bloggserie som inte varit oerhört positiv, så man får väl helt enkelt säga grattis till er läsare som nu blir bjudna på ännu en del! (Det faktum att författaren inte har kännedom om en enda recension överhuvudtaget gör inte påståendet mindre falskt.)

I denna (typ) femte del kommer vi att avsluta uppgifterna om ECB, börja titta på CBC, och sedan direkt börja prata om något helt annat… Vi kommer nämligen avsluta denna bloggpost med att beskriva sårbarheten Zerologon som blev aktuell för några månader sedan. (En uppmärksam läsare kanske nu förstår den underfundiga titeln. Det är påfrestande att vara så förtjusande skojig som man är eftersom förväntningarna på ens texter bara ökar och ökar, men med det skämtet avklarat så känns det som att vi redan nått komedi-kvotan för denna del. Dags att vara allvarliga.)

Challenge 13: ECB cut-and-paste

(Det här är egentligen en ganska lätt uppgift, men nedanstående förklaring blev ganska detaljerad och extremt långrandig – ursäkta på förhand…)
Låt oss bygga upp ett litet scenario.

  1. Säg att det finns en webbapplikationen på URL:en simovits.com/ecb där man kan logga in med användarnamn och lösenord.
  2. När man loggar in så sker någon form av autentisering som kollar att användarnamnet stämmer överens med det angivna lösenordet (den biten är inte intressant för detta tankeexperiment).
  3. Om lösenordet är korrekt så hämtar applikationen användarens profil från en databas, som enbart applikationen har åtkomst till, med hjälp av en funktion som kallas profile_for.

Exempel: Databasens information om användaren gss@simovits.com är att användaren har ett UID = 10 (låt oss för enkelhetens skull anta att detta är samma för alla användare) och att användarens behörighetsroll är “user”.
Funktionen profile_for hämtar denna information från databasen och returnerar följande sträng:

profile_for("gss@simovits.com") = "email=gss@simovits.com&uid=10&role=user"
   

Formatet att koda parametrar med tecknen “&” och “=” som i strängen ovan är standard och tolkas enligt:

{
    email : gss@simovits.com,
      uid : 10,
     role : user
}
   

Användaren skickas därefter vidare till URL:en
simovits.com/ecb?email=gss@simovits.com&uid=10&role=user
där applikationen kan läsa av inputdatat och se att användaren gss@simovits.com har UID = 10 och har “user”-behörigheter.

Anledning till varför man väljer att skicka denna information i URL:en kan exempelvis vara för att applikationen ska slippa kommunicera med databasen varje gång användaren interagerar med tjänsten – eller så består applikationen av flera separata delar där den bara den första delen har kontakt med databasen. Det är helt enkelt jättepraktiskt! Men… En dumming som ser detta kan inse att om man ändrar “role=user” till “role=admin” så skulle man troligtvis få administratörsbehörigheter och få tillgång till funktioner som vanliga användare saknar.

(Man kan också tänka sig att man lägger informationen i en sessions-cookie istället för i URL:en, och de tekniker som vi kommer beskriva skulle fungera för båda alternativen.)

Två potentiella brister som en illasinnad person skulle kunna försöka utnyttja är följande:

Risk 1: Ifall webbapplikationen tillåter de två tecknen “&” och “=” i användarnamnet så kan man skapa en användare med det inte särskilt charmanta användarnamnet “gss@simovits.com&role=admin“. Funktionen profile_for kommer även för denna användare returnera att användarens roll är “user” och har UID = 10:
profile_for(gss@simovits.com&role=admin) = "email=gss@simovits.com&role=admin&uid=10&role=user"
Denna sträng kan alltså tolkas som:

{
    email : gss@simovits.com,
     role : admin,
      uid : 10,
     role : user
}
   

I detta fall finns det alltså två möjliga tilldelningar för vilken roll som användaren har och beroende på implementation skulle det kunna vara så applikationen väljer den första av dessa, vilket skulle innebära att applikationen luras till att tro att användaren har rollen admin.

Denna brist kan motverkas genom att inte tillåta användarnamnet att innehålla något av tecknen “&” och “=“.

Risk 2: Manuell ändring av URL:en. Den förra bristen går enkelt att undvika genom att inte tillåta användarnamn som innehåller vissa specifika symboler. Men vad händer om en skurk väljer att istället manuellt ändra URL:en och själv navigerar till sidan:

simovits.com/ecb?email=gss@simovits.com&uid=10&role=admin

Enligt logiken skulle applikationen även i detta fall läsa ut att gss@simovits.com har admin-behörigheter.

Den förra bristen bestod av att man la in data i databasen som applikationen inte kunde tyda ordentligt. Den svaghet vi beskrev nu bygger istället på att manipulera data som applikationen skickar mellan sina olika delar. Det är ju inte heller så bra och visar på att det i grunden inte är en bra idé att skicka viktiga parametrar i klartext i en URL.

Detta problem kan dock “lösas” genom att tillämpa kryptologi! Spoiler: Vi skriver här “lösas” med citationstecken eftersom det med en dålig implementation inte är en vidare bra lösning.

Kryptologi-förbättring. Låt oss förbättra funktionen profile_for så att den returnerar en kryptering av strängen med en för användaren okänd nyckel. Exempel:

profile_for("gss@simovits.com") = kryptering("email=gss@simovits.com&uid=10&role=user") = VGl0dGEgaW50ZSEgQmFyYSBldHQgZXhlbXBlbC4uLg==
   

En sådan förbättring av funktionen innebär att applikationen istället för att skicka informationen i klartext istället skickar användaren vidare till URL:en:

simovits.com/ecb?VGl0dGEgaW50ZSEgQmFyYSBldHQgZXhlbXBlbC4uLg==

Därefter kan applikationen dekryptera denna sträng för att kunna avläsa användarens rättigheter. Notera att även om skurken vet strukturen på strängen innan krypteringen så går det inte längre att byta ut “user” mot “admin” eftersom man ej har tillgång till nyckeln som används vid krypteringen. Supersmart!

Frågan är bara: Vilken krypteringsalgoritm borde användas? I det senaste blogginlägget pratade vi mycket om AES i ECB-läge, så låt oss testa det! Spoiler: Det är ett exempel på en ytterst dålig implementation…

En kort en-menings-påminnelse om hur AES i ECB-läge fungerar:
En sträng delas in i block (delsträngar) av längd 16 – där det sista blocket paddas för att få korrekt längd – och vardera block krypteras med AES och en konstant nyckel.

Låt oss fundera över hur denna krypteringsalgoritm skulle tillämpas i vårt exempel. Strängen “email=gss@simovits.com&uid=10&role=user” delas in i tre block av längd 16 enligt nedan:

|0123456789abcdef|0123456789abcdef|0123456789abcdef|
|email=gss@simovi|ts.com&uid=10&ro|le=userXXXXXXXXX|
   

där vi använder tecknet “|” för att visualisera blocken och vi använt tecknet “X” för att beteckna paddingen på det sista blocket så att den blir precis 16 tecken lång. Dessa tre block krypteras därefter var och en med AES och den hemliga nyckeln.

Svagheten med ECB är att blocken krypteras oberoende av varandra. Detta faktum kan nämligen utnyttjas i en klipp-och-klistra-attack!

Klipp-och-klistra-attacken. Låt oss skapa tre nya användare i systemet:

1) AAAAAAAAAAAAAgss@simovits.com, 
2) AAAAAAAAAAadmin, 
3) AAAAAAAAAAAAAA
   

Dessa användare kommer också skapas med “user”-behörigheter. Så vad händer när man tar profile_for för dessa?

1) profile_for("AAAAAAAAAAAAgss@simovits.com"):
– Strängen blir: email=AAAAAAAAAAAAgss@simovits.com&uid=10&role=user
– Blocken blir:

|0123456789abcdef|0123456789abcdef|0123456789abcdef|0123456789abcdef|
|email=AAAAAAAAAA|AAAgss@simovits.|com&uid=10&role=|userXXXXXXXXXXXX|
   

Följande är ganska osannolikt, men låt oss för enkelhetens skull säga att krypteringen av dessa block med den hemliga nyckeln är:

AES("email=AAAAAAAAAA")="1111111111111111",
AES("AAAgss@simovits.")="2222222222222222",
AES("com&uid=10&role=")="3333333333333333",
AES("userXXXXXXXXXXXX")="4444444444444444".
   

Med andra ord, vi antar att krypteringen av strängen

email=AAAAAAAAAAAAAgss@simovits.com&uid=10&role=userXXXXXXXXXXXX

blir strängen

1111111111111111222222222222222233333333333333334444444444444444

vilket alltså också blir resultatet av anropet profile_for("AAAAAAAAAAAAgss@simovits.com"). Denna krypterade sträng kommer vi som användare få ta del av eftersom vi kan avläsa URL:en som vi skickas vidare till, men nyckeln som använts för krypteringen har vi ej tillgång till.

2) profile_for("AAAAAAAAAAadminXXXXXXXXXXX"):
– Strängen blir: “email=AAAAAAAAAAadmin&uid=10&role=user
– Blocken blir:

|0123456789abcdef|0123456789abcdef|0123456789abcdef|
|email=AAAAAAAAAA|admin&uid=10&rol|e=userXXXXXXXXXX|
   

Vi fortsätter med de osannolika krypteringsresultaten och antar att

AES("email=AAAAAAAAAA")="1111111111111111",
AES("admin&uid=10&rol")="5555555555555555",
AES("e=userXXXXXXXXXX")="6666666666666666".
   

Det vill säga:

profile_for("AAAAAAAAAAadminXXXXXXXXXXX") = "111111111111111155555555555555556666666666666666"
   

3) profile_for("AAAAAAAAAAAAAA"):
– Strängen blir AAAAAAAAAAAAAA&uid=10&role=user
– Blocken blir:

|0123456789abcdef|0123456789abcdef|0123456789abcdef|
|email=AAAAAAAAAA|AAAA&uid=10&role|=userXXXXXXXXXXX|
   

Som en slump antar vi även här att:

AES("email=AAAAAAAAAA")="1111111111111111",
AES("AAAA&uid=10&role")="7777777777777777",
AES("=userXXXXXXXXXXX")="8888888888888888".
   

Alltså:

profile_for("AAAAAAAAAAAAAA") = 
"111111111111111177777777777777778888888888888888"
   

Låt oss nu ta och klippa och klistra lite med några av blocken från de tre krypterade strängarna vi fått och helt enkelt besöka URL:en:

simovits.com/ecb?11111111111111112222222222222222333333333333333355555555555555558888888888888888

Applikationen kommer då använda den hemliga nyckeln för att dekryptera strängen den fått vilket leder till:

|1111111111111111|2222222222222222|3333333333333333|5555555555555555|8888888888888888|
|email=AAAAAAAAAA|AAAgss@simovits.|com&uid=10&role=|admin&uid=10&rol|=userXXXXXXXXXXX|
   

Applikationen får därmed ut följande information:

{
    email : AAAAAAAAAAAAAgss@simovits.com,
     role : admin,
      uid : 10,
      rol : user
}
   


Användaren AAAAAAAAAAAAAgss@simovits.com kommer alltså tilldelas admin-behörigheter. Notera att användaren dessutom har den okända egenskapen “rol=user” – vilket med största sannolikhet kommer ignoreras av applikationen då den ej kommer leta efter saker den ej känner till.

Utan att känna till den hemliga krypteringsnyckeln så går det alltså att få applikationen att kryptera ett antal speciellt konstruerade block som vi sedan kan klippa-och-klistra ifrån för att skapa en lång sträng som applikationen kommer kunna dekryptera, avläsa och tolka som att vår användare har admin-behörigheter.

Slutsatsen som den vakna läsaren bör ta med sig från denna uppgift (tillsammans med de uppgifter vi gick igenom i den förra delen av denna bloggserie) är att man aldrig bör använda AES i ECB-läge.

Låt oss därför sluta bry oss om ECB och istället gå tillbaka till Challenge 10 där man ska titta på ett annat “läge” för AES.

Eller ja, juste! Uppgiften vi just kollat på handlar om att skriva ett program som utför denna klipp-och-klistra-attack. Det lämnas till läsaren.

PS: Nyfikna läsare kanske nu har börjat tycka att författaren bara är en bluff. Det finns nämligen ingen applikationen på simovits.com/ecb som läsaren kan hacka – denna URL användes bara för att statuera ett exempel. Förhoppningsvis kan detta falskspel förlåtas med tiden. DS

Challenge 10 – Implement CBC-mode

I vår förra bloggpost skippades denna uppgift då vi ville fokusera helt och hållet på ECB, men nu är det dags att ta tag i gamla synder!

Algoritmen AES bygger i grunden på att kryptera block av längd 16 med en hemlig nyckel av samma längd. Så när man vill kryptera en sträng som har längd 16 så är ju AES perfekt. Vi har tidigare även lärt oss att tillämpa “padding” för att utöka längden på en sträng som är för kort till att ha precis längd 16.

Det som återstår är att hitta en bra metod för att kryptera strängar som är längre än block-storleken 16. Vi har ju redan sett, och förkastat, ECB-läget, och här kommer vi lista några olika tekniker man skulle kunna använda istället. För jämförelsens skull börjar vi med att påminna om ECB för att därefter förklara CBC, vilket egentligen räcker för denna uppgift. Men… det finns en annan teknik som är väldigt lik CBC (nämligen CFB) som har en variant (CFB-8) som varit lite aktuell senaste tiden och författaren har svårt att hålla sig till ämnet… Så vi kommer beskriva även dessa andra metoder och sedan köra en liten avstickare där vi pratar om sårbarheten Zerologon.

Men innan det är dags för Zerologon så ska vi alltså lista lite olika krypteringsmetoder. Låt oss beskriva de olika metoderna genom ett exempel, med en sträng \( P = \text{“}\verb!Tjabba, tjena. Vad tycker du om denna bloggpost?!\text{“} \)

Denna sträng är 48 tecken lång och vi kan alltså dela in denna sträng i tre delsträngar:
\( P_1 = \text{“}\verb!Tjabba, tjena. V!\text{“} \)
\( P_2 = \text{“}\verb!ad tycker du om !\text{“} \)
\( P_3 = \text{“}\verb!denna bloggpost?!\text{“} \)
och skriva \( P=P_1+P_2+P_3 \) (där “+” betecknar konkatenering av strängar). Låt \( K \) beteckna en nyckel – en sträng av längd 16 – som används i AES-krypteringen. Dessutom använder vi tecknet \(\oplus\) som notation för XOR av strängar (bitvis addition modulo 2, vilket vi redan beskrivit i del 1) och så använder vi (icke-standard-)beteckning ADS för inversen (dekryptering) av AES.

ECB – Electronic Code Book
Kryptering:
\(\text{AES}_{\text{ECB}}(P, K) = C_1+C_2+C_3 = C\)
där
\( C_1 = \text{AES}(P_1, K)\)
\( C_2 = \text{AES}(P_2, K)\)
\( C_3 = \text{AES}(P_3, K)\)

Dekryptering:
\(\text{ADS}_{\text{ECB}}(C, K) = P_1+P_2+P_3 = P\)
där
\( P_1 = \text{ADS}(C_1, K)\)
\( P_2 = \text{ADS}(C_2, K)\)
\( P_3 = \text{ADS}(C_3, K)\)

CBC – Cipher Block Chaining
Innan vi går igenom definitionen för denna metod så ska vi försöka motivera varför den är uppbyggd som den är. Som namnet antyder så går denna metod ut på att fläta ihop de krypterade blocken i en lång kedja. Det kommer innebära att två block som är identiska men som förekommer i olika sektioner av texten krypteras olika – tvärtemot vad som sker för ECB.

Men vad händer om man har två olika meddelanden men vars första block är identiska – kommer det inte alltid leda till att de två krypterade meddelandena också börjar med identiska block? Det är ju gärna något man vill undvika, och för denna anledning så använder man sig (förutom av den hemliga nyckeln) även av en extra 16 tecken lång sträng, en så kallad initieringsvektor (IV-sträng). Man kan tänka på denna sträng som ett frö till en slumptalsgenerator, och om man ser till att välja olika frön varje gång så kommer de resulterande kryptotexterna alltid se olika ut (även om man krypterar samma meddelande med samma nyckel).

Nu är det nog med svammel, här är definitionerna:

Kryptering:
\(\text{AES}_{\text{CBC}}(P, K, IV) = C_1+C_2+C_3 \)
där
\( C_1 = \text{AES}(P_1\oplus IV, K)\)
\( C_2 = \text{AES}(P_2 \oplus C_1, K)\)
\( C_3 = \text{AES}(P_3\oplus C_2, K)\)

Dekryptering:
För att dekryptera kan man helt enkelt göra det omvända:

\( \text{AES}(P_1\oplus IV, K) = C_1\quad \Rightarrow\quad P_1\oplus IV = \text{ADS}(C_1,K) \quad \Rightarrow\quad P_1 = \text{ADS}(C_1,K) \oplus IV \)

Alltså:
\( P_1 = \text{ADS}(C_1,K) \oplus IV \)
\( P_2 = \text{ADS}(C_2,K) \oplus C_1 \)
\( P_3 = \text{ADS}(C_3,K) \oplus C_2 \)

Att skriva ett program som utför dessa block-operationer är vad uppgiften går ut på. Hursomhelst, låt oss fortsätta med lite andra tekniker:

CFB – Cipher FeedBack
Kryptering:
\(\text{AES}_{\text{CFB}}(P, K, IV) = C_1+C_2+C_3 \)
där
\( C_1 = P_1\oplus\text{AES}(IV, K)\)
\( C_2 = P_2 \oplus\text{AES}(C_1, K)\)
\( C_3 = P_3\oplus\text{AES}(C_2, K)\)

Dekryptering:
\( P_1 =C_1\oplus \text{AES}(IV,K) \)
\( P_2 = C_2\oplus \text{AES}(C_1,K) \)
\( P_3 = C_3\oplus\text{AES}(C_2,K) \)

Notera att denna teknik är väldigt lik CBC-metoden (XOR-operationen är bara flyttad utanför krypteringsfunktionen) och har den intressanta egenskapen att man inte använder ADS-funktionen för dekryptering.

CFB-8 – Cipher FeedBack med bit-längd 8
De tre tidigare teknikerna vi har sett utför operationer på de olika 16-längds-blocken som strängarna består av. Notera att dessa tre tekniker alltid kommer resultera i en sträng vars längd är en multipel av block-längden 16. Den metod vi nu ska förklara är lite annorlunda. För de insatta så är detta ett ström-chiffer till skillnad från de tidigare som är så kallade block-chiffer.

Eftersom det visar sig att vi i detta fall inte kommer behöva bry oss om block så kan vi här göra ett enklare exempel där vi visar hur det går till när man krypterar en kortare sträng \(P=p_1p_2p_3\) som bara består av tre tecken \(p_1,p_2\text{ och }p_3\).


Låt oss skriva \( C_{\{-15,0\}} = c_{-15}c_{-14}\ldots c_{-1}c_{0} = IV\) där vi bara notationsmässigt indexerat de 16 tecknen i IV-strängen från -15 till 0.

Sätt \( E_1=\text{AES}(C_{\{-15,0\}}, K)=\text{AES}(IV,K)\)

Låt \( e_1 \) vara det första tecknet i strängen \(E_1\) och sätt \(c_1 = p_1\oplus e_1\).

Låt oss nu skriva \(C_{\{-14,1\}} = c_{-14}c_{-13}\ldots c_{0}c_{1}\), vilket helt enkelt är strängen \( C_{\{-15,0\}} = c_{-15}c_{-14}\ldots c_{-1}c_{0} \) men där vi tagit bort det första tecknet \(c_{-15}\) och lagt på tecknet \(c_1\) på slutet.

Sätt \(E_2 = \text{AES}(C_{\{-14,1\}}, K)\) och beräkna:

\(c_2 = p_2 \oplus e_2,\quad \text{där }e_2\text{ är det första tecknet av strängen }E_2\).

Vi fortsätter på liknande sätt och skriver \(C_{\{-13,2\}} = c_{-13}c_{-12}\ldots c_{1}c_{2}\).

Med \(E_3 = \text{AES}(C_{\{-13,2\}}, K)\) kan vi beräkna:

\(c_3 = p_3\oplus e_3,\quad \text{där } e_3\text{ det första tecknet av strängen }E_3.\)

De tre tecknen \(c_1,c_2\text{ och }c_3\) ger oss krypteringen av \(P=p_1p_2p_3\). Med andra ord \(\text{AES}_{\text{CFB-8}}(p_1p_2p_3, K, IV) = c_1c_2c_3 \)

Det är helt förståeligt om denna förklaring var lite svår att hänga med på. Förhoppningsvis kan det hjälpa med ett exempel:

Exempel. Låt \(P=\text{“}\verb!HEJ!\text{“}\) vara texten som vi vill kryptera med hjälp av nyckeln \(K=\text{“}\verb!YELLOW SUBMARINE!\text{“}\) och \(IV=\text{“}\verb!NINJA RABBITS GO!\text{“}\). Eftersom resultatet av AES-krypteringar i princip aldrig består av enbart läsbara tecken så kommer vi i detta exempel ange felaktiga resultat av AES-beräkningarna som bara ska ses som visuella hjälpmedel. Beräkningarna nedan kommer alltså ej vara korrekta!

Med inputsträngarna ovan gäller att \( C_{\{-15,0\}} = \verb!”NINJA RABBITS GO”!\) och vi låtsas som att:

\( E_1=\text{AES}(\verb!”NINJA RABBITS GO”!, \verb!”YELLOW SUBMARINE”!) =\verb&”zUSmfsia9%2@a!2e”&\)

Då är \( e_1=\verb!”z”! \) och \(c_1 = p_1\oplus e_1=\verb&”H”&\oplus \verb&”z”& = \verb&”2&\verb!”!\).

Vi får därmed att \(C_{\{-14,1\}} =\verb!”INJA RABBITS GO2!\verb!”!\) och vi låtsas som att:

\(E_2 = \text{AES}(\verb!”INJA RABBITS GO2!\verb!”!, \verb!”YELLOW SUBMARINE”!)=\verb&”+[fneJF2MS!3fjMC”&\)

Då är \( e_2=\verb!”+”! \) och \(c_2 = p_2 \oplus e_2=\verb!”E”!\oplus \verb!”+”!=\verb!”n”!\).

Nu blir \(C_{\{-13,2\}} = \verb!”NJA RABBITS GO2n”!\) och vi låtsas att:

\(E_3 = \text{AES}(\verb!”NJA RABBITS GO2n”!, \verb!”YELLOW SUBMARINE”!)=\verb!”4SkdsP&e&JDFSNn2!\verb!”!\).

Det ger att \(e_3=\verb!”4!\verb!”!\), så \(c_3 = p_3\oplus e_3=\verb!”J”!\oplus \verb!”4!\verb!”!=\verb!”~”!\)

Till slut får vi alltså att \(\text{AES}_{\text{CFB-8}}(\verb!”HEJ”!, \verb!”YELLOW SUBMARINE”!, \verb!”NINJA RABBITS GO”!) = \verb!”2n~”!. \) (Men eftersom vi använt fel AES-beräkningar av visuella skäl så stämmer inte detta egentligen – den intresserade läsaren kan göra den korrekta beräkningen på egen hand.)

Ny avstickare: Zerologon

All information som kommer förklaras nedan har redan beskrivits i det whitepaper som släpptes i samband med annonseringen av sårbarheten och den intresserade läsaren hänvisas dit för mer information. I detta avsnitt tänker vi försöka förklara sårbarheten i termer av de tekniker som vi redan beskrivit ovan.

Bakgrund: Zerologon är en sårbarhet i ett protokoll som används mellan servrar och klienter för att autentisera sig sinsemellan och som kan leda till att en icke-behörig användare kan logga in i servern som administratör med maximala behörigheter. Det är illa i sig, men det som är riktigt illa är att de servrar som man autentiserar sig mot med detta protokoll är domänkontrollanter (de där superviktiga servrarna som håller reda på behörigheterna hos varje användare i ett nätverk). Det är inte så bra.

En skurk kan alltså använda sårbarheten, logga in på en domänkontrollant som administratör, skapa en ny användare med behörigheter till hela domänen (eller bara stjäla lösenordet hos en existerande sådan användare) och därigenom få full kontroll över hela dator-nätverket. Det låter ju faktiskt ganska illa. Nu tänker kanske den förhoppningsfulle läsaren att denna sårbarhet åtminstone måste vara extremt komplex att utföra? Nja…

Förklaring av protokollet (minus detaljer):
Säg att vi har en klient och en server.
0. Klienten och servern har sedan tidigare bytt ett hemligt lösenord H (exempelvis “Vinter2021”) med varandra.
När klienten vill prata med servern görs följande:

  1. Klienten slumpar en 8 tecken lång sträng (A) och skickar till servern.
  2. Servern tar emot A, slumpar en ny sträng med 8 tecken (B) och skickar till klienten.
  3. Klienten och servern har nu båda kännedom om H, A och B.
  4. Klienten stoppar in dessa tre strängar i en funktion F som genererar en nyckel K=F(H,A,B).
  5. Klienten använder nyckeln K för att kryptera strängen A och skapar på så sätt strängen C=kryptering(A,K) som den skickar till servern.
  6. Servern tar emot strängen C, beräknar också K =F(H,A,B) och använder denna nyckel för att beräkna D=kryptering(A,K).
  7. Om C är lika med D så godkänner servern att klienten är den som den utger sig för att vara.

Förklaringen till varför detta ska funka är att klienten måste känna till det hemliga lösenordet H för att kunna beräkna nyckeln K och därigenom kunna kryptera strängen A till det korrekta resultatet. Det måste väl vara stensäkert? Återigen… implementationsmisstag kan göra vad som helst sårbart.

Vad är problemet ovan? Krypteringsfunktionen (som vi inte nämnt än) valdes till AES i CFB-8-läge. Det är i sig helt okej (men ovanligt). Problemet är att programmerarna dessutom valde att göra det enkelt för sig och hårdkodade in att alltid använda IV som en sträng av 16 0-bytes – dvs 16 kopior av tecknet med ASCII-värde 0, inte tecknet “0” med ASCII-värde 30. För att förvirra så mycket som möjligt så kommer vi nedan skriva 0 för att beteckna just symbolen med ASCII-värde 0.

Zerologon-attacken:

  1. En skurk-klient som inte känner till hemligheten H skickar den inte särskilt slumpade strängen A=00000000.
  2. Servern tar emot A=00000000 och skickar tillbaka en sträng B som är en genuint slumpad sträng med 8 tecken.
  3. Servern har nu kännedom om H, A och B, men skurk-klienten känner bara till A och B.
  4. Skurk-klienten kan inte beräkna nyckeln K eftersom den inte känner till H.
  5. Skurk-klienten kan inte kryptera strängen A=00000000 eftersom den inte känner till nyckeln K, men gör en chansning och skickar C=00000000.
  6. Servern tar emot strängen C=00000000, beräknar K =F(H,A,B) och använder denna nyckel för att beräkna D=kryptering(A,K).
  7. Servern kollar om D är lika med C=00000000 och autentiserar skurk-klienten om detta är sant.

Med en bra krypteringsmetod bör skurk-klienten kunna gissa rätt med C=00000000 med sannolikhet \( 1/256^8 = 1/2^{64} \approx 0.000000000000000005\%\)

Det visar sig att skurk-klienten kommer gissa rätt med sannolikhet \( 1/256\approx0.39\%\). Den sannolikheten är aningen högre än det förväntade… Hur kan det komma sig?

Låt oss titta på hur krypteringen av \(A=p_1p_2p_3p_4p_5p_6p_7p_8=00000000\) med AES i CFB-8-läget beräknas med ett IV=0000000000000000.

Vi får \( C_{\{-15,0\}} = 0000000000000000 = IV\).

\( E_1=\text{AES}(C_{\{-15,0\}}, K)=\text{AES}(0000000000000000,K)\)

Om det första tecknet \(e_1\) i strängen \(E_1\) råkar vara en 0-byte så gäller att \(c_1 = p_1\oplus e_1=0\oplus0=0\).

Eftersom \(c_1=0\) får vi att \(C_{\{-14,1\}} =0000000000000000=C_{\{-15,0\}}\), så allting upprepar sig med \(E_2 = E_1, e_2=e_1=0\) och \(c_2 = p_2 \oplus e_2=0\oplus0=0\)

Eftersom \(c_2=0\) får vi att \(C_{\{-13,2\}} = C_{\{-15,0\}}\), vilket innebär att allting upprepar sig en gång till så \(E_3 = E_1, e_3=e_1=0\) och \(c_3 = p_3 \oplus e_3=0\oplus0=0\)

Allting kommer fortsätta upprepa sig och leda till att även \(c_4=0,c_5=0,c_6=0,c_7=0,c_8=0\). Det vill säga, om tecknet \(e_1\) råkar bli 0-byten så kommer krypteringen av \(A=00000000\) bli \(C=00000000\). Vad är sannolikheten att \(e_1=0\)? Jo, sannolikheten är 1/256 eftersom AES är en bra krypteringsalgoritm där alla 256 tecken är lika sannolika utfall.

Standardinställningen för detta protokoll tillåter en stor mängd misslyckade autentiseringsförsök, så en skurk-klient skulle helt enkelt kunna göra en massa login-försök mot en given domänkontrollant genom att bara skicka massa nollor, och i snitt kommer 256 försök räcka för att få en godkänd autentisering. Man får slutligen ge beröm till namngivaren då Zerologon måste klassas som ett utmärkt namn för en sårbarhet där man med hög sannolikhet kan logga in sig på en server med hjälp av bara nollor.

Vad rekommenderas att ta med sig från denna text? Använd inte statiska IV-strängar när ni krypterar med CBC, CFB eller CFB-8, och använd speciellt inte strängar med bara nollor… – det är inte kryptologi, bara idioti.

Det var allt för denna gång, men förtvivla inte – det kommer säkert en ny del innan ni vet ordet av! (Vilket ord skulle det vara? Ingen aning!)