Începutul anului 2012, un dezvoltator, numit Egor Homakov, a profitat de o gaură de securitate la Github (o aplicație Rails) pentru a obține accesul la proiectul Rails.
Intenția lui a fost, în mare parte, de a sublinia o problemă comună de securitate cu multe aplicații Rails care rezultă dintr-o caracteristică, cunoscută sub denumirea de misiune de masă (și a făcut-o destul de tare). În acest articol, vom examina ce misiune de masă este, cum poate fi o problemă și ce puteți face în privința dvs. în propriile aplicații.
Pentru a începe, să aruncăm o privire mai întâi la ceea ce însemnă misiunea de masă și de ce ea există. Cu titlu de exemplu, imaginați-vă că avem următoarele Utilizator
clasă în aplicația noastră:
# Să presupunem următoarele câmpuri: [: id:: first:: last:: email] Utilizator de clasă < ActiveRecord::Base end
Analiza de masă ne permite să setăm o mulțime de atribute simultan:
ul => "John",: ultima => "Doe",: email => "[email protected]" user = User.new (attrs) user.first # => user.last # => "Doe" user.email # => "[email protected]"
Fără comoditatea alocării în masă, va trebui să scriem o instrucțiune de atribuire pentru fiecare atribut pentru a obține același rezultat. Iată un exemplu:
=> "John",: last => "Doe",: email => "[email protected]" user = User.new user.first = attrs [: first] user.last = attrs [: last] user.email = attrs [: email] user.first # => "John" user.last # => "Doe" user.email # => "[email protected]"
Evident, acest lucru poate deveni obositor și dureros; așa că ne plecăm la picioarele leneșei și spunem, da da, misiunea de masă este un lucru bun.
O problemă cu uneltele ascuțite este că te poți tăia cu ei.
Dar asteapta! O problemă cu uneltele ascuțite este că te poți tăia cu ei. Încărcarea în masă nu face excepție de la această regulă.
Să presupunem acum că aplicația noastră mică imaginară a dobândit capacitatea de a trage rachete. Deoarece nu vrem ca lumea să se transforme în cenușă, adăugăm un câmp de permisiune boolean la Utilizator
model pentru a decide cine poate rachete rachete.
clasa AddCanFireMissilesFlagToUsers < ActiveRecord::Migration def change add_column :users, :can_fire_missiles, :boolean, :default => sfârșitul final fin
Să presupunem, de asemenea, că avem o modalitate prin care utilizatorii să își editeze informațiile de contact: aceasta ar putea fi o formă undeva accesibilă utilizatorului, cu câmpuri de text pentru numele, prenumele și adresa de e-mail a utilizatorului.
Prietenul nostru John Doe decide să-și schimbe numele și să își actualizeze contul de e-mail. Când trimite formularul, browserul va emite o solicitare similară cu următoarea:
PUT http://missileapp.com/users/42?user[first]=NewJohn&user[email][email protected]
Actualizați
acțiune în cadrul UsersController
ar putea să arate ceva asemănător:
def update user = User.find (params [: id]) dacă user.update_attributes (params [: user]) # Alocare de masă! redirect_to home_path altceva face: edita sfârșitul final
Având în vedere exemplul nostru de cerere, params
hash va arata similar cu:
id => 42,: user => : first => "NewJohn",: email => "[email protected]") #: id - parsed de router #: user - șir de interogare
Acum să spunem că NewJohn devine puțin ascuns. Nu aveți nevoie neapărat de un browser pentru a emite o solicitare HTTP, așa că scrie un script care emite următoarea solicitare:
PUT http://missileapp.com/users/42?user[can_fire_missiles]=true
Domenii, cum ar fi
: admin
,:proprietar
, și: public_key
, sunt ușor de ghicit.
Când această solicitare ne lovește Actualizați
acțiunea, update_attributes
apel va vedea : can_fire_missiles => true
, și să dea lui NewJohn capacitatea de a trage rachete! Vaiul a devenit noi.
Acesta este exact modul în care Egor Homakov și-a dat el însuși accesul la proiectul Rails. Deoarece Rails este atât de convențional greu, câmpuri cum ar fi : admin
, :proprietar
, și : public_key
sunt ușor de ghicit. Mai mult, dacă nu există protecții, puteți obține acces la lucruri pe care nu ar trebui să le puteți atinge.
Deci, cum ne protejăm de sarcina de masă proastă? Cum împiedicăm NewJohnurile lumii să ne arunce rachetele cu o abandonare nesăbuită?
Din fericire, Rails oferă câteva instrumente pentru a gestiona problema: attr_protected
și attr_accessible
.
attr_protected
: Lista neagraUtilizarea attr_protected
, puteți specifica câmpurile care nu pot fi niciodată mass-ly alocabilă:
utilizator de clasă < ActiveRecord::Base attr_protected :can_fire_missiles end
Acum, orice încercare de a acorda masă can_fire_missiles
atributul va eșua.
attr_accessible
: WhiteListProblema cu attr_protected
este că este prea ușor să uitați să adăugați un câmp nou introdus în listă.
Aici e locul attr_accessible
intră. După cum probabil ați ghicit, este opusul attr_protected
: listați numai atributele pe care doriți să le asignați în funcție de masă.
Ca atare, ne putem schimba Utilizator
clasă la această abordare:
utilizator de clasă < ActiveRecord::Base attr_accessible :first, :last, :email end
Aici, menționăm în mod explicit ceea ce poate fi atribuit în masă. Orice altceva va fi interzis. Avantajul este că dacă, adică, adăugăm un admin
pavilion la Utilizator
model, va fi în siguranță în mod automat de la alocarea de masă.
Ca regulă generală, ar trebui să preferați attr_accessible
la attr_protected
, deoarece vă ajută să vă greșiți pe deplin de prudență.
Rails 3.1 a introdus conceptul de "roluri" de atribuire a maselor. Ideea este că puteți specifica diferite attr_protected
și attr_accessible
liste pentru situații diferite.
utilizator de clasă < ActiveRecord::Base attr_accessible :first, :last, :email # :default role attr_accessible :can_fire_missiles, :as => : admin #: admin rolul utilizatorului final = User.new (: can_fire_missiles => true) # utilizează rolul implicit user.can_fire_missiles # => false user2 = User.new (: can_fire_missiles => true =>: admin) user.can_fire_missiles # => true
Puteți controla comportamentul de alocare a masei în aplicația dvs. prin editarea config.active_record.whitelist_attributes
stabilirea în cadrul config / application.rb
fişier.
Dacă este setat la fals
, protecția pentru alocarea masei va fi activată numai pentru modelele pe care le specificați attr_protected
sau attr_accessible
listă.
Dacă este setat la Adevărat
, repartizarea masei va fi imposibilă pentru toate modelele, cu excepția cazului în care acestea specifică o attr_protected
sau attr_accessible
listă. Rețineți că această opțiune este activată în mod prestabilit de la Rails 3.2.3 înainte.
Începând cu Rails 3.2, există suplimentar o opțiune de configurare pentru a controla strictețea protecției de alocare a masei: config.active_record.mass_assignment_sanitizer
.
Dacă este setat la :strict
, va ridica un ActiveModel :: MassAssignmentSecurity :: Eroare
de fiecare dată când cererea dvs. încearcă să atribuie în mod masiv ceva ce nu trebuie. Va trebui să rezolvați în mod explicit aceste erori. Începând cu versiunea v3.2, această opțiune este stabilită pentru dvs. în mediile de dezvoltare și de testare (dar nu și în producție), probabil pentru a vă ajuta să identificați unde se pot întâmpla probleme legate de repartizarea în masă.
Dacă nu este setat, se va ocupa în mod silențios de protecția în masă - ceea ce înseamnă că va seta doar atributele pe care le presupune, dar nu va ridica o eroare.
Sarcina de asignare de masă este într-adevăr despre manipularea intrărilor neimpozitate.
Incidentul Homakov a inițiat o conversație în jurul protecției misiunii de masă în comunitatea Rails (și în alte limbi, de asemenea); o întrebare interesantă a fost ridicată: securitatea alocării maselor aparține în stratul model?
Unele aplicații au cerințe complexe de autorizare. Încercarea de a gestiona toate cazurile speciale din stratul modelului poate începe să se simtă neclintit și prea complicat, mai ales dacă vă găsiți tencuială rolurile
peste tot.
O insight importantă aici este că securitatea în misiune în masă este într-adevăr despre manipularea intrărilor neimpozitate. Întrucât o aplicație Rails primește intrarea utilizatorului în stratul de controler, dezvoltatorii au început să se întrebe dacă ar fi mai bine să se ocupe de problema acolo în loc de modele ActiveRecord.
Rezultatul acestei discuții este gemul Parametrii puternici, disponibil pentru utilizare cu Rails 3, și implicit în lansarea versiunii Rails 4.
Presupunând că cererea noastră de rachete este învinsă de Rails 3, iată cum am putea să o actualizăm pentru a fi folosită cu gemele parametrilor stong:
Adăugați următoarea linie în Gemfile:
gem puternic_parametre
În config / application.rb
:
config.active_record.whitelist_attributes = false
utilizator de clasă < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end
clasa 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
Dacă încerci ceva de genul ăsta user.update_attributes (params)
, veți primi o eroare în cererea dvs. Trebuie să suni mai întâi permite
pe params
hash cu cheile care sunt permise pentru o acțiune specifică.
Avantajul acestei abordări constă în faptul că trebuie să fii explicit în privința inputului pe care îl accepți în punctul în care ai de-a face cu intrarea.
Notă: Dacă aceasta a fost o aplicație Rails 4, codul de control este tot ce ne-ar fi nevoie; funcționalitatea puternică a parametrilor va fi coaptă în mod implicit. Ca rezultat, nu veți avea nevoie să includeți modelul sau bijuteria separată din Gemfile.
Încărcarea în masă poate fi o caracteristică incredibil de utilă atunci când scrieți codul Rails. De fapt, este aproape imposibil să scrieți un cod rezonabil fără Rails. Din nefericire, misiunea de masă fără minte este, de asemenea, plină de pericol.
Sperăm că sunteți acum echipați cu instrumentele necesare pentru a naviga în siguranță în apele misiunii de masă. Iată mai puține rachete!