Asignare de masă, șine și tu

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


Ce este asignarea de masă?

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.


Problema (potențială) cu asignarea de masă

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.


Cum se face cu asignarea în masă

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 neagra

Utilizarea 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: WhiteList

Problema 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ță.

Asocierea rolurilor

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

Aplicație la nivel de configurare

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.

strânsoare

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


Rails 4 Parametrii puternici: o abordare diferită

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 bijuteria

Adăugați următoarea linie în Gemfile:

gem puternic_parametre

Opriți protecția asociată modelului de masă

În config / application.rb:

config.active_record.whitelist_attributes = false

Spuneți modele despre asta

utilizator de clasă < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end

Actualizați controlerele

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.


Înfășurarea în sus

Î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!

Cod