Protéger une application CodeIgniter contre CSRF

Dans le didacticiel d’aujourd’hui, nous allons apprendre à protéger votre application CodeIgniter (antérieure à 2.0) sans douleur et de manière efficace contre les attaques par falsification de requêtes entre sites. La bibliothèque que nous allons créer aujourd'hui automatisera tous les mécanismes de protection, ce qui renforcera la sécurité de votre site..


Étape 1 - Comprendre le vecteur d'attaque

Les attaques de contrefaçon de requêtes entre sites sont basées sur des formulaires non protégés sur vos sites..

Un attaquant pourrait créer un faux formulaire sur son site, par exemple un formulaire de recherche. Ce formulaire peut avoir des entrées cachées contenant des données malveillantes. À présent, le formulaire n'est pas envoyé au site de l'attaquant pour effectuer la recherche. en réalité, la forme pointe vers votre site! Puisque votre site Web aura confiance en l'authenticité du formulaire, il exécute et exécute les actions demandées (et peut-être même malveillantes)..

Imaginez qu'un utilisateur soit connecté à votre site et soit redirigé vers le site de l'attaquant pour une raison quelconque (phishing, XSS, vous le nommez). Le formulaire de l'attaquant pourrait pointer sur le formulaire de suppression de votre compte sur votre site. Si l'utilisateur effectue une "recherche" sur le site de l'attaquant, son compte sera alors supprimé à son insu.!

Il existe de nombreux moyens de prévenir ces attaques..

  • Vérifiez l'en-tête HTTP Referer et voyez s'il appartient à votre site. Le problème avec cette méthode est que tous les navigateurs ne soumettent pas cet en-tête (j'ai personnellement eu ce problème une fois avec IE7); il pourrait être forgé de toute façon.
  • Une autre méthode (celle que nous allons utiliser) consiste à inclure une chaîne aléatoire (un "jeton") sur chaque formulaire et à stocker ce jeton dans la session de l'utilisateur. Sur chaque POSTER request, comparez le jeton soumis à celui du magasin et, s’ils diffèrent, refusez la demande. Votre site doit toujours être protégé contre XSS, car sinon, cette méthode devient inutile..

Étape 2 - Planification

Nous devrons faire trois choses pour chaque demande:

  • Si la demande est un POSTER demander, valider que le jeton soumis.
  • Générer un jeton s'il n'y en a pas.
  • Injectez le jeton dans toutes les formes. Cela rend la méthode transparente et sans douleur, puisqu'aucune modification n'est nécessaire sur vos vues..

Pour faire cela automatiquement, nous utiliserons les crochets CodeIgniter. Les crochets nous permettent d'exécuter toutes les actions sur différentes parties de la demande. Nous aurons besoin de trois:

  • post_controller_constructor - Pour vérifier le jeton soumis, nous aurons besoin d'un post_controller_constructor crochet. Accrocher cette action avant que cet événement ne nous donne pas accès à l'instance de CodeIgniter correctement.
  • Générer le jeton - Pour générer le jeton, nous utiliserons le même point d'ancrage que précédemment. Cela nous permet d’y avoir accès au cas où nous aurions besoin de l’imprimer manuellement selon nos vues..
  • display_override - Pour injecter automatiquement le jeton dans notre vue, nous devrons utiliser le display_override crochet. C'est un crochet délicat, car, comme son nom l'indique, il remplace l'affichage des vues. Nous devons générer le contenu nous-mêmes si nous utilisons ce hook (consultez la documentation de CodeIgniter pour plus d'informations).

Étape 3 - Génération de jetons

Commençons. Nous allons procéder étape par étape afin de tout expliquer le plus précisément possible. Nous allons créer la méthode qui génère le jeton en premier afin de pouvoir tout tester correctement par la suite. Créez un fichier dans votre système / application / crochets dossier appelé "csrf.php"et collez le code suivant:

CI = & get_instance (); 

