Sont PDO déclarations préparées à l'avance suffisante pour empêcher l'injection SQL?
Disons que j'ai un code comme ceci:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
L'AOP documentation dit:
Les paramètres pour préparer les états n'ont pas besoin d'être cité; le pilote s'en occupe pour vous.
Qui est vraiment tout ce que je dois faire pour éviter les injections SQL? Est-ce vraiment si simple?
Vous pouvez supposer MySQL si cela fait une différence. Aussi, je suis seulement curieux au sujet de l'utilisation des requêtes préparées contre les injections SQL. Dans ce contexte, je ne se soucient pas de XSS ou d'autres vulnérabilités.
- une meilleure approche de la 7ème numéro de répondre à stackoverflow.com/questions/134099/...
Vous devez vous connecter pour publier un commentaire.
La réponse courte est PAS, PDO prépare ne sera pas vous protéger de toutes les attaques par Injection SQL. Pour certains obscur bord-cas.
Je suis l'adaptation cette réponse parler de PDO...
La longue réponse n'est pas si facile. Il est basé sur une attaque montré ici.
L'Attaque
Donc, nous allons commencer par montrer l'attaque...
Dans certaines circonstances, qui sera de retour de plus de 1 ligne. Nous allons disséquer ce qui se passe ici:
De la sélection d'un Jeu de Caractères
Pour cette attaque fonctionne, nous avons besoin de l'encodage que le serveur attend la connexion à la fois pour encoder
'
comme en ASCII, c'est à dire0x27
et avoir certains caractères dont le dernier octet est un ASCII\
c'est à dire0x5c
. Comme il s'avère, il y a 5 de ces codages pris en charge dans MySQL 5.6 par défaut:big5
,cp932
,gb2312
,gbk
etsjis
. Nous allons sélectionnergbk
ici.Maintenant, il est très important de noter l'utilisation de
SET NAMES
ici. Cela définit le jeu de caractères SUR LE SERVEUR. Il y a une autre manière de faire, mais nous allons y arriver assez vite.La Charge Utile
La charge utile que nous allons utiliser pour cette injection commence avec la séquence d'octets
0xbf27
. Dansgbk
, c'est une séquence de caractères; danslatin1
, c'est la chaîne¿'
. Notez que danslatin1
etgbk
,0x27
sur son propre est un littéral'
caractère.Nous avons choisi cette charge utile car, si nous avons appelé
addslashes()
sur elle, nous aimerions insérer un fichier ASCII\
c'est à dire0x5c
, avant la'
caractère. Donc nous avions le vent avec0xbf5c27
, qui engbk
est un deux séquence de caractères:0xbf5c
suivie par0x27
. Ou en d'autres termes, un valide caractère, suivi par un sans échappement'
. Mais nous ne sommes pas à l'aide deaddslashes()
. Donc à la prochaine étape...$stmt->execute()
La chose importante à réaliser est ici que PDO par défaut ne PAS faire vrai déclarations préparées à l'avance. Il émule (pour MySQL). Par conséquent, PDO en interne construit la chaîne de requête, l'appelant
mysql_real_escape_string()
(MySQL C la fonction de l'API) sur chaque liés chaîne de valeur.L'API C appel à
mysql_real_escape_string()
diffère deaddslashes()
en ce qu'il sait de la connexion jeu de caractères. De sorte qu'il peut effectuer échapper correctement pour le jeu de caractères que le serveur attend. Toutefois, à ce stade, le client pense que nous sommes toujours à l'aide delatin1
pour le lien, parce que nous n'avons jamais dit le contraire. Nous avons dit que la serveur nous utilisonsgbk
, mais le client pense toujours que c'estlatin1
.Par conséquent, l'appel à
mysql_real_escape_string()
insère la barre oblique inverse, et nous avons un libre pendaison'
caractère de notre "échappé" contenu! En fait, si nous étions à regarder$var
dans legbk
jeu de caractères, nous aimerions voir:Qui est exactement ce que l'attaque nécessite.
La Requête
Cette partie n'est qu'une formalité, mais voici le rendu de la requête:
Félicitations, vous venez de vous a attaqué avec succès un programme en utilisant PDO Préparées...
La Solution Simple
Maintenant, il est intéressant de noter que vous pouvez empêcher cela en désactivant émulé préparées:
Ce sera généralement entraîner une véritable déclaration préparée à l'avance (c'est à dire les données d'être envoyé dans un paquet distinct de la requête). Cependant, être conscient que PDO silencieusement de secours à l'émulation d'états que MySQL ne peut pas préparer de manière native: ceux qu'il peut sont répertorié dans le manuel, mais méfiez-vous de sélectionner la version du serveur).
Le Corriger Corriger
Le problème ici est que nous n'avons pas d'appel de l'API C de l'
mysql_set_charset()
au lieu deSET NAMES
. Si nous le faisions, nous serions bien à condition que nous soyons à l'aide d'une base MySQL depuis 2006.Si vous utilisez une version antérieure de MySQL version, puis une bug dans
mysql_real_escape_string()
signifie que la séquence de caractères tels que ceux de notre charge utile ont été traités comme de simples octets pour échapper à des fins même si le client avait été correctement informé de la connexion de l'encodage et donc, cette attaque serait encore réussir. Le bug a été corrigé dans MySQL 4.1.20, 5.0.22 et 5.1.11.Mais le pire, c'est que
PDO
ne pas exposer l'API C pourmysql_set_charset()
jusqu'à 5.3.6, de sorte que dans les versions antérieures, il ne peut pas empêcher cette attaque pour chaque commande!Il est maintenant exposé comme un Paramètre DSN, qui devrait être utilisé au lieu de
SET NAMES
...La Grâce Salvatrice
Comme nous l'avons dit au début, pour que cette attaque de travail de la connexion de base de données doivent être codées à l'aide d'un vulnérables jeu de caractères.
utf8mb4
est pas vulnérables et qui, pourtant, peuvent soutenir chaque caractère Unicode: alors vous pourriez choisir d'utiliser à la place—mais il a seulement été disponible depuis MySQL 5.5.3. Une alternative estutf8
, qui est aussi pas vulnérables et peut prendre en charge l'ensemble de l'Unicode Plan Multilingue De Base.Alternativement, vous pouvez activer le
NO_BACKSLASH_ESCAPES
mode SQL, qui (entre autres choses) altère le fonctionnement demysql_real_escape_string()
. Avec ce mode activé,0x27
sera remplacé par0x2727
plutôt que0x5c27
et ainsi échapper processus ne peut pas créer des caractères valides dans les vulnérables codages où ils n'existaient pas auparavant (c'est à dire0xbf27
est encore0xbf27
etc.)—donc le serveur sera toujours rejeter la chaîne comme non valide. Cependant, voir @eggyal réponse pour une vulnérabilité différente qui peuvent résulter de l'utilisation de ce mode SQL (mais pas avec PDO).Sûr Exemples
Les exemples suivants sont en sécurité:
Parce que le serveur s'attend à
utf8
...Parce que nous avons correctement définir le jeu de caractères de sorte que le client et le serveur match.
Parce que nous avons éteint émulé préparées.
Parce que nous avons définir le jeu de caractères correctement.
Parce que MySQLi n'est vrai déclarations préparées à l'avance tout le temps.
L'Emballage Jusqu'À
Si vous:
OU
utf8
/latin1
/ascii
/etc)OU
NO_BACKSLASH_ESCAPES
mode SQLVous êtes sûr à 100%.
Sinon, vous êtes vulnérable même si vous êtes en utilisant PDO Préparées...
Additif
J'ai été en train de travailler sur un patch pour modifier la valeur par défaut de ne pas imiter prépare pour une future version de PHP. Le problème que je suis en cours d'exécution en est que BEAUCOUP de tests de pause où je le fais. Un des problèmes est que émulé prépare fera que jeter des erreurs de syntaxe sur exécuter, mais c'est vrai prépare permettra de générer des erreurs sur les préparer. De sorte que peut provoquer des problèmes (et c'est la raison pour tests sont borking).
mysql_real_escape_string
ne serait pas de traiter correctement les cas où la connexion a été correctement configuré pour BIG5/GBK. Donc, en fait, même en l'appelantmysql_set_charset()
sur mysql < 5.0.22 serait vulnérable à ce bug! Donc, non, ce post est toujours applicable à 5.0.22 (parce que mysql_real_escape_string est seulement charset de suite à des appels à partir demysql_set_charset()
, qui est ce que ce post parle de dérivation)...PDO::ATTR_EMULATE_PREPARES = false
peut causer chuté DB connexions lors de la préparation de requêtes SQL. Je n'ai pas identifié les circonstances exactes encore (il m'arrive lors de l'utilisation de beaucoup de jointures et de la sélection d'une coutume DB de la fonction), mais c'est quelque chose d'être conscient de quand à l'aide de cette option.$pdo->query('SET NAMES gbk');
dans mon script? Si je suis à l'origine$pdo->query('SET NAMES utf8');
?NO_BACKSLASH_ESCAPES
peuvent également présenter de nouvelles vulnérabilités : stackoverflow.com/a/23277864/1014813SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
est une attaque réussie. Pour moi, il ressemble à qui est à la recherche de quelque chose qui n'est pas là. Le commentaire est l'attaque? Il laisse everyhting la pendaison? Vous pourriez peut-être ajouter quelques lignes pour expliquer cette partie, pour les noobs comme moi. Merci pour l'incroyable réponse.SELECT * FROM test WHERE name = '縗' drop table admins /*' LIMIT 1
? Ne devrait pas être quelque chose commeSELECT * FROM test WHERE name = 'a'; drop table admins; /*' LIMIT 1
? Mettre des points-virgules après chaque déclaration et l'utilisation d'un simple personnage avec un\x27
à la fin? Je suis sûr que je suis absent quelque chose ici.charset=latin1
. Jeté erreur #42000, en indiquant...near '/*' LIMIT 1' at line 1"
. Est-ce que ça doit être comme ça?Préparées /requêtes paramétrées sont généralement suffisantes pour empêcher 1er ordre injection que l'instruction*. Si vous utilisez de l'onu-vérifié sql dynamique de n'importe où ailleurs dans votre application, vous êtes toujours vulnérable à 2ème ordre injection.
D'ordre 2 injection de données a été parcouru par le biais de la base de données une fois avant d'être inclus dans une requête, et est beaucoup plus difficile à enlever. Autant que je sache, vous avez failli ne jamais voir le réel conçu 2ème ordre d'attaques, car il est généralement plus facile pour les attaquants social-ingénieur à leur façon, mais vous avez parfois l'ordre 2 bugs surgissent en raison de l'extra bénigne
'
caractères ou similaire.Vous pouvez accomplir une 2ème commande d'injection d'attaque lorsque vous pouvez provoquer une valeur à stocker dans une base de données qui est ensuite utilisée comme un littéral dans une requête. Comme un exemple, disons que vous entrez les informations suivantes à votre nouveau nom d'utilisateur lors de la création d'un compte sur un site web (en supposant DB MySQL pour cette question):
Si il n'y a pas d'autres restrictions sur le nom d'utilisateur, une déclaration préparée serait encore assurez-vous que ci-dessus incorporé requête n'est pas exécutée au moment de l'insertion, et de stocker la valeur correctement dans la base de données. Cependant, imaginez que, plus tard, l'application récupère votre nom d'utilisateur de la base de données, et utilise la concaténation de chaîne d'inclure cette valeur une nouvelle requête. Vous pouvez obtenir de voir quelqu'un d'autre mot de passe. Depuis les premiers noms dans la table des utilisateurs ont tendance à être admins, vous avez également peut-être juste donné la ferme. (À noter également: c'est une raison de plus de ne pas stocker les mots de passe en texte clair!)
Nous voir, alors, que les instructions préparées sont assez pour une seule requête, mais par eux-mêmes, ils sont pas suffisante pour protéger contre les attaques par injection sql dans l'ensemble de l'application, car ils manquent d'un mécanisme pour assurer que tous les accès à une base de données dans l'application utilise le code sécurisé. Cependant, utilisé dans le cadre de la bonne conception de l'application — ce qui peut inclure des pratiques telles que la révision du code ou de l'analyse statique, ou l'utilisation d'un ORM, les données de la couche, ou couche de service que les limites de sql dynamique — préparées sont le principal outil pour résoudre le problème de l'Injection Sql. Si vous suivez la bonne application des principes de conception, tels que l'accès aux données est séparée du reste de votre programme, il devient facile de faire respecter ou de vérification que toutes les requêtes utilise correctement paramétrées. Dans ce cas, l'injection sql (premier et second ordre) est complètement empêché.
*Il s'avère que MySql/PHP (bon d'accord, ont été) juste muette à propos de la manipulation de paramètres lors de la large personnages sont impliqués, et il y a encore un rare cas exposé dans le d'autres hautement voté réponse ici qui peuvent permettre l'injection de passer par une requête paramétrée.
mysqli_real_escape_string
et puis AOP? Bien sûr, votre final AOP rend sécurisé si vous utilisez paramétrés états, mais tous les bits de (inutile) de "sécurité" que vous avez ajoutés sont plus susceptibles de présenter une faille de sécurité loin de la DB de la sécurité, ou à tout le moins cauchemar code qui est terrible de comprendre et de travailler avec.Non, ils ne le sont pas toujours.
Cela dépend si vous autorisez la saisie de l'utilisateur pour être placée dans la requête elle-même. Par exemple:
serait vulnérable aux injections SQL et l'utilisation des requêtes préparées dans cet exemple ne fonctionnera pas, parce que l'entrée de l'utilisateur est utilisé comme identifiant, et non pas comme des données. Le droit de réponse ici serait d'utiliser une sorte de filtrage et de validation comme:
Remarque: vous ne pouvez pas utiliser PDO pour lier les données qui se passe à l'extérieur de DDL (Data Definition Language), c'est à dire ce qui ne fonctionne pas:
La raison pour laquelle le ci-dessus ne fonctionne pas parce que
DESC
etASC
ne sont pas données. AOP ne peut échapper pour données. Deuxièmement, vous ne pouvez même pas mettre'
des guillemets autour d'elle. La seule façon de permettre à l'utilisateur choisi le tri est de filtrer manuellement et vérifier qu'il soitDESC
ouASC
.SELECT * FROM 'table'
peut-être tort, comme il se doitSELECT * FROM `table`
ou sans backsticks. Puis certaines choses commeORDER BY DESC
oùDESC
vient de l'utilisateur ne peut pas être tout simplement échappé. Donc, en pratique, les scénarios sont plutôt illimité.switch
pour dériver le nom de la table.sprintf
. Juste à construire un modèle de déclaration de là, et ensuite seulement de préparer le modèle généré déclaration.Oui, c'est suffisant. Le chemin des attaques de type injection de travail, est en quelque sorte d'obtenir les services d'un interprète (base de données) afin d'évaluer quelque chose, qui aurait été données, comme si c'était du code. Cela n'est possible que si vous mélangez le code et les données dans le même format (par exemple. lorsque vous construisez une requête comme une chaîne de caractères).
De paramétrer des requêtes en envoyant le code et les données séparément, de sorte qu'il serait jamais être possible de trouver un trou.
Vous pouvez toujours être vulnérables à d'autres d'injection attaques de type si. Par exemple, si vous utilisez les données dans une page HTML, vous pourriez être assujetti à des attaques de type XSS.
Non ce n'est pas assez (dans certains cas)! Par défaut PDO utilise des émules préparées lors de l'utilisation de MySQL en tant que pilote de base de données. Vous devez toujours désactiver émulé préparées lors de l'utilisation de MySQL et PDO:
Une autre chose qui doit toujours être le fait de définir l'encodage correct de la base de données:
Voir aussi cette question: Comment puis-je prévenir l'injection SQL en PHP?
Également noter que c'est seulement sur la base de données côté des choses que vous auriez encore à vous regarder lors de l'affichage des données. E. g. en utilisant
htmlspecialchars()
à nouveau avec le bon encodage et citant style.Personnellement, je serais toujours exécuter une certaine forme de l'assainissement sur les données, car vous ne pouvez jamais faire confiance la saisie de l'utilisateur, cependant lors de l'utilisation des espaces réservés /paramètre de liaison de la saisie de données est envoyé au serveur séparément à l'instruction sql et ensuite lier ensemble. La clé ici est que cette lie les données fournies à un type spécifique et une utilisation spécifique et élimine toute possibilité de changer la logique de l'instruction SQL.
Eaven si vous allez à prévenir l'injection sql front-end, à l'aide de html ou js contrôles, vous devez considérer que, avant la fin des vérifications sont "insertions by-passables".
Vous pouvez désactiver le js ou modifier un modèle avec une avant la fin de l'outil de développement (construit en avec firefox ou chrome, de nos jours).
Afin de prévenir l'injection SQL, serait en droit de vérifier les entrées de date backend à l'intérieur de votre contrôleur.
Je voudrais vous suggérer d'utiliser filter_input() PHP natif de la fonction, afin de désinfecter les OBTENIR et les valeurs d'ENTRÉE.
Si vous voulez aller de l'avant avec la sécurité, pour mieux requêtes de base de données, j'aimerais vous proposer d'utiliser une expression régulière pour valider le format de données.
preg_match() vous aideront dans ce cas!
Mais prenez soin de vous! Moteur d'expressions régulières n'est pas si léger. L'utiliser uniquement si nécessaire, faute de quoi votre demande performances vont diminuer.
La sécurité a un coût, mais ne perdez pas votre performance!
Exemple facile:
si vous voulez vérifier si une valeur, il a reçu de OBTENEZ est un nombre, moins de 99
if(!preg_match('/[0-9]{1,2}/')){...}
est heavyer de
Donc, la réponse finale est: "Non! AOP Préparées ne pas empêcher tout type d'injection sql"; Elle n'empêche pas des valeurs inattendues, juste inattendu concaténation