Tidlig i 2012 tok en utvikler, kalt Egor Homakov, fordel av et sikkerhetshull på Github (en Rails app) for å få forpliktet tilgang til Rails prosjektet.
Hans hensikt var for det meste å påpeke et felles sikkerhetsproblem med mange Rails-programmer som resulterer fra en funksjon, kjent som masseoppgave (og gjorde det ganske høyt). I denne artikkelen vurderer vi hva masseoppgave er, hvordan det kan være et problem, og hva du kan gjøre med det i dine egne applikasjoner.
For å begynne med, la oss først ta en titt på hva masseoppdrag betyr, og hvorfor den eksisterer. For eksempel, tenk at vi har følgende Bruker
klasse i vår søknad:
# Anta følgende felt: [: id,: først,: siste,: e-post] klasse Bruker < ActiveRecord::Base end
Masseoppgave tillater oss å sette en haug med attributter på en gang:
attrs = : first => "John",: last => "Doe",: email => "[email protected]" bruker = User.new (attrs) user.first # => "John" user.last # => "Doe" user.email # => "[email protected]"
Uten massetilordningens bekvemmelighet må vi skrive en oppgaveoppgave for hvert attributt for å oppnå det samme resultatet. Her er et eksempel:
attrs = : first => "John",: last => "Doe",: email => "[email protected]" bruker = User.new user.first = attrs [: first] user.last = attrs [: last] user.email = attrs [: email] user.first # => "John" user.last # => "Doe" user.email # => "[email protected]"
Tydeligvis kan dette bli kjedelig og smertefullt; slik at vi bøyer seg på luktens føtter og sier ja ja, masseoppdrag er en god ting.
Et problem med skarpe verktøy er at du kan kutte deg med dem.
Men vent! Et problem med skarpe verktøy er at du kan kutte deg med dem. Masseoppgave er ikke noe unntak fra denne regelen.
Anta nå at vår lille, imaginære søknad har oppnådd evnen til å brenne missiler. Da vi ikke vil at verden skal vende seg til aske, legger vi til et boolesk tillatelsesfelt til Bruker
modell for å avgjøre hvem som kan brenne missiler.
klasse AddCanFireMissilesFlagToUsers < ActiveRecord::Migration def change add_column :users, :can_fire_missiles, :boolean, :default => falsk endeend
La oss også anta at vi har en måte for brukerne å redigere deres kontaktinformasjon: dette kan være et skjema et sted som er tilgjengelig for brukeren med tekstfelter for brukerens fornavn, etternavn og e-postadresse.
Vår venn John Doe bestemmer seg for å endre navn og oppdatere e-postkontoen sin. Når han sender skjemaet, utsteder nettleseren en forespørsel som ligner på følgende:
PUT http://missileapp.com/users/42?user[first]=NewJohn&user[email][email protected]
De Oppdater
handling innenfor UsersController
kan se noe ut som:
def update user = User.find (params [: id]) hvis user.update_attributes (params [: user]) # Masseoppgave! redirect_to home_path else render: rediger slutten
Gitt vår eksempelforespørsel, er params
hash vil se ut som:
: id => 42,: user => : first => "NewJohn",: email => "[email protected]" #: id - analysert av ruteren #: bruker - analysert fra innkommende søkestreng
La oss nå si at NewJohn blir litt sneaky. Du trenger ikke nødvendigvis en nettleser for å utstede en HTTP-forespørsel, så han skriver et skript som utsteder følgende forespørsel:
PUT http://missileapp.com/users/42?user[can_fire_missiles]=true
Felt, som
: admin
,:Eieren
, og: public_key
, er ganske enkelt gjettelig.
Når denne forespørselen treffer vår Oppdater
handling, den update_attributes
samtale vil se : can_fire_missiles => true
, og gi NewJohn muligheten til å skyte raketter! Ve har blitt oss.
Dette er akkurat slik Egor Homakov gav seg selv tilgang til Rails-prosjektet. Fordi Rails er så konvensjonelle, tunge felt som : admin
, :Eieren
, og : public_key
er ganske enkelt gjettelig. Videre, hvis det ikke finnes beskyttelser på plass, kan du få tilgang til ting som du ikke skal kunne røre.
Så hvordan beskytter vi oss selv fra vilje masseoppdrag? Hvordan hindrer vi at NewJohns fra verden skyter våre missiler med hensynsløs forlatelse?
Heldigvis gir Rails et par verktøy for å håndtere problemet: attr_protected
og attr_accessible
.
attr_protected
: The BlackListVed hjelp av attr_protected
, Du kan spesifisere hvilke felt som aldri kan være masse-ly dras:
klassen bruker < ActiveRecord::Base attr_protected :can_fire_missiles end
Nå, ethvert forsøk på masse-tilordne can_fire_missiles
attributtet vil mislykkes.
attr_accessible
: The WhiteListProblemet med attr_protected
er at det er for enkelt å glemme å legge til et nylig implementert felt i listen.
Dette er hvor attr_accessible
kommer inn. Som du kanskje har gjettet, er det motsatte av attr_protected
: Bare oppgi attributter som du vil være massetildelbare.
Som sådan kan vi bytte våre Bruker
klasse til denne tilnærmingen:
klassen bruker < ActiveRecord::Base attr_accessible :first, :last, :email end
Her viser vi eksplisitt hva som kan tilordnes masse. Alt annet vil bli ugyldiggjort. Fordelen her er at hvis vi sier, legg til en admin
flagg til Bruker
modell, vil det automatisk være trygt fra masseoppdrag.
Som hovedregel bør du foretrekke attr_accessible
til attr_protected
, som det hjelper deg med å feire på forsiktighetssiden.
Rails 3.1 introduserte begrepet massetildeling "roller". Tanken er at du kan spesifisere forskjellig attr_protected
og attr_accessible
lister for forskjellige situasjoner.
klassen bruker < ActiveRecord::Base attr_accessible :first, :last, :email # :default role attr_accessible :can_fire_missiles, :as => : admin #: admin rolle sluttbruker = User.new (: can_fire_missiles => true) # bruker: standard roll user.can_fire_missiles # => false user2 = User.new (: can_fire_missiles => true,: as =>: admin) user.can_fire_missiles # => true
Du kan kontrollere masseoppdragsadferd i din søknad ved å redigere config.active_record.whitelist_attributes
Innstilling i config / application.rb
fil.
Hvis satt til falsk
, Massetildelingsbeskyttelse vil bare bli aktivert for modellene der du angir en attr_protected
eller attr_accessible
liste.
Hvis satt til ekte
, masseoppgave vil være umulig for alle modeller med mindre de angir en attr_protected
eller attr_accessible
liste. Vær oppmerksom på at dette alternativet er aktivert som standard fra Rails 3.2.3 fremover.
Fra begynnelsen med Rails 3.2 er det i tillegg et konfigurasjonsalternativ for å kontrollere strengen av massetildelingsbeskyttelse: config.active_record.mass_assignment_sanitizer
.
Hvis satt til :streng
, det vil heve en ActiveModel :: MassAssignmentSecurity :: Feil
når som helst som søknaden din forsøker å massere tilordne noe det ikke skal. Du må eksplisitt håndtere disse feilene. Som av v3.2, er dette alternativet satt for deg i utviklings- og testmiljøene (men ikke produksjon), antagelig for å hjelpe deg med å spore opp hvor masseoppdragsproblemer kan være.
Hvis den ikke er satt, vil den håndtere masseoppdragsbeskyttelsen stille - noe som betyr at den bare vil angi attributter den skal, men vil ikke hente en feil.
Massetilsynssikkerhet handler egentlig om å håndtere usikker innsats.
Homakov-hendelsen startet en samtale rundt massetildelingsbeskyttelse i Rails-samfunnet (og videre til andre språk); et interessant spørsmål ble tatt opp: tilhører sikkerhetstilsynet i modelllaget?
Enkelte programmer har komplekse autorisasjonskrav. Å forsøke å håndtere alle spesielle tilfeller i modelllaget kan begynne å føle seg klumpete og overkompliserte, spesielt hvis du finner deg selv plastering roller
over alt.
En viktig innsikt her er at masseoppdragssikkerhet egentlig handler om å håndtere usikker innsats. Som en Rails-applikasjon mottar brukerinngang i kontrolleringslaget, begynte utviklere å lure på om det kan være bedre å håndtere problemet der istedenfor ActiveRecord-modeller.
Resultatet av denne diskusjonen er den kraftige parameternes perle, tilgjengelig for bruk med Rails 3, og en standard i den kommende Rails 4-utgivelsen.
Forutsatt at vårt missilprogram er bult på Rails 3, her er hvordan vi kan oppdatere den til bruk med stongparameter-perlen:
Legg til følgende linje i Gemfile:
perle strong_parameters
Innenfor config / application.rb
:
config.active_record.whitelist_attributes = false
klassen bruker < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end
klasse UsersController < ApplicationController def update user = User.find(params[:id]) if user.update_attributes(user_params) # see below redirect_to home_path else render :edit end end private # Require that :user be a key in the params Hash, # and only accept :first, :last, and :email attributes def user_params params.require(:user).permit(:first, :last, :email) end end
Nå, hvis du forsøker noe som user.update_attributes (parametere)
, Du får en feil i søknaden din. Du må først ringe tillate
på params
hash med nøklene som er tillatt for en bestemt handling.
Fordelen med denne tilnærmingen er at du må være eksplisitt om hvilket innspill du aksepterer i det punktet du har å gjøre med inngangen.
Merk: Hvis dette var en Rails 4 app, er kontrolleren koden alt vi trenger; Den sterke parameterfunksjonaliteten vil bli bakt inn som standard. Som et resultat trenger du ikke inkluderingen i modellen eller den separate perlen i Gemfile.
Masseoppgave kan være en utrolig nyttig funksjon ved skriving av Rails kode. Faktisk er det nesten umulig å skrive rimelig Rails kode uten den. Dessverre er tankeløs masseoppgave også fulle av fare.
Forhåpentligvis er du nå utstyrt med de nødvendige verktøyene for å navigere trygt i masseoppdragsvannene. Her er til færre raketter!