Je crois que je comprends les bases de l'injection SQL. Je sais également que l'utilisation d'instructions préparées avec des fichiers PHP est le meilleur moyen d'empêcher l'injection SQL. On m'a toujours dit que l'injection SQL se produit le plus souvent lorsqu'un attaquant entre des commandes SQL valides dans des champs de données de formulaire ou un fichier champs de saisie sur un site public.
Cependant, si j'ai sur mon site des fichiers PHP accessibles uniquement par un utilisateur authentifié, est-il toujours nécessaire à 100% d'utiliser des instructions préparées?
Aussi, qu'en est-il des requêtes SQL qui ne nécessitent aucune donnée utilisateur externe pour s'exécuter?
Quelque chose comme:
SELECT * FROM tableName
Si je ne passe aucune variable à une requête, est-elle toujours vulnérable à l'injection SQL?
Faites-vous entièrement confiance à tous vos utilisateurs authentifiés? Y compris qu'ils ne verront pas leurs comptes compromis par des attaquants? C'est mauvais si un attaquant a accès à un compte, mais bien pire s'il peut ensuite en tirer parti pour voler le reste de votre base de données.
Pour votre deuxième point, l'exemple que vous avez utilisé ne serait pas vulnérable. Cependant, faites attention à plus de cas limites tels que
$query = "SELECT * FROM tableName WHERE secret='".dbquery("SELECT secret FROM otherTable WHERE id=3")."'";
S'il était possible d'insérer une charge utile dans otherTable
dans la colonne secret
pour un enregistrement avec id
3, cela serait vulnérable à Second Order SQLi , même si la routine d'insertion utilise des instructions préparées.
Selon les commentaires:
SELECT * FROM tableName WHERE secret=(SELECT secret FROM otherTable WHERE id=3)
est exécuté dans SQL, donc pas de problème. Le problème se produit lorsque les données extraites de la base de données sont considérées comme approuvées au niveau de la couche application et utilisées dans des requêtes ultérieures.
Vous dites "je crois que je comprends les bases de l'injection SQL". mais le reste de la question implique que peut-être vous pouvez encore avoir une certaine confusion. Quoi qu'il en soit, c'est un sujet important et beaucoup de gens continuent d'être confus au sujet du problème et de la solution.
Je sais également que l'utilisation d'instructions préparées avec des fichiers PHP est le meilleur moyen d'empêcher l'injection SQL.
L'utilisation d'instructions préparées est une condition nécessaire mais non suffisante pour se protéger de l'injection SQL (SQLi). Pour comprendre pourquoi, il est important de comprendre la cause des vulnérabilités d'injection SQL. Voici les causes:
C'est ça. Si vous prenez l'entrée de l'utilisateur et la placez dans une instruction SQL que vous compilez ensuite sur la base de données, vous pouvez être (et êtes probablement) sujet à des vulnérabilités SQLi. Si vous faites cela et utilisez une instruction préparée, vous n'avez rien résolu. Vous êtes toujours vulnérable.
Alors, pourquoi l'utilisation de déclarations préparées est-elle toujours donnée comme réponse? Si nous ne pouvons pas concaténer le SQL de l'entrée, cela signifie que notre SQL doit être plus ou moins codé en dur. Mais que faire si vous souhaitez retirer les informations utilisateur de celui qui vient de se connecter? Nous ne pouvons pas écrire une nouvelle instruction SQL chaque fois que nous avons un nouvel utilisateur.
Ce qui rend les instructions préparées utiles pour éliminer ce problème, c'est qu'elles permettent des paramètres. Cela signifie qu'au lieu de faire quelque chose comme ça:
// Don't do this! Subject to injection
"SELECT credit_card_number FROM user_data where userid = '" + userinput + "'"
Nous pouvons le faire:
"SELECT credit_card_number FROM user_data where userid = ?"
Et compilez cette déclaration sur la base de données. Lorsque nous allons l'exécuter, nous devons transmettre une valeur de paramètre ou la requête échouera. Pourquoi cela résout-il le problème? Supposons qu'un attaquant trouve comment obtenir la chaîne: foo' OR 1 = 1 OR 'a' = 'a
dans la valeur d'entrée utilisateur. Dans le premier cas, le SQL suivant s'exécutera:
SELECT credit_card_number FROM user_data where userid = 'foo' OR 1 = 1 OR 'a' = 'a';
Renvoyer chaque ligne du tableau. Dans le second cas, ce SQL s'exécutera:
SELECT credit_card_number FROM user_data where userid = ?
avec le paramètre foo' OR 1 = 1 OR 'a' = 'a
. Sauf si vous avez un utilisateur avec l'ID improbable foo' OR 1 = 1 OR 'a' = 'a
il n'y aura aucun résultat. Mais soyons clairs, vous pouvez utiliser la première (mauvaise) approche avec des instructions préparées et vous serez toujours vulnérable. Il n'y a rien de magique dans la déclaration préparée qui nettoiera l'entrée. C'est que vous ne pouvez pas utiliser la deuxième approche (correcte) avec une instruction régulière qui ne prend pas en charge les paramètres.
Je pense que souvent les gens qui comprennent cela utiliseront le raccourci "utiliser des instructions préparées" parce que la principale raison de les utiliser est parce qu'ils prennent en charge les paramètres. Mais ce sont vraiment les paramètres qui sont la solution à ce problème.
En plus de l'excellente réponse de Matthew, même si vous faites confiance à vos utilisateurs, vous pouvez toujours être vulnérable à CSRF , ce qui pourrait permettre à un attaquant d'effectuer une injection SQL sans avoir un compte.
Un attaquant peut être en mesure de créer un lien ou un formulaire, qu'un utilisateur authentifié peut cliquer ou envoyer par inadvertance, ce qui provoque une injection SQL. Pour cette raison, j'affirme que vous ne devez jamais faire confiance aux entrées utilisateur et que vous devez nettoyer et utiliser les requêtes paramétrées pour toutes les entrées utilisateur.
C'est une question très populaire sur Stack Overflow, et cela m'a fait développer une sorte de "réponse standard".
Tout d'abord, la question sonne comme si vous essayez de vous négocier une "remise" dans le développement. Mais pourquoi essayez-vous ainsi? Pourquoi une telle question se pose-t-elle jamais? Les instructions préparées sont-elles trop difficiles à saisir/à mettre en œuvre pour vous? Ensuite, vous devriez poser une question différente, "Comment puis-je rendre les déclarations préparées moins fastidieuses?" En fait, une instruction préparée est plus facile que toute autre méthode d'interaction avec la base de données.
Donc, en premier lieu, il n'y a aucune raison de poser une telle question.
Vous devez formater vos requêtes indépendamment des menaces possibles. Vous le faites, pas pour les célèbres Bobby Tables , mais pour une humble fille Sarah O'Hara en premier lieu (ou tout autre humain ou entité qui porte un nom qui pourrait faire une base de données étranglement, s’il n’est pas correctement formaté).
Donc, ce n'est pas une injection SQL pour laquelle vous devez utiliser des instructions préparées, mais simplement pour garantir que votre requête sera toujours syntaxiquement correcte.
L'utilisation d'aucune instruction préparée signifie que vous êtes résigné à formater vos données manuellement, et il y a beaucoup d'embûches sur cette route. J'ai essayé de les résumer dans mon n article sur l'injection SQL . En voici un extrait:
Le formatage manuel peut être incomplet. Prenons le cas des tables Bobby. C'est un parfait exemple de mise en forme incomplète: une chaîne que nous avons ajoutée à la requête a seulement été citée, mais pas échappée! Alors que, comme nous venons de l'apprendre de ce qui précède, les guillemets et les échappements doivent toujours aller de pair (avec la définition du codage approprié pour la fonction d'échappement). Mais dans une application habituelle PHP qui effectue le formatage des chaînes SQL séparément (en partie dans la requête et en partie ailleurs), il est très probable qu'une partie de la mise en forme soit simplement ignorée.
Le formatage manuel peut être appliqué au mauvais littéral . Ce n'est pas grave tant que nous utilisons formatage complet (car cela entraînera une erreur immédiate qui peut être corrigée dans la phase de développement), mais lorsqu'il est combiné avec un formatage incomplet, c'est un vrai désastre. Il y a des centaines de réponses sur le grand site de Stack Overflow, suggérant d'échapper aux identifiants de la même manière que les chaînes. Ce qui serait complètement inutile et provoquerait une injection SQL.
Le formatage manuel est essentiellement une mesure non obligatoire . Tout d'abord, il y a un cas évident de manque d'attention, où la mise en forme appropriée peut être simplement oubliée. Mais il y a un cas vraiment bizarre - de nombreux PHP utilisateurs souvent intentionnellement refusent d'appliquer toute mise en forme, car jusqu'à ce jour, ils séparent toujours les données pour "nettoyer" "et" impur "," entrée utilisateur "et" entrée non utilisateur ", etc. Penser qu'une donnée" sûre "n'a besoin d'aucune mise en forme. Ce qui est un non-sens - souvenez-vous de Sarah O'Hara. Du point de vue du formatage, c'est la destination qui compte. Un développeur doit faire attention au type de littéral SQL, pas à la source de données. Est-ce une chaîne qui va à la requête? Il doit alors être formaté en chaîne. Peu importe que ce soit à partir de l'entrée de l'utilisateur ou juste apparu mystérieusement de nulle part au milieu de l'exécution du code.
Le formatage manuel peut être séparé de l'exécution réelle de la requête par une distance considérable.
La question la plus sous-estimée et négligée. Pourtant, le plus essentiel de tous, car il peut gâcher toutes les autres règles seul, s'il n'est pas suivi.
Presque tous les utilisateurs de PHP sont tentés de faire toute la "désinfection" en un seul endroit, loin de l'exécution réelle de la requête, et une telle approche erronée est à elle seule une source d'innombrables défauts:
un formatage prématuré gâtera la variable source, la rendant inutilisable pour autre chose.
Enfin et surtout. Nous devons tous nous rappeler que les littéraux de données ne sont pas les seules parties variables de la requête. Parfois, un identifiant ou un mot-clé doit également être ajouté. Les instructions préparées ne sont d'aucune utilité dans ce cas, mais ces parties de requête ne devraient pas avoir moins de protection. Heureusement, pour les identifiants et les mots clés, la liste des variantes possibles est toujours limitée, nous pouvons donc utiliser l'approche whitelisting que j'ai décrite dans ma réponse à la question de référence sur Stack Overflow , donc je ne me répéterai pas.
Pour répondre à votre dernière question - non, une requête sans parties variables n'est pas vulnérable, car il n'y a aucun point à injecter.
Un utilisateur vraiment digne de confiance n'existe pas. Même si vous ignorez les menaces internes (ce qui est une omission flagrante de nos jours), il existe d'autres risques. L'utilisateur peut avoir écrit son mot de passe sur un post-it, ou son appareil peut être compromis par des logiciels malveillants qui pourraient exploiter vos vulnérabilités à l'insu de l'utilisateur.
En laissant un trou d'injection SQL exposé à tout utilisateur, vous leur accordez effectivement un accès illimité et non contrôlé à votre base de données. Selon la façon dont vous avez bien configuré votre modèle de sécurité, ils sont également susceptibles de tout supprimer.
Fondamentalement, vous devez décider contre qui vous vous protégez et baser votre niveau d'effort sur cela, mais laisser un trou d'injection SQL ouvert est simplement bâclé et vous expose à des risques pour aucun avantage tangible. Si vous implémentez correctement des requêtes paramétrées, il ne devrait pas y avoir de différence d'effort pour le faire correctement.
Une dernière considération ... La sécurité n'est pas une chose binaire. Vous devriez avoir plusieurs couches, chacune protégeant contre différents vecteurs d'attaque. Si une couche est compromise, les couches restantes protégeront vos données (ou atténueront la perte). Dans ce scénario, une attaque de contrefaçon de demande intersite permettrait aux attaquants de posséder votre base de données. Si vous avez corrigé l'injection SQL, le pire qu'ils pourraient faire est ce que votre application permet à un utilisateur de faire dans des conditions normales ¹.
¹ (Et pour atténuer cela, vous pouvez mettre en place une limitation de débit ou un système de surveillance/alerte. Vous pouvez continuer indéfiniment).