Espérons que ce que nous avons ajouté ci-dessus devrait vous paraître plutôt fondamental. Nous créons une classe, appelée CSRF_Protection, une variable d'instance pour contenir l'occurrence CodeIgniter, deux variables statiques pour contenir le nom du paramètre qui stockera le jeton et une pour stocker le jeton lui-même afin de faciliter l'accès à l'ensemble de la classe. Dans le constructeur de classe (__construction), nous récupérons simplement l'instance CodeIgniter et la stockons dans notre variable d'instance correspondante.

Remarque: Le "nom du paramètre" est le nom du champ qui contient le jeton. Donc sur les formulaires, c'est le nom de l'entrée cachée.

Après le constructeur de la classe, collez le code suivant:

/ ** * Génère un jeton CSRF et le stocke en session. Un seul jeton par session est généré. * Cela doit être lié à un hook post-contrôleur, et avant le hook * qui appelle la méthode inject_tokens (). * * @return void * @author Ian Murray * / fonction publique generate_token () // Charge la bibliothèque de session si elle n'est pas chargée $ this-> CI-> load-> bibliothèque ('session'); if ($ this-> CI-> session-> userdata (self :: $ nom_poste) === FAUX) // Génère un jeton et le stocke en session, car l'ancien semble avoir expiré. self :: $ token = md5 (uniqid (). microtime (). rand ()); $ this-> CI-> session-> set_userdata (self :: $ token_name, self :: $ token);  else // Définissez la variable locale pour un accès facile self :: $ token = $ this-> CI-> session-> userdata (self :: $ token_name); 

Pas à pas:

  • Nous chargeons la bibliothèque de session au cas où elle ne serait pas chargée automatiquement. Nous en avons besoin pour stocker le jeton.
  • Nous déterminons si le jeton a déjà été généré. Si la données d'utilisateur retours de méthode FAUX, alors le jeton n'est pas encore présent.
  • Nous générons un jeton et le stockons dans la variable de classe pour un accès facile. Le jeton pourrait être presque n'importe quoi vraiment. J'ai utilisé ces trois fonctions pour s'assurer que c'est très au hasard.
  • Nous le stockons ensuite dans la variable de session avec le nom configuré précédemment..
  • Si le jeton était déjà présent, nous ne pas générer et stocker à la place dans la variable de classe pour un accès facile.

Étape 4 - Validation du jeton

Nous devons nous assurer que le jeton a été soumis et qu'il est valide si la demande est une requête. POSTER demande. Allez-y et collez le code suivant dans votre csrf.php fichier:

/ ** * Valide un jeton soumis lorsque la demande POST est faite. * * @return void * @author Ian Murray * / fonction publique validate_tokens () // S'agit-il d'une demande de publication? if ($ _SERVER ['REQUEST_METHOD'] == 'POST') // Le champ de jeton est-il défini et valide? $ posted_token = $ this-> CI-> input-> post (self :: $ token_name); if ($ posted_token === FALSE || $ posted_token! = $ this-> CI-> session-> userdata (self :: $ token_name)) // Requête non valide, erreur d’envoi 400. show_error ('Requête non valide. Les jetons ne correspondaient pas. ', 400); 
  • Ci-dessus, nous validons la demande, mais seulement si c'est un POSTER demande, ce qui signifie qu’un formulaire a effectivement été soumis. Nous vérifions cela en jetant un coup d’œil au REQUEST_METHOD dans le $ _SERVER super global.
  • Vérifiez si le jeton a bien été posté. Si la sortie de $ this-> CI-> input-> post (self :: $ token_name) est FAUX, alors le jeton n'a jamais été posté.
  • Si le jeton n'a pas été posté ou s'il ne correspond pas à celui que nous avons généré, refusez la demande avec l'erreur "Requête incorrecte"..

Étape 5 - Injecter des jetons dans les vues

C'est la partie amusante! Nous devons injecter les jetons sous toutes leurs formes. Pour nous faciliter la vie, nous allons placer deux balises méta dans notre (Rails-like). De cette façon, nous pouvons également inclure le jeton dans les requêtes AJAX..

Ajoutez le code suivant à votre csrf.php fichier:

