Pouvez-vous pirater votre propre site? Quelques considérations essentielles sur la sécurité

Deux fois par mois, nous revoyons certains des articles préférés de nos lecteurs au cours de l'histoire de Nettuts +. Ce tutoriel a été publié pour la première fois en juillet 2008..

La première version devient dorée! Les visiteurs débarquent de tous les coins du globe. Vous savez qu'il y a probablement quelques problèmes de démarrage. Je veux dire, c'est 1.0.0.0? tous ces zéros sont destinés à nous permettre un peu de grâce, à droite?

Peut-être que cette feuille de style ignoble ne sera tout simplement pas mise en cascade avec élégance sur le navigateur X. Un commentaire incomplet laisse entrevoir une marge brisée. Peut-être devriez-vous avoir persisté ces connexions à la base de données après tout. Hé, nous négligeons tous l'idée de commencer à utiliser notre première version, mais combien de ces oublis pouvons-nous nous contenter d'estomac et combien pourraient-ils laisser un goût amer dans le nôtre, et plus douloureusement encore la bouche de nos clients?

Cet article décrit l'étape de la planification en vue de la création d'une application Web hypothétique centrée sur l'utilisateur..

Bien que vous ne disposiez pas d'un projet complet - ni d'un cadre prêt pour le marché, j'espère que chacun d'entre vous, face à la charge de travail à venir, pourra réfléchir aux meilleures pratiques décrites. Alors, sans plus tarder? Êtes-vous assis confortablement?


L'exemple

Notre client nous a demandé d’intégrer dans un site existant un système de relecture de livres. Le site a déjà des comptes d'utilisateurs et permet les commentaires anonymes.

Après une discussion rapide avec le client, nous avons la spécification suivante à mettre en œuvre et seulement vingt-quatre heures pour le faire:

Remarque: le serveur du client exécute PHP5 et MySQL - mais ces détails ne sont pas essentiels pour comprendre le bugbears décrit dans cet article..


Les processus:

Notre client nous a donné un PHP inclus pour accéder à la base de données:

Nous n'avons pas réellement besoin de la source de ce fichier pour l'utiliser. En fait, si le client nous avait simplement indiqué où il habitait, nous aurions pu l’utiliser avec une déclaration include et $ db variable.

Sur autorisation? dans le schéma datatable, nous nous intéressons aux noms de colonne suivants:

  • Nom d'utilisateur, varchar (128) - stocké sous forme de texte brut.
  • mot de passe, varchar (128) - stocké sous forme de texte brut.

Étant donné que nous travaillons contre la montre? écrivons le plus rapidement possible une fonction PHP que nous pourrons réutiliser pour authentifier nos utilisateurs:


Variables $ _REQUEST

Dans le code ci-dessus, vous remarquerez que j’ai mis en évidence une zone orange et une zone rouge..

Pourquoi ai-je souligné le pas si dangereux $ _REQUEST les variables?

Bien que cela n’expose aucun danger réel, il permet une approche laxiste en matière de code côté client. PHP comporte trois tableaux que la plupart d’entre nous utilisons pour obtenir nos données postées d’utilisateurs. Le plus souvent, nous pourrions être tentés d’utiliser ces informations. $ _REQUEST. Ce tableau donne à notre PHP un accès pratique aux variables POST et GET, mais c’est là un blocage potentiel.?

Considérez le scénario suivant. Vous écrivez votre côté client code pour utiliser des requêtes POST, mais vous transférez le projet pendant que vous prenez une pause - et à votre retour, votre acolyte a écrit quelques requêtes GET dans le projet. Tout va bien - mais ça ne devrait pas.

Quelques instants plus tard, un utilisateur non averti tape un lien externe dans une zone de commentaire et, avant même que vous le sachiez, ce site externe contient une douzaine de combinaisons de nom d'utilisateur / mot de passe dans son journal de référent..

En référençant le $ _POST variables au lieu de $ _REQUEST, nous éliminons accidentellement publier tout code de travail susceptible de révéler une requête GET risquée.

Le même principe s'applique aux identificateurs de session. Si vous constatez que vous écrivez des variables de session dans des URL, vous faites une erreur ou vous avez un problème. très bon raison de le faire.


Injection SQL

En se référant à nouveau au code PHP, la ligne surlignée en rouge aurait peut-être sauté à certains d'entre vous? Pour ceux qui n'ont pas repéré le problème, je vais vous donner un exemple et à partir de là, voir si quelque chose vous semble risqué.

La protection la plus rapide est de dépouiller la enceinte personnages ou leur échapper.

Cette image montre clairement l’inconvénient d’incorporer des variables directement dans des instructions SQL. Bien qu'on ne puisse pas dire exactement quoi contrôler un utilisateur malveillant pourrait avoir - il est garanti, si vous utilisez cette méthode pour chaîner une instruction SQL, que votre serveur est à peine protégé. L'exemple ci-dessus est assez dangereux sur un compte en lecture seule; les pouvoirs d'une connexion en lecture / écriture ne sont limités que par votre imagination.

