I början av 2012 utnyttjade en utvecklare, namngiven Egor Homakov, ett säkerhetshål hos Github (a Rails app) för att få tillträde till Rails-projektet.
Hans avsikt var för det mesta att påpeka ett gemensamt säkerhetsproblem med många Rails-appar som härrör från en funktion, känd som massuppgift (och gjorde det ganska högt). I den här artikeln granskar vi vilken massuppgift som är, hur det kan vara ett problem och vad du kan göra om det i dina egna applikationer.
Till att börja med, låt oss först ta en titt på vad massuppdrag innebär och varför det finns. Tänk på ett exempel att vi har följande Användare
klass i vår ansökan:
# Antag följande fält: [: id,: första,: sista,: e-post] klass Användare < ActiveRecord::Base end
Masstilldelning tillåter oss att ställa in en massa attribut på en gång:
attrs = : first => "John",: last => "Doe",: email => "[email protected]" användare = User.new (attrs) user.first # => "John" user.last # => "Doe" user.email # => "[email protected]"
Utan att masstilldelningen är bekväm, måste vi skriva ett uppdragsförklaring för varje attribut för att uppnå samma resultat. Här är ett exempel:
attrs = : first => "John",: last => "Doe",: email => "[email protected]" användare = User.new user.first = attrs [: first] user.last = attrs [: sista] user.email = attrs [: email] user.first # => "John" user.last # => "Gör" user.email # => "[email protected]"
Självklart kan detta bli tråkigt och smärtsamt; så vi böjer sig vid lövhetens fötter och säger ja ja, massuppgift är en bra sak.
Ett problem med skarpa verktyg är att du kan skära dig själv med dem.
Men vänta! Ett problem med skarpa verktyg är att du kan skära dig själv med dem. Masstilldelning är inget undantag från denna regel.
Antag nu att vår lilla imaginära ansökan har förvärvat förmågan att avfyra missiler. Eftersom vi inte vill att världen ska vända sig till aska, lägger vi till ett booleskt tillståndsfält till Användare
modell för att bestämma vem som kan avfyra missiler.
klass AddCanFireMissilesFlagToUsers < ActiveRecord::Migration def change add_column :users, :can_fire_missiles, :boolean, :default => falskt slutänden
Låt oss också anta att vi har ett sätt för användarna att redigera deras kontaktuppgifter: Det här kan vara en form någonstans som är tillgänglig för användaren med textfält för användarens förnamn, efternamn och e-postadress.
Vår vän John Doe bestämmer sig för att ändra sitt namn och uppdatera sitt e-postkonto. När han skickar formuläret utfärdar webbläsaren en förfrågan som liknar följande:
PUT http://missileapp.com/users/42?user[first]=NewJohn&user[email][email protected]
De uppdatering
handling inom UsersController
kan se något ut som:
def update user = User.find (params [: id]) om user.update_attributes (params [: user]) # Massuppgift! redirect_to home_path else render: redigera slutet slutet
Med tanke på vår exemplarförfrågan, params
hash kommer att likna:
: id => 42,: user => : first => "NewJohn",: email => "[email protected]" #: id - parsed av routern #: användaren - analyseras från inkommande frågesträng
Låt oss nu säga att NewJohn blir lite lurig. Du behöver inte nödvändigtvis en webbläsare för att utfärda en HTTP-begäran, så han skriver ett skript som utfärdar följande förfrågan:
PUT http://missileapp.com/users/42?user[can_fire_missiles]=true
Fält, som
:administration
,:ägare
, och: public_key
, är ganska lätt gissningsbara.
När denna förfrågan träffar vår uppdatering
handling, the update_attributes
samtal kommer att se : can_fire_missiles => true
, och ge NewJohn möjligheten att skjuta missiler! Ve har blivit oss.
Det är precis som Egor Homakov gav sig själv tillträde till Rails-projektet. Eftersom Rails är så konventionella-tunga, fält som :administration
, :ägare
, och : public_key
är ganska lätt gissningsbara. Om det inte finns några skydd på plats kan du få tillgång till saker som du inte ska kunna röra.
Så hur skyddar vi oss själva från missnöje? Hur förhindrar vi att NewJohns i världen bränner våra missiler med hänsynslös överge?
Lyckligtvis ger Rails ett par verktyg för att hantera problemet: attr_protected
och attr_accessible
.
attr_protected
: Svarta listanAnvänder sig av attr_protected
, Du kan ange vilka fält som aldrig får vara mass-ly hänförlig:
klass användare < ActiveRecord::Base attr_protected :can_fire_missiles end
Nu, ett försök att massa-tilldela can_fire_missiles
attributet kommer att misslyckas.
attr_accessible
: VitlistanProblemet med attr_protected
är att det är för lätt att glömma att lägga till ett nyligen implementerat fält i listan.
Det är här attr_accessible
kommer in. Som du kanske har gissat är det motsatsen till attr_protected
: lista bara de attribut som du vill vara massaöverdragbara.
Som sådan kan vi byta vår Användare
klass till detta tillvägagångssätt:
klass användare < ActiveRecord::Base attr_accessible :first, :last, :email end
Här listar vi uttryckligen ut vad som kan tilldelas massa. Allt annat kommer att förbjudas. Fördelen här är att om vi säger, lägg till en administration
flagga till Användare
modell, kommer det automatiskt att vara säkert från massuppdrag.
Som regel bör du föredra attr_accessible
till attr_protected
, eftersom det hjälper dig att erta på sidan av försiktighet.
Rails 3.1 introducerade begreppet mass-assignment "roller". Tanken är att du kan ange olika attr_protected
och attr_accessible
listar för olika situationer.
klass användare < ActiveRecord::Base attr_accessible :first, :last, :email # :default role attr_accessible :can_fire_missiles, :as => : admin #: admin roll slutanvändare = User.new (: can_fire_missiles => true) # använder: standard roll user.can_fire_missiles # => false user2 = User.new (: can_fire_missiles => true,: as =>: admin) user.can_fire_missiles # => true
Du kan styra massuppdragsbeteendet i din ansökan genom att redigera config.active_record.whitelist_attributes
inställning inom config / application.rb
fil.
Om den är inställd på falsk
, Masstilldelningsskydd kommer endast att aktiveras för de modeller där du anger en attr_protected
eller attr_accessible
lista.
Om den är inställd på Sann
, Masstilldelningen är omöjlig för alla modeller om inte de anger en attr_protected
eller attr_accessible
lista. Observera att det här alternativet är aktiverat som standard från Rails 3.2.3 framåt.
Från och med Rails 3.2 finns det dessutom ett konfigurationsalternativ för att kontrollera strängheten i masstilldelningsskydd: config.active_record.mass_assignment_sanitizer
.
Om den är inställd på :sträng
, det kommer att höja en ActiveModel :: MassAssignmentSecurity :: Error
när som helst som din ansökan försöker att massa-tilldela något som det inte borde göra. Du måste uttryckligen hantera dessa fel. Från v3.2 är det här alternativet inställt för dig i utvecklings- och testmiljöer (men inte produktion), förmodligen för att hjälpa dig att spåra där massuppdragsproblem kan vara.
Om den inte är inställd, kommer den att hantera massöverdelningsskydd tyst - vilket innebär att den bara anger de attribut som den ska men kommer inte att ge upphov till ett fel.
Masstilldelningssäkerhet handlar verkligen om att hantera otillförlitlig inmatning.
Homakovincidenten inledde en konversation kring masstilldelningsskydd i Rails community (och vidare till andra språk); en intressant fråga höjdes: Hos masstilldelningssäkerhet hörs i modellskiktet?
Vissa program har komplicerade behörighetskrav. Att försöka hantera alla speciella fall i modellskiktet kan börja känna sig klumpigt och överkomplicerat, särskilt om du befinner dig i plastering roller
över hela stället.
En viktig inblick i detta är att massuppdragssäkerhet verkligen handlar om att hantera otillförlitlig inmatning. Som en Rails-applikation får användarinmatning i reglerskiktet började utvecklare undra om det kan vara bättre att hantera problemet där istället för ActiveRecord-modeller.
Resultatet av denna diskussion är den starka parameterns pärla, tillgänglig för användning med Rails 3, och en standard i den kommande Rails 4-utgåvan.
Förutsatt att vår missilansökan är bult på Rails 3, så här kan vi uppdatera den för användning med stongparameterns pärla:
Lägg till följande rad i Gemfile:
pärla strong_parameters
Inom config / application.rb
:
config.active_record.whitelist_attributes = false
klass användare < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end
klass 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
Nu, om du försöker något liknande user.update_attributes (params)
, Du får ett fel i din ansökan. Du måste först ringa tillåta
på params
hash med nycklarna som är tillåtna för en specifik åtgärd.
Fördelen med detta tillvägagångssätt är att du måste vara tydlig om vilken ingång du accepterar när du hanterar inmatningen.
Notera: Om det här var en Rails 4 app, är kontrollerkoden allt vi behöver, Den starka parameterns funktionalitet bakas som standard. Som ett resultat behöver du inte inkludera i modellen eller den separata pärlan i Gemfile.
Masstilldelning kan vara en otroligt användbar funktion när du skriver Rails-kod. Det är faktiskt nästan omöjligt att skriva rimlig Rails kod utan den. Olyckligtvis är tanklös massauppgift också full av fara.
Förhoppningsvis är du nu utrustad med nödvändiga verktyg för att navigera säkert i masstilldelningsvattnet. Här är färre missiler!