/ ** * Ceci injecte des balises masquées sur tous les formulaires POST avec le jeton csrf. * Injecte également des en-têtes méta dans  de sortie (le cas échéant) pour un accès facile * depuis les frameworks JS. * * @retour void * @author Ian Murray * / fonction publique inject_tokens () $ output = $ this-> CI-> output-> get_output (); // Injecte dans le formulaire $ output = preg_replace ('/ (<(form|FORM)[^>] * (method | METHOD) = "(post | POST)" [^>] *>) / ',' $ 0', $ output); // Inject dans  $ output = preg_replace ('/ (<\/head>) / ',''. "\ n". ''. "\ n". '$ 0', $ sortie); $ this-> CI-> output -> _ display ($ output); 
  • Puisque c'est un display_override hook, nous devons récupérer la sortie générée à partir de CodeIgniter. Nous faisons cela en utilisant le $ this-> CI-> output-> get_output () méthode.
  • Pour injecter les balises dans nos formulaires, nous allons utiliser des expressions régulières. L'expression garantit que nous injectons une balise d'entrée masquée (qui contient notre jeton généré) uniquement dans les formulaires avec une méthode de type. POSTER.
  • Nous devons également injecter nos balises méta dans le entête (si présent). C’est simple, puisque la balise de tête de fermeture ne doit être présente qu’une fois par fichier..
  • Enfin, puisque nous utilisons le display_override hook, la méthode par défaut pour afficher votre vue ne sera pas appelée. Cette méthode inclut toutes sortes de choses, ce que nous ne devrions pas, uniquement pour injecter du code. L'appeler nous-mêmes résout ce problème.

Étape 6 - Crochets

Dernier point, mais non le moindre, nous devons créer les hooks eux-mêmes - ainsi nos méthodes sont appelées. Collez le code suivant dans votre système / application / config / hooks.php fichier:

// // Crochets de protection CSRF, ne les touchez pas sauf si vous savez ce que vous // faites. // // L'ORDRE DE CES CROCHETS EST EXTREMEMENT IMPORTANT! // // CECI DOIT ÊTRE PREMIÈREMENT DANS LA LISTE DE CROCHETS post_controller_constructor. $ hook ['post_controller_constructor'] [] = array (// Attention au "[]", ce n'est pas le seul hook post_controller_constructor 'class' => 'CSRF_Protection', 'function' => 'validate_tokens', 'nomfichier' = > 'csrf.php', 'filepath' => 'crochets'); // Génère le jeton (DOIT ARRIVER APRÈS QUE LA VALIDATION A ÉTÉ EFFECTUÉE, MAIS AVANT LE CONTRÔLEUR // EST EXÉCUTÉ, L'UTILISATEUR N'A AUCUN ACCÈS À UN JETON VALIDE POUR DES FORMULES PERSONNALISÉES). $ hook ['post_controller_constructor'] [] = array (// Attention au "[]", ce n'est pas le seul hook post_controller_constructor 'class' => 'CSRF_Protection', 'fonction' => 'generate_token', 'nomfichier' = > 'csrf.php', 'filepath' => 'crochets'); // Ceci injecte des jetons sur toutes les formes $ hook ['display_override'] = array ('class' => 'CSRF_Protection', 'function' => 'inject_tokens', 'filename' => 'csrf.php', 'filepath' => 'crochets');
  • Le premier crochet appelle le validate_tokens méthode. Ce n'est pas le seul post_controller_constructor crochet, nous devons donc ajouter ces crochets ("[]"). Reportez-vous à la documentation sur les crochets CodeIgniter pour plus d'informations..
  • Le deuxième crochet, qui est aussi un post_controller_constructor, génère le jeton s'il n'a pas encore été généré.
  • Le troisième est la substitution d'affichage. Ce crochet injectera nos jetons dans le forme et le entête.

Emballer

Avec un minimum d'effort, nous avons construit une assez belle bibliothèque pour nous-mêmes.

Vous pouvez utiliser cette bibliothèque dans n’importe quel projet, ce qui protégera automatiquement votre site contre CSRF..

Si vous souhaitez contribuer à ce petit projet, merci de laisser un commentaire ci-dessous ou de préciser le projet sur GitHub. Alternativement, à partir de CodeIgniter v2.0, la protection contre les attaques CSRF est désormais intégrée au framework.!