Se protéger contre l'injection SQL est en fait assez facile. Voyons tout d'abord le cas des variables de chaîne incluses avec guillemets:

La solution la plus rapide est de dépouiller le enceinte personnages ou leur échapper. Depuis PHP 4.3.0, la fonction mysql_real_escape_string a été disponible pour nettoyer les chaînes entrantes. La fonction prend la chaîne brute en tant que paramètre unique et renvoie la chaîne avec les caractères volatiles échappés.. toutefois mysql_real_escape_string ne pas échapper à tous les caractères qui sont des caractères de contrôle valables dans SQL? les éléments surlignés dans l'image ci-dessous montrent les techniques que j'utilise pour assainir Chaîne, nombre et Booléen valeurs.

Le premier point fort, la ligne qui définit $ string_b utilise une fonction PHP appelée addcslashes. Cette fonction fait partie de PHP depuis la version 4 et, comme cela est écrit dans l'exemple ci-dessus, c'est ma méthode préférée pour la santé et la sécurité des chaînes SQL..

Une mine d'informations est disponible dans la documentation PHP, mais je vais expliquer brièvement ce que addcslashes fait et comment il diffère de mysql_real_escape_string.

Dans le diagramme ci-dessus, vous pouvez voir que mysql_real_escape_string n'ajoute pas de barres obliques au (%) caractère.

le % est utilisé en SQL COMME clauses, ainsi que quelques autres. Il se comporte comme un joker et ne pas un personnage littéral. Donc, il devrait être échappé par un caractère de barre oblique inverse précédent dans tous les cas où les littéraux de chaîne constituent une instruction SQL.

Le deuxième paramètre, je passe à addcslashes, qui dans l'image est audacieux; est le groupe de caractères pour lequel PHP va ajouter des barres obliques. Dans la plupart des cas, il faudra Divisé la chaîne que vous fournissez personnages, et ensuite opérer sur chacun. Il est à noter que ce groupe de caractères peut également recevoir une plage de caractères, bien que cela dépasse le cadre de cet article. Dans les scénarios dont nous discutons, nous pouvons utiliser des caractères alphanumériques littéralement, par exemple. ? abcd1234? et tous les autres caractères en tant que leur littéral de style C? \ r \ n \ t ?, ou leur index ASCII? \ x0A \ x0D \ x09?.

La mise en évidence suivante sécurise nos valeurs numériques pour les instructions SQL.

Cette fois, nous ne voulons rien nous échapper, nous voulons simplement n’avoir qu’une valeur numérique valide, qu’il s’agisse d’un nombre entier ou d’une virgule flottante..

Vous avez peut-être remarqué ligne 10, et peut-être demandé quel était son but. Il y a quelques années, j'ai travaillé sur un système de journalisation de centre d'appels qui utilisait variable + = 0; pour assurer des valeurs numériques. Pourquoi cela a été fait, je ne peux pas dire honnêtement? sauf avant PHP 4, c'est comme ça que nous l'avons fait?! Peut-être que quelqu'un qui lit peut faire la lumière sur le sujet. Autre que cela, si vous, comme je l'ai fait, rencontrez une ligne comme ça dans la nature, vous saurez ce qu'il essaie de faire.

Avancer ensuite; lignes 11 et 12 sommes tout ce dont nous avons besoin pour préparer nos valeurs d’entrée numériques pour SQL. Je devrais dire, avait la chaîne d'entrée $ number_i contenait des caractères non numériques devant ou à gauche des numériques? nos valeurs $ number_a, $ number_b et $ number_c serait tous est égal à 0.

Nous allons utiliser floatval nettoyer nos nombres d'entrée; PHP n'imprime que les décimales lorsqu'elles existent dans la valeur en entrée. Par conséquent, les imprimer dans une instruction SQL ne provoquera pas d'erreur si aucune décimale ne figurait dans l'entrée. Tant que le code de notre serveur est sûr, nous pouvons laisser les plus précis en train de valider à notre code côté client.

Avant de passer à la liste finale de notre PHP, nous allons jeter un coup d’œil à la dernière code mettre en évidence, la boxe booléenne.

Comme l’équivalent C ++, un booléen en PHP est vraiment un entier. Comme dans, True + True = Deux. Il existe d'innombrables façons de traduire une chaîne d'entrée en un type booléen, mon préféré étant: la chaîne en minuscule contient-elle le mot true?

Vous pouvez tous avoir vos propres méthodes préférées; la chaîne d'entrée est-elle explicitement égale? ou est la chaîne d'entrée? 1? etc? ce qui est important, c'est que la valeur entrant, quelle qu'elle soit, est représentée par un booléen (ou un entier) avant d'être utilisée..

Ma philosophie personnelle est simplement: si X est vrai ou faux, puis X est un booléen. Je vais écrire avec bonheur tout le code que je pourrais avoir besoin de revoir plus tard avec Booleans et ne pas short, int, tinyint ou tout ce qui n'est pas booléen. Ce qui se passe sur le métal ne me concerne pas, alors à quoi il ressemble à un humain est beaucoup plus important.

