Mens rapporter varierer, rapporterte The Washington Post at den siste iCloud-kjendisfotohackingen er sentrert rundt Finn min iPhones ubeskyttede påloggingspunkt:
"... sikkerhetsforskere ble sagt å ha funnet en feil i iClouds Find My iPhone-funksjon som ikke kuttet av brute force-angrep. Apples uttalelse ... antyder at selskapet ikke ser denne åpenbaringen som et problem. Og det er et problem, ifølge til sikkerhetsforsker og Washington Post-bidragsyter Ashkan Soltani.
Jeg er enig. Jeg skulle ønske Apple hadde vært mer kommende; sin omhyggelig formulerte respons igjen rom for forskjellige tolkninger og syntes å skylde ofrene.
Hackere kan ha brukt dette iBrute-skriptet på GitHub for å målrette kjendisregnskap via Finn min iPhone; Sårbarheten har siden blitt stengt.
Siden en av de rikeste selskapene i verden ikke allokerte ressursene for å rangere grensen for alle deres autentiseringspunkter, er det sannsynlig at noen av webappsene dine ikke inkluderer prisbegrensning. I denne opplæringen vil jeg gå gjennom noen av de grunnleggende konseptene for takstbegrensning og en enkel implementering for ditt PHP-baserte webprogram.
Forskning fra tidligere hack har utsatt passord som folk pleier å bruke hyppigst. Xeno.net publiserer en liste over de ti tusen beste passordene. Deres diagram nedenfor viser at frekvensen av vanlige passord i deres topp 100 liste er 40%, og topp 500 utgjør 71%. Med andre ord bruker folk ofte og bruker et lite antall passord igjen; delvis fordi de er enkle å huske og enkle å skrive.
Det betyr at selv en liten ordbok angrep ved hjelp av bare de tjuefem vanligste passordene kan være ganske vellykket når du målretter mot tjenester.
Når en hacker identifiserer et inngangspunkt som tillater ubegrenset påloggingsforsøk, kan de automatisere høyhastighets, høyt volum ordlisteangrep. Hvis det ikke er noen takstbegrensning, blir det enkelt for hackere å angripe med større og større ordbøker - eller automatiserte algoritmer med uendelig antall permutasjoner.
Videre, hvis personlig informasjon om offeret er kjent, f.eks. deres nåværende partner eller kjæledyrs navn kan en hacker automatisere angrep av permutasjoner av sannsynlige passord. Dette er et vanlig sårbarhet for kjendiser.
For å beskytte innlogginger er det et par tilnærminger som jeg anbefaler som utgangspunkt:
I begge tilfeller vil vi måle mislykkede forsøk under et bestemt vindu eller tidsvinduer, f.eks. 15 minutter og 24 timer.
En risiko for å blokkere forsøk med brukernavn er at den faktiske brukeren kunne bli låst ut av kontoen sin. Så, vi vil sørge for at vi gjør det mulig for den gyldige brukeren å åpne kontoen sin og / eller tilbakestille passordet.
En risiko for å blokkere forsøk med IP-adresse er at de ofte deles av mange mennesker. For eksempel kan et universitet være vert for både den faktiske kontoinnehaveren og noen som forsøker å skadelig sin konto. Blokkering av en IP-adresse kan blokkere hackeren, så vel som den faktiske brukeren.
En kostnad for økt sikkerhet er imidlertid ofte litt økt ulempe. Du må avgjøre hvor strengt du skal vurdere å begrense dine tjenester og hvor lett du vil gjøre det for brukere å åpne kontoer på nytt.
Det kan være nyttig å kode et hemmelig spørsmål i appen din, som kan brukes til å autentisere en bruker hvis konto ble blokkert. Alternativt kan du sende et passord tilbakestill til e-posten deres (håper at det ikke er blitt kompromittert).
Jeg har skrevet litt kode for å vise deg hvordan du vurderer å begrense webapplikasjonene dine; eksemplene mine er basert i Yii Framework for PHP. Mesteparten av koden gjelder for alle PHP / MySQL applikasjoner eller rammer.
Først må vi opprette et MySQL-tabell for å lagre informasjon fra mislykkede påloggingsforsøk. Bordet skal lagre IP adresse
av den forespørgende brukeren, forsøket på brukernavn eller e-postadresse som ble brukt og en tidsstempel:
$ this-> createTable ($ this-> tableName, array ('id' => 'pk', 'ip_address' => 'streng IKKE NULL', 'brukernavn' => 'streng IKKE NULL', 'created_at' => 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',), $ this-> MySqlOptions);
Deretter lager vi en modell for LoginFail-tabellen med flere metoder: legge til, sjekk og rens.
Når det er en feilmelding, legger vi en rad til LoginFail-tabellen:
offentlig funksjon legg til ($ brukernavn) // legg til en rad til den mislykkede innloggingstabellen med brukernavn og IP-adresse $ failure = new LoginFail; $ failure-> brukernavn = $ brukernavn; $ failure-> ip_address = $ this-> getUserIP (); $ failure-> created_at = ny CDbExpression ('NU ()'); $ Feil-> Lagre (); // når det er et mislykket innloggingsprogram, rens eldre feillogg $ this-> purge ();
Til getUserIP ()
, Jeg brukte denne koden fra Stack Overflow.
Vi kan også benytte muligheten til et mislykket innlogging, for å rense bordet med eldre poster. Jeg gjør dette for å hindre at verifiseringskontrollene senker seg over tid. Eller du kan implementere en rensing i en bakgrunn cron oppgave hver time eller hver dag:
rensing av offentlig funksjon ($ min = 120) // rensing mislyktes påloggingsoppføringer eldre enn $ minutter $ minutes_ago = (tid () - (60 * $ minutter)); // f.eks. 120 minutter siden $ criteria = new CDbCriteria (); LoginFail :: modell () -> older_than ($ MINUTES_AGO) -> applyScopes ($ kriterier); LoginFail :: modell () -> Slett ($ kriterier);
Yii-godkjenningsmodulen jeg bruker ser slik ut:
offentlig funksjon autentiserer ($ attributt, $ params) if (! $ this-> hasErrors ()) // vi vil bare autentisere når ingen inngangsfeil $ identity = new UserIdentity ($ this-> brukernavn, $ this-> passord); $ Identitets-> godkjenne (); hvis (LoginFail :: model () -> sjekk ($ dette-> brukernavn)) $ this-> addError ("brukernavn", UserModule :: t ("Kontoadgang er blokkert, vennligst kontakt support.")); ellers switch ($ identity-> errorCode) tilfelle UserIdentity :: ERROR_NONE: $ duration = $ this-> rememberMe? Yii :: app () -> controller-> modul-> rememberMeTime: 0; Yii :: app () -> bruker-> logg ($ identitet, $ varighet); gå i stykker; case UserIdentity :: ERROR_EMAIL_INVALID: $ this-> addError ("brukernavn", UserModule :: t ("Email er feil.")); LoginFail :: modell () -> legg ($ dette-> brukernavn); gå i stykker; case UserIdentity :: ERROR_USERNAME_INVALID: $ this-> addError ("brukernavn", UserModule :: t ("Brukernavn er feil.")); LoginFail :: modell () -> legg ($ dette-> brukernavn); gå i stykker; case UserIdentity :: ERROR_PASSWORD_INVALID: $ this-> addError ("passord", UserModule :: t ("Passordet er feil.")); LoginFail :: modell () -> legg ($ dette-> brukernavn); gå i stykker; case UserIdentity :: ERROR_STATUS_NOTACTIV: $ this-> addError ("status", UserModule :: t ("Din konto er ikke aktivert.")); gå i stykker; case UserIdentity :: ERROR_STATUS_BAN: $ this-> addError ("status", UserModule :: t ("Din konto er blokkert.")); gå i stykker;
Når innloggningskoden min oppdager en feil, ringer jeg metoden for å legge til detaljer om den til LoginFail-tabellen:
LoginFail :: modell () -> legg ($ dette-> brukernavn);
Verifikasjonsseksjonen er her. Dette løper med hvert påloggingsforsøk:
$ Identitets-> godkjenne (); hvis (LoginFail :: model () -> sjekk ($ dette-> brukernavn)) $ this-> addError ("brukernavn", UserModule :: t ("Kontoadgang er blokkert, vennligst kontakt support."));
Du kan graft disse funksjonene til din egen kodes innloggingsautentiseringsseksjon.
Bekreftelseskontrollen min ser etter et høyt antall mislykkede påloggingsforsøk for brukernavnet i spørsmålet og separat for IP-adressen som brukes:
offentlig funksjonskontroll ($ brukernavn) // sjekk om mislykket innloggingsgrense er blitt overtrådt // for brukernavn i siste 15 minutter og siste time // og for IP-adresse i de siste 15 minuttene og siste time $ has_error = false; $ minutes_ago = (tid () - (60 * 15)); // 15 minutter siden $ hours_ago = (tid () - (60 * 60)); // 1 time siden $ user_ip = $ this-> getUserIP (); hvis (LoginFail :: model () -> siden ($ minutes_ago) -> brukernavn ($ brukernavn) -> count ()> = selv: FAILS_USERNAME_QUARTER_HOUR) $ has_error = true; else if (LoginFail :: model () -> siden ($ minutes_ago) -> ip_address ($ user_ip) -> count ()> = selv: FAILS_IP_QUARTER_HOUR) $ has_error = true; else if (LoginFail :: model () -> siden ($ hours_ago) -> brukernavn ($ brukernavn) -> count ()> = selv: FAILS_USERNAME_HOUR) $ has_error = true; ellers hvis (LoginFail :: modell () -> siden ($ timer_ago) -> ip_address ($ user_ip) -> count ()> = selv: FAILS_IP_HOUR) $ has_error = true; hvis ($ har_error) $ this-> add ($ brukernavn); returner $ has_error;
Jeg sjekker grenseverdiene for de siste femten minuttene, så vel som den siste timen. I mitt eksempel tillater jeg 3 mislykkede påloggingsforsøk per femten minutter og seks per time for et gitt brukernavn:
const FAILS_USERNAME_HOUR = 6; const FAILS_USERNAME_QUARTER_HOUR = 3; const FAILS_IP_HOUR = 24; const FAILS_IP_QUARTER_HOUR = 12;
Vær oppmerksom på at bekreftelseskontrollene mine bruker YiS ActiveRecord-kalt rekkevidde for å forenkle databasekoden:
// rekkevidde av rader siden tidsstempel offentlig funksjon siden ($ tstamp = 0) $ this-> getDbCriteria () -> mergeWith (array ('condition' => '(UNIX_TIMESTAMP (created_at)>'. $ tstamp. ')' ,)); returner $ dette; // omfang av rader før tidsstempel offentlig funksjon older_than ($ tstamp = 0) $ this-> getDbCriteria () -> mergeWith (array ('condition' => '(UNIX_TIMESTAMP (created_at)<'.$tstamp.')', )); return $this; public function username($username=") $this->getDbCriteria () -> mergeWith (array ('condition' => '(brukernavn = "'. $ brukernavn. '")')); returner $ dette; offentlig funksjon ip_address ($ ip_address = ") $ this-> getDbCriteria () -> mergeWith (array ('condition' => '(ip_address ="'. $ ip_address. '")')) ;
Jeg har prøvd å skrive disse eksemplene slik at du enkelt kan tilpasse dem. For eksempel kan du legge ut kontrollene for den siste timen og stole på det siste 15 minutters intervallet. Alternativt kan du endre konstantene for å angi høyere eller lavere terskler for antall pålogginger per intervall. Du kan også skrive mye mer sofistikerte algoritmer. Det er opp til deg.
Med dette eksempelet, for å forbedre ytelsen, vil du kanskje indeksere LoginFail-tabellen etter brukernavn og separat etter IP-adresse.
Eksempelkoden min endrer ikke statusen til kontoer for å blokkere eller gir funksjonalitet for å blokkere bestemte kontoer, jeg lar det gå opp til deg. Hvis du implementerer en blokkerings- og tilbakestillingsmekanisme, kan du kanskje tilby funksjonalitet for å blokkere separat etter IP-adresse eller brukernavn.
Jeg håper du har funnet dette interessant og nyttig. Ta gjerne inn korrigeringer, spørsmål eller kommentarer nedenfor. Jeg ville være spesielt interessert i alternative tilnærminger. Du kan også nå meg på Twitter @ reifman eller email meg direkte.
Kreditt: iBrute forhåndsvisning bilde via Heise Security