Affectation de masse, Rails et vous

Au début de 2012, un développeur, nommé Egor Homakov, a profité d'une faille de sécurité chez Github (une application Rails) pour obtenir un accès valide au projet Rails..

Son intention était principalement de signaler un problème de sécurité commun à de nombreuses applications Rails, qui résulte d'une fonctionnalité appelée affectation de masse (et l'a fait plutôt fort). Dans cet article, nous verrons ce qu'est l'affectation en masse, en quoi cela peut poser problème et ce que vous pouvez faire à ce sujet dans vos propres applications..


Quelle est l'affectation de masse?

Pour commencer, examinons d'abord ce que signifie affectation en masse et pourquoi elle existe. À titre d’exemple, imaginons que nous avons les éléments suivants Utilisateur classe dans notre application:

# Supposons les champs suivants: [: id,: first,: last,: email] class User < ActiveRecord::Base end

L'affectation en masse nous permet de définir plusieurs attributs à la fois:

attrs = : first => "John",: last => "Doe",: email => "[email protected]" user = User.new (attrs) user.first # => "John" user.last # => "Doe" user.email # => "[email protected]"

Sans la commodité de l'affectation en masse, nous devrions écrire une instruction d'affectation pour chaque attribut pour obtenir le même résultat. Voici un exemple:

attrs = : first => "John",: last => "Doe",: email => "[email protected]" user = User.new user.first = attrs [: premier] user.last = attrs [: last] user.email = attrs [: email] user.first # => "John" user.last # => "Doe" user.email # => "[email protected]"

Évidemment, cela peut devenir fastidieux et pénible; donc nous nous inclinons devant la paresse et disons, oui oui, l'affectation de masse est une bonne chose.


Le problème (potentiel) avec l'assignation en masse

Un problème avec les outils tranchants est que vous pouvez vous couper avec eux.

Mais attendez! Un problème avec les outils tranchants est que vous pouvez vous couper avec eux-mêmes. L'affectation en masse n'échappe pas à cette règle.

Supposons maintenant que notre petite application imaginaire a acquis la capacité de tirer des missiles. Comme nous ne voulons pas que le monde se transforme en cendres, nous ajoutons un champ de permission booléen au Utilisateur modèle pour décider qui peut tirer des missiles.

classe AddCanFireMissilesFlagToUsers < ActiveRecord::Migration def change add_column :users, :can_fire_missiles, :boolean, :default => faux fin fin

Supposons également que les utilisateurs disposent d'un moyen de modifier leurs informations de contact: il peut s'agir d'un formulaire accessible à l'utilisateur avec des champs de texte pour le prénom, le nom de famille et l'adresse électronique de l'utilisateur..

Notre ami John Doe décide de changer de nom et de mettre à jour son compte de messagerie. Quand il soumet le formulaire, le navigateur émettra une demande semblable à celle-ci:

PUT http://missileapp.com/users/42?user[first]=NewJohn&user[email][email protected]

le mettre à jour action dans le UtilisateursContrôleur pourrait ressembler à quelque chose comme:

def update user = User.find (params [: id]) si user.update_attributes (params [: user]) # Affectation en masse! redirect_to home_path else render: modifier fin fin

Compte tenu de notre exemple de demande, le params hash ressemblera à:

: id => 42,: user => : first => "NewJohn",: email => "[email protected]" #: id - analysé par le routeur #: user - analysé à partir du courrier entrant chaîne de requête

Maintenant, disons que NewJohn devient un peu sournois. Vous n'avez pas nécessairement besoin d'un navigateur pour émettre une requête HTTP. Il écrit donc un script qui émet la requête suivante:

PUT http://missileapp.com/users/42?user[can_fire_missiles]=true

Les champs, comme : admin, :propriétaire, et :Clé publique, sont assez faciles à deviner.

Quand cette demande frappe notre mettre à jour l'action, la update_attributes appel va voir : can_fire_missiles => true, et donnez à NewJohn la possibilité de tirer des missiles! Le malheur est devenu nous.

C’est exactement comme cela que Egor Homakov s’est donné l’engagement d’accéder au projet Rails. Parce que Rails est si conventionnel, des champs comme : admin, :propriétaire, et :Clé publique sont assez faciles à deviner. De plus, s’il n’ya pas de protection en place, vous pouvez accéder à des choses que vous n'êtes pas censé pouvoir toucher..


Comment gérer l'affectation de masse

Alors, comment pouvons-nous nous protéger de l'affectation de masse sans motif? Comment pouvons-nous empêcher les NewJohns du monde de tirer nos missiles avec un abandon inconsidéré?

Heureusement, Rails fournit quelques outils pour gérer le problème: attr_protected et attr_accessible.

attr_protected: La liste noire

En utilisant attr_protected, vous pouvez spécifier quels champs peuvent ne jamais être massivement assignable:

classe utilisateur < ActiveRecord::Base attr_protected :can_fire_missiles end

Maintenant, toute tentative d'assigner en masse le can_fire_missiles attribut va échouer.

attr_accessible: La liste blanche

Le problème avec attr_protected est-ce qu'il est trop facile d'oublier d'ajouter un champ nouvellement implémenté à la liste.

C'est ici que attr_accessible entre. Comme vous l’auriez peut-être deviné, c’est le contraire de attr_protected: liste seulement les attributs que vous voulez assigner en masse.

