Massuppdrag, Rails och Du

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.


Vad är Mass Assignment?

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.


Det (potentiella) problemet med massuppdrag

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.


Hur man hanterar massuppdrag

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 listan

Anvä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: Vitlistan

Problemet 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.

Massuppdragsroller

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

Tillämpningsövergripande konfiguration

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.

stränghet

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.


Rails 4 Starka parametrar: En annan metod

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 pärlan

Lägg till följande rad i Gemfile:

pärla strong_parameters

Stäng av modellbaserat masstilldelningsskydd

Inom config / application.rb:

config.active_record.whitelist_attributes = false

Berätta för modellerna om det

klass användare < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end

Uppdatera kontrollerna

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åtaparams 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.


Avslutar

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!