hvis du kan lide at gå til Ordlejre, som jeg gør, har du sandsynligvis allerede hørt dette: “hashing af adgangskode er ikke sikkert” eller i den mest tekniske version: “…fordi det er MD5 baseret”.
sandt eller ej, en stærk adgangskode hashing er afgørende for et stort økosystem som f.eks. Så jeg besluttede at se nærmere på hashingsystemet og forsøge at knække hashes fra bunden!
forståelse af adgangskode hashes
jeg begyndte at Google og fandt ud af, at de fleste af oplysningerne derude er generiske og forvirrende. Masser af referencer til de anvendte PHP-biblioteker (bærbar hash fra phpass), men intet virkelig konkret.
jeg besluttede at tage en anden tilgang ud fra en antagelse:
hashing er en envejs proces, men hashing er på en eller anden måde i stand til at godkende brugere, der matcher deres adgangskode input med hash gemt i databasen
derfra begyndte jeg at kontrollere koden og fandt den første interessante funktion: wp_check_password($password,$hash)
som sammenligner den almindelige tekst adgangskode med hash og Returnerer SAND, hvis de matcher.
1// presume the new style phpass portable hash. 2if ( empty( $wp_hasher ) ) { 3 require_once ABSPATH . WPINC . '/class-phpass.php'; 4 // By default, use the portable hash from phpass. 5 $wp_hasher = new PasswordHash( 8, true ); 6} 7 8$check = $wp_hasher->CheckPassword( $password, $hash ); 9 10/** This filter is documented in wp-includes/pluggable.php */ 11return apply_filters( 'check_password', $check, $password, $hash, $user_id );
går gennem koden, det hurtigt bringe os til CheckPassword
, crypt_private
og encode64
som dybest set er, hvor magien sker.
lang historie kort, crypt_private($password, $stored_hash)
re-hashes adgangskoden, før det bliver sammenlignet med den lagrede hash. Hvis de matcher, er adgangskoden korrekt, og godkendelsen fortsætter. Hvilket betyder, at vi også kan bruge denne funktion til at knække hash.
Hash anatomi
dette er en hash:
1$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1
for nemheds skyld antager vi, at siden bruger PHP > 5 og den nyeste phpass bærbare hash, som er den mest almindelige opsætning.
de første 3 tegn $P$
er et ID, der fortæller systemet, hvilken slags oh hash vi har.
Tegnnummer 3 (tæller fra 0) bruges til at bestemme, hvor mange gange md5 () skal behandle inputstrengen.
tegn fra 4 til 12 nPVO4gP9
er saltet, som er en tilfældig streng, der er knyttet til adgangskoden før hashing, for at give det mere tilfældighed. For eksempel, hvis din adgangskode er admin, bliver den vendt til nPVO4gP9admin og derefter hashet.
den resterende del af hash JUMSAM1WlLTHPdH6EDj4e1
er den virkelige tilfældighed, genereret af salt+adgangskoden, der sendes i en udokumenteret encode64
funktion, som udfører nogle bitvise operationer på inputstrengen og returnerer en 22 tegn output.
ikke så klart, uh? Hold dig til mig.
indtil videre ved vi:
- den første del af hash er et fast id
- den anden er en enkelt char, der bruges som tæller, også fast
- den tredje er saltet, bundet til adgangskoden
- den sidste er tilfældig, genereret af ‘salt + pass’ behandlet af encode64-funktion
1
så vi kan omskrive logikken-salt + adgangskode hashede gange og bestået i encode64-for at udføre et ordbog eller bruteforce-angreb og opnå den samme ‘sidste del’ af hash, og det ville være en vellykket hash-crack!
i et virkeligt scenarie, og hackere ville være mere interesserede i at finde svage adgangskoder, fordi de er mere tilbøjelige til at blive genbrugt i stedet for tilfældige, der ofte genereres af en adgangskodeadministrator og så stedspecifik.
omskrivning af encode64 ved hjælp af golang
jeg besluttede at gå med golang, fordi det er meget hurtigt, og vi har brug for al den hastighed til at beregne store adgangskodeordbøger.
ligesom i krypteringsproceduren tager scriptet hash og isolerer saltet, så komponerer det adgangskoden (salt+pass), der forsøger hver adgangskode i ordbogen og md5-hashing antal gange, og vi har set, hvordan K er bestemt.
1itoa64 := "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 2hashloop := 1 << strings.Index(itoa64, string(hash)); // char n. 4 in the hash
udført det, det udfører en række bitvise operationer på bytes opnået ved hjælp af ord() i PHP, som vi ikke har brug for i golang, da vi allerede har byte-værdier:
1itoa64 & 0x3f]
på dette tidspunkt indså jeg, at vi ikke engang behøver at finde hele strengen. Det er nok at matche et par tegn for at matche hele adgangskoden og gøre scriptet mere performant! Så på grund af karakteren af hashing processen, hvis char n. 0 / 4 / 8 … matcher, adgangskoden bestået som input er korrekt.
og BOOM, vi knækkede hash:
1wphashcrash '$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1' Dev/wphashcrash/dict.txt 2$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1 admin 32020/05/30 12:44:15 Executed in 0.000221
koden er som sædvanlig på GitHub – https://github.com/francescocarlucci/wphashcrash – og i øjeblikket er scriptet en POC og får kun en enkelt adgangskode hash som input, og stien til adgangskodeordbogen.
jeg har allerede skitseret i readme nogle forbedringer, der ville være rart at have, og jeg vil nok tilføje i fremtiden.
hvis du virkelig vil prøve at hacke en liste over hashes, kan du gaffel det eller bare pakke scriptet i en bash for loop.
Secuirty overvejelser
på dette tidspunkt, jeg tror, det er klart nok, at hvis en angriber får adgang til databasen på en hjemmeside, han/hun kan dybest set knække hver svag adgangskode.
dette uheldige, især fordi:
- RCE vil også højst sandsynligt give adgang til DB
- dette sidste punkt kombineret med brugerkonti er et enormt område for freelancers arbejde og er meget almindeligt at finde gamle/ubrugte admin-og FTP-konti, der er oprettet til midlertidige behov
dette sidste punkt, kombineret med brugerkonti og et fælles problem, efterlader døren åben for at få adgang til dB ved hjælp af stjålne/pantsatte admin legitimationsoplysninger.
derudover håndhæver mange hjemmesider ikke stærke adgangskoder for ikke at skade brugeroplevelsen, og jeg har direkte erfaring med nogen af de nævnte punkter.
når en hjemmeside er kompromitteret, har svage hashes gemt, at angribere kan kompromittere andre brugerkonti på andre sider, hvis de har tendens til at genbruge adgangskoder, og vi ved, at de gør det.
har vi løsninger?
min forskning gik ikke så langt, jeg ville kun se, hvordan man bryder hash.
Bcrypt er kendt for at være en stærkere hashing-metode sammenlignet med md5, og der er et eksisterende plugin, der bruger bcrypt og erstatter alle de kernefunktioner, der er nødvendige for at håndtere adgangskoder:
- vp_check_adgangskode ()
- vp_hash_adgangskode ()
- vp_set_adgangskode()
og selvfølgelig kan du skrive din egen løsning som udvikler, men jeg mener, at dette er et problem at løse på kerneniveau for at sigte mod en stor vedtagelse.
Final note
der er mange værktøjer til at knække hashes, som Hashcat og John the Ripper, som kan være endnu mere effektive, men igen er omfanget af denne forskning at forstå hashstrukturen og knække den fra bunden.
tak for læsning.
Pernille