En tant que tel, nous pouvons changer notre Utilisateur classe à cette approche:

classe utilisateur < ActiveRecord::Base attr_accessible :first, :last, :email end

Ici, nous listons explicitement ce qui peut être assigné en masse. Tout le reste sera refusé. L’avantage ici est que si nous ajoutons un admin drapeau au Utilisateur modèle, il sera automatiquement protégé de l'affectation en masse.

En règle générale, vous devriez préférer attr_accessible à attr_protected, comme il vous aide à pécher par excès de prudence.

Rôles d'attribution en masse

Rails 3.1 a introduit le concept de "rôles" d'affectation en masse. L’idée est que vous pouvez spécifier différents attr_protected et attr_accessible listes pour différentes situations.

classe utilisateur < ActiveRecord::Base attr_accessible :first, :last, :email # :default role attr_accessible :can_fire_missiles, :as => : admin #: admin rôle user = User.new (: can_fire_missiles => true) # utilise le rôle par défaut user.can_fire_missiles # => false user2 = User.new (: can_fire_missiles => true,: as =>: admin) user.can_fire_missiles # => true

Configuration à l'échelle de l'application

Vous pouvez contrôler le comportement des assignations en masse dans votre application en modifiant la config.active_record.whitelist_attributes mise dans le config / application.rb fichier.

Si réglé à faux, la protection d’affectation en masse ne sera activée que pour les modèles où vous spécifiez un attr_protected ou attr_accessible liste.

Si réglé à vrai, l’affectation en masse sera impossible pour tous les modèles, sauf s’ils spécifient un attr_protected ou attr_accessible liste. Veuillez noter que cette option est activée par défaut à partir de Rails 3.2.3..

Rigueur

Depuis Rails 3.2, il existe en outre une option de configuration permettant de contrôler la rigueur de la protection d'attribution en masse: config.active_record.mass_assignment_sanitizer.

Si réglé à :strict, il va élever un ActiveModel :: MassAssignmentSecurity :: Error chaque fois que votre application tente d'assigner en masse quelque chose qu'elle ne devrait pas. Vous devrez gérer ces erreurs explicitement. À partir de la v3.2, cette option est définie pour vous dans les environnements de développement et de test (mais pas de production), sans doute pour vous aider à localiser les problèmes d'affectation en masse..

S'il n'est pas défini, il gérera la protection d'affectation en masse en mode silencieux - ce qui signifie qu'il ne définira que les attributs qu'il est censé définir, mais ne génère pas d'erreur..


Rails 4 Strong Parameters: une approche différente

La sécurité des affectations en masse consiste vraiment à gérer des entrées non fiables.

L'incident d'Homakov a initié une conversation autour de la protection des assignations en masse dans la communauté Rails (et dans d'autres langues également); une question intéressante a été soulevée: la sécurité des assignations en masse appartient-elle à la couche modèle?

Certaines applications nécessitent des autorisations complexes. Essayer de gérer tous les cas particuliers de la couche de modèle peut commencer à sembler maladroit et trop compliqué, surtout si vous vous retrouvez en train de plâtrer rôles partout.

Un élément clé à retenir ici est que la sécurité des affectations en masse concerne en réalité la gestion des entrées non fiables. Lorsqu'une application Rails reçoit les entrées de l'utilisateur dans la couche du contrôleur, les développeurs se sont demandé s'il serait peut-être préférable de traiter le problème là-bas plutôt que des modèles ActiveRecord..

Le résultat de cette discussion est la gem Strong Parameters, disponible pour une utilisation avec Rails 3, et une valeur par défaut dans la prochaine version de Rails 4..

En supposant que notre application de missile soit bult sur Rails 3, voici comment nous pourrions la mettre à jour pour l'utiliser avec le paramètre stong gem:

Ajouter la gemme

Ajoutez la ligne suivante au fichier Gemfile:

gem strong_parameters

Désactiver la protection d'attribution de masse basée sur un modèle

Dans config / application.rb:

config.active_record.whitelist_attributes = false

Parlez aux modèles

classe utilisateur < ActiveRecord::Base include ActiveModel::ForbiddenAttributesProtection end

Mettre à jour les contrôleurs

classe 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

Maintenant, si vous essayez quelque chose comme user.update_attributes (params), vous obtiendrez une erreur dans votre application. Vous devez d'abord appeler permis sur le params hachage avec les clés autorisées pour une action spécifique.

L'avantage de cette approche est que vous devez indiquer explicitement quelle entrée vous acceptez au moment où vous traitez avec l'entrée.

Remarque: S'il s'agissait d'une application Rails 4, le code du contrôleur est tout ce dont nous avons besoin; la fonctionnalité de paramètres forts sera intégrée par défaut. En conséquence, vous n’avez pas besoin d’inclure dans le modèle ni de la gemme séparée dans le fichier Gemfile..


Emballer

L'affectation en masse peut être une fonctionnalité extrêmement utile lors de l'écriture de code Rails. En fait, il est presque impossible d'écrire un code Rails raisonnable sans celui-ci. Malheureusement, l'affectation de masse sans esprit est également périlleuse.

J'espère que vous êtes maintenant équipé des outils nécessaires pour naviguer en toute sécurité dans les eaux de masse. Voici pour moins de missiles!