om du gillar att gå till WordCamps som jag har du förmodligen hört det här redan: ”WordPress-lösenordshashing är inte säkert”, eller i den mest tekniska versionen: ”…eftersom det är md5-baserat”.
sant eller inte, ett starkt lösenordshashing är avgörande för ett stort ekosystem som WordPress, som alltid har varit ett saftigt mål för hackare. Så jag bestämde mig för att titta närmare på hashsystemet och försöka knäcka WordPress-hashar från början!
förstå WordPress lösenord hashes
jag började göra några googling och fann att det mesta av informationen där ute är generisk och förvirrande. Massor av referenser till PHP-bibliotek som används (bärbar hash från phpass), men ingenting riktigt konkret.
jag bestämde mig för att ta ett annat tillvägagångssätt från ett antagande:
hashing är en enkelriktad process, men WordPress kan på något sätt autentisera användare som matchar deras lösenordsinmatning med hash lagrad i databasen
därifrån började jag kontrollera koden och hittade den första intressanta funktionen: wp_check_password($password,$hash)
som jämför det vanliga lösenordet med hash och returnerar sant om de matchar.
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 );
genom att gå igenom koden tar det oss snabbt till CheckPassword
, crypt_private
och encode64
vilket i princip är där magin händer.
lång historia kort, crypt_private($password, $stored_hash)
åter hashes lösenordet innan det blir jämfört med den lagrade hash. Om de matchar är lösenordet korrekt och autentiseringen fortsätter. Vilket innebär att vi också kan använda den funktionen för att knäcka hash.
Hash anatomi
Detta är en WordPress hash:
1$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1
för enkelhetens skull antar vi att webbplatsen använder PHP> 5 och den senaste phpass portable hash, som är den vanligaste inställningen.
de första 3 tecknen $P$
är ett ID som berättar för systemet vilken typ oh hash vi har.
Teckennummer 3 (räknat från 0) används för att bestämma hur många gånger md5 () måste bearbeta inmatningssträngen.
tecken från 4 till 12 nPVO4gP9
är saltet, vilket är en slumpmässig sträng som läggs till lösenordet före hashing, för att ge det mer slumpmässighet. Till exempel, om ditt lösenord är admin, blir det vänt till nPVO4gP9admin och sedan hashed.
den återstående delen fo hash JUMSAM1WlLTHPdH6EDj4e1
är den verkliga slumpmässigheten, genererad av salt+ – lösenordet som skickas i en odokumenterad encode64
– funktion, som utför vissa bitvis operationer på ingångssträngen och returnerar en 22 teckenutgång.
inte så klart, uh? Håll dig till mig.
hittills vet vi:
- den första delen av hash är ett fast id
- den andra är en enda röding som används som räknare, även fast
- den tredje är saltet, bundet till lösenordet
- den sista är slumpmässig, genererad av ’salt + pass’ bearbetad av encode64-funktionen
1
så vi kan skriva om logiken-salt + lösenord hashed X gånger och passerade i encode64 – för att utföra en ordbok eller bruteforce attack, och få samma ’sista delen’ av hash och det skulle vara en framgångsrik hash spricka!
i ett verkligt scenario, och hackare skulle vara mer intresserade av att hitta svaga lösenord eftersom de är mer benägna att återanvändas, istället för slumpmässiga som ofta genereras av en lösenordshanterare och så platsspecifik.
omskrivning encode64 med golang
jag bestämde mig för att gå med golang eftersom det är väldigt snabbt, och vi behöver all den hastigheten för att beräkna stora lösenordsordböcker.
precis som i WordPress-krypteringsproceduren tar skriptet hash och isolerar saltet, då komponerar det lösenordet (salt+pass) som försöker varje lösenord i ordboken och md5-hashing X antal gånger, och vi har sett hur X bestäms.
1itoa64 := "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 2hashloop := 1 << strings.Index(itoa64, string(hash)); // char n. 4 in the hash
gjort det, det utför en serie bitvis operation på byte som erhållits med ord () i PHP, vilket vi inte behöver i golang eftersom vi redan har bytevärden:
1itoa64 & 0x3f]
vid denna tidpunkt insåg jag att vi inte ens behöver hitta hela strängen. Det räcker att matcha några tecken för att matcha hela lösenordet och göra manuset mer performant! Så, på grund av hashprocessens natur, om char n. 0 / 4 / 8 … matchningar, lösenordet som skickas som inmatning är korrekt.
och BOOM, vi knäckt hash:
1wphashcrash '$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1' Dev/wphashcrash/dict.txt 2$P$BnPVO4gP9JUMSAM1WlLTHPdH6EDj4e1 admin 32020/05/30 12:44:15 Executed in 0.000221
koden är som vanligt på GitHub – https://github.com/francescocarlucci/wphashcrash – och för tillfället är skriptet en POC och får bara en enda lösenordshash som inmatning och sökvägen till lösenordsordlistan.
jag skisserade redan i readme några förbättringar som skulle vara trevliga att ha och jag kommer förmodligen att lägga till i framtiden.
om du verkligen vill försöka hacka en lista med hashar kan du gaffla den eller bara sätta in manuset i en bash för loop.
Secuirty överväganden
vid denna tidpunkt tror jag att det är tillräckligt klart att om en angripare får tillgång till databasen på en WordPress-webbplats kan han/hon i princip knäcka varje svagt lösenord.
detta olyckliga, särskilt för att:
- SQL Injection är inte så sällsynt i WordPress plugins
- RCE kommer också sannolikt att ge tillgång till DB
- WordPress ekosystem är ett stort område för frilansare arbete och är mycket vanligt att hitta gamla/oanvända wp-admin och FTP-konton, som skapats för tillfälliga behov
denna sista punkt, i kombination med användare uppräkning som en WordPress vanligt problem, lämnar dörren öppen för att få tillgång till db med hjälp av stulna/pantsatta administratörsuppgifter.
dessutom använder många webbplatser inte starka lösenord för att inte skada användarupplevelsen, och jag har direkt erfarenhet av någon av de nämnda punkterna.
när en webbplats är en komprometterad, med svaga hashar lagrade tillåter angripare att kompromissa med andra användarkonton på andra webbplatser, om de tenderar att återanvända lösenord och vi vet att de gör det.
har vi lösningar?
min forskning gick inte så långt, jag ville bara se hur man bryter WordPress-hashar.
Bcrypt är känt för att vara en starkare hashingmetod jämfört med md5, och det finns ett befintligt plugin som använder bcrypt och ersätter alla kärnfunktioner som behövs för att hantera lösenord:
- wp_check_password ()
- wp_hash_password ()
- wp_set_password()
och självklart kan du skriva din egen lösning som utvecklare, men jag tror att det här är ett problem att fixa på kärnnivå för att sträva efter en stor adoption.
Final note
det finns många verktyg för att knäcka hashar, som Hashcat och John the Ripper som kan vara ännu mer performant, men igen, omfattningen av denna forskning är att förstå WordPress hashstruktur och knäcka den från grunden.
Tack för att du läste.
frenxi