Ainsi, comme pour les nombres et les chaînes, nos Booléens sont garantis en toute sécurité dès le moment où nous les insérons dans notre script. De plus, notre code d'hygiène n'a pas besoin de lignes supplémentaires.


Traitement HTML

Maintenant que nous avons protégé notre code SQL contre les injections et que nous sommes certains que seule une connexion POST peut fonctionner avec notre script, nous sommes prêts à implémenter notre fonctionnalité de soumission de commentaires..

Notre client souhaite permettre aux utilisateurs ayant activé la révision de formater leurs contributions au format HTML standard. Cela semblerait assez simple, mais nous savons également que les adresses e-mail coûtent dix dollars, et que les comptes de librairie sont créés par programme - donc, dans l'intérêt de tous, nous nous assurerons que seules les balises que nous disons passer.

Décider de la manière dont nous vérifions la nouvelle évaluation peut sembler décourageant. La spécification HTML comporte un tableau plutôt sain de balises, dont nous sommes heureux de permettre la plupart..

Aussi long que la tâche puisse paraître, je conseille vivement à tous - choisir quoi autoriser, et jamais quoi refuser. Langages de balisage du navigateur et du serveur tout adhérer à XML comme une structuration, afin que nous puissions baser notre code sur le fait fondamental que le code exécutable doit être entouré par, ou faire partie de, des balises entre crochets.

Certes, il y a plusieurs façons d'obtenir le même résultat. Pour cet article, je vais décrire un pipeline d’expressions régulières possible:

Ces expressions régulières ne produiront pas une sortie parfaite, mais dans la majorité des cas, elles devraient faire un travail presque élégant.

Jetons un coup d'œil à l'expression régulière que nous allons utiliser dans notre PHP. Vous remarquerez que deux tableaux ont été déclarés. $ safelist_review et $ safelist_comment - afin que nous puissions utiliser les mêmes fonctions pour valider les avis et, plus tard, les commentaires:

? et voici la fonction principale que nous appellerons pour assainir les données de révision et de commentaire:

Les paramètres d'entrée, j'ai mis en évidence le rouge et le bleu. $ input est les données brutes telles que soumises par l'utilisateur et $ liste est une référence au tableau d'expressions; $ safelist_review ou $ safelist_comment en fonction bien sûr du type de soumission que nous souhaitons valider.

La fonction renvoie la version reformatée des données soumises - toutes les balises qui ne pas passer toutes les expressions régulières dans notre liste choisie sont converties en équivalents codés HTML. Ce qui dans les termes les plus simples fait < et > dans < et > les autres caractères sont également modifiés, mais aucun d’entre eux ne constitue une menace pour la sécurité de notre client ou des utilisateurs..

Note: Les fonctions: cleanWhitespace et getTags sont inclus dans les fichiers source de l'article.

Vous auriez raison de supposer que tout ce que nous avons vraiment fait est de survivre à l'esthétique des pages de notre site et de ne pas avoir tout fait pour protéger la sécurité de l'utilisateur. Il reste cependant un trou de sécurité assez énorme: injection de JavaScript.

Cette faille pourrait être corrigée par quelques expressions régulières supplémentaires et / ou par une modification de celles que nous utilisons déjà. Notre expression régulière d'ancrage ne permet que? /? ?,? h? ? et ?#? ? des valeurs comme href attribut - qui n’est en réalité qu’un exemple de solution. Les navigateurs, à travers le tableau, comprennent une grande variété de script visible attributs, tels que sur clic, en charge et ainsi de suite.

Nous avons essentiellement créé un problème épineux pour nous-mêmes. Nous voulions autoriser le langage HTML, mais nous avons maintenant une liste presque infinie de mots-clés à supprimer. Il existe bien sûr un moyen moins que parfait - mais assez rapide pour le faire:

Après réflexion, vous auriez tout à fait le droit de demander: «Pourquoi n’avons-nous pas simplement utilisé le code BBC, Textile ou? ??

Moi-même, si je m'occupais du traitement des balises, je pourrais même opter pour la marche XML. Après toutes les données entrantes devrait être valide XML.

Cependant, cet article n'a pas pour but de nous apprendre à utiliser les expressions rationnelles, PHP ou comment écrire quoi que ce soit dans une langue donnée. La raison derrière cela est simplement, ne laissez aucune porte entrouverte.

Alors finissons-en alors; avec un rapide aperçu de ce que nous avons examiné:

Certes, cet article ne vous a pas équipé de projet sur étagère. Mon objectif principal n'était pas de faire fuir les concepteurs qui codent, ou de chiffonner le travail des codeurs n'importe où, mais d'encourager tout le monde à créer du code robuste dès le départ. Cela dit, je prévois de revenir plus tard sur certains éléments de cet article..

Jusque-là, codage sûr!