environ 3 minutes de lecture

Sécurité

Si vous pensez que la technologie peut résoudre vos problèmes de sécurité alors vous n'avez rien compris aux problèmes ni à la technologie. (Bruce Schneier)

Scroll

Utilisateurs à privilèges

Limiter les rôles attribuables

Les utilisateurs avec accès au back-office ne doivent pas pouvoir modifier les utilisateurs avec des droits supérieurs aux leurs. Un non admin qui a le droit de modifier le rôle d’un user peut modifier le code html de son formulaire pour attribuer un rôle superadmin - même si le rôle n’est pas affiché par défaut dans le formulaire. Il faut donc vérifier qu’un user a bien le droit de donner le rôle qu’il veut donner.

Avec une méthode listant les rôles gérés, par exemple:

class User
  attr_accessor :modified_by

  before_validation :check_modifier_role

  def roles_managed
    [:admin, :editor, :translator]
  end

  protected

  def check_modifier_role
    errors.add(:role, 'cannot be set to this role') if modified_by && !modified_by.get_roles_managed.include?(self.role)
  end
end

Evidemment, la vraie méthode prendrait les infos en db.

Il suffit d’ajouter dans le controller:

def update
  @user.modified_by = current_user
  ...
end

Interdire l’édition d’un rôle supérieur

Un utilisateur admin ne doit pas pouvoir éditer un superadmin.

can :manage, User, role: user.roles_managed

Injection javascript

Nettoyer les champs de saisie AVANT l’entrée en base de donnée

Pour les champs importants, notamment ceux saisissables en front

before_validation :sanitize_fields

def sanitize_fields
  full_sanitizer = Rails::Html::FullSanitizer.new

  # Only text allowed
  self.email = full_sanitizer.sanitize(self.email)
  self.name = full_sanitizer.sanitize(self.name)
  self.firstname = full_sanitizer.sanitize(self.firstname)
  self.gender = full_sanitizer.sanitize(self.gender)
  self.mobile = full_sanitizer.sanitize(self.mobile)
  self.pos = full_sanitizer.sanitize(self.pos)
  self.address = full_sanitizer.sanitize(self.address)
  self.zipcode = full_sanitizer.sanitize(self.zipcode)
  self.city = full_sanitizer.sanitize(self.city)
end

TODO (gem?)

sanitize_fields :email, :name, :firstname, :gender, :mobile, :pos, :address, :zipcode, :city

Eviter des méthodes to_s trop ouvertes

On peut sanitize aussi la méthode to_s des modèles pour éviter l’injection XSS, si une donnée hackée a réussi à rentrer dans la base de données.

def to_s
  sanitize("#{first_name} #{last_name}")
end

Sanitize par sécurité les affichages dans les vues

<%= sanitize model.method %>

Authentification

Eviter l’usurpation de cookies de session

Par défaut lorsqu’on se déconnecte avec devise le dernier identifiant de session stocké en cookie reste valable. Donc si on “copie” cet identifiant avant de se dé-logger et qu’on modifie après le cookie de session pour remettre l’identifiant copié, on se retrouve connecté.

Potentiellement si un utilisateur distant récupère cet id de session il peut usurper une identité.

Pour éviter ça on ajoute au “salt” de devise un token unique, qu’on invalide à la déconnexion du user.

De plus on ajoute une déconnexion automatique de la session en cas d’inactivité prolongée.

Cf méthode 2 ici

Paramétrer les cookies par défaut pour renforcer la sécurité

Il faut mettre une durée par défaut aux cookies, sinon ils sont définis en “jusqu’à la fin de la session”, et avec les navigateurs qui maintiennent ouvertes les sessions ils n’expirent plus jamais.

Il faut aussi les paramétrer pour qu’ils soient “secure”, çàd forcer leur utilisation en https.

Attention en développement (http) on ne peut pas utiliser de cookies secure.

Dans /config/initalizers/session_store.rb :

if Rails.env == 'development'
  Rails.application.config.session_store :cookie_store, key: "_{PROJECT}_session", expire_after: 1.days
else
  Rails.application.config.session_store :cookie_store, key: "_{PROJECT}_session", expire_after: 1.days, secure: true
end

Paramétrer correctement Devise

On doit activer au minimum les modules :timeoutable (pour gérer la déconnexion automatique en cas d’inaction), :lockable (pour gérer les X tentatives infructueuses). Evidemment il faut faire le setup qui va avec dans l’initializer de Devise.

Il est possible d’activer le mode “paranoid” de Devise pour neutraliser les messages (par exemple on n’aura plus de différence sur le Recover password entre un mail qui n’existe pas en DB et un mail qui existe). Ca augmente la sécurité mais c’est casse couilles pour l’utilisateur final (par exemple l’utilisateur ne sait plus que son compte est locked, il a tout le temps un message “email ou mdp invalide”).