Disons que j'ai un code comme celui-ci:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
La documentation PDO indique:
Les paramètres des instructions préparées n'ont pas besoin d'être cités; le conducteur s'en charge pour vous.
Est-ce vraiment tout ce que je dois faire pour éviter les injections SQL? est-ce vraiment si facile?
Vous pouvez assumer MySQL si cela fait une différence. De plus, je ne suis vraiment curieux que d'utiliser des instructions préparées contre l'injection SQL. Dans ce contexte, je ne me soucie pas de XSS ou d'autres vulnérabilités possibles.
php
security
pdo
sql-injection
Mark Biek
la source
la source
Réponses:
La réponse courte est NON , PDO prépare ne vous défendra pas de toutes les attaques SQL-Injection possibles. Pour certains cas de bord obscurs.
J'adapte cette réponse pour parler de PDO ...
La réponse longue n'est pas si simple. C'est basé sur une attaque démontrée ici .
L'attaque
Commençons donc par montrer l'attaque ...
Dans certaines circonstances, cela renverra plus d'une ligne. Décortiquons ce qui se passe ici:
Sélection d'un jeu de caractères
Pour que cette attaque fonctionne, nous avons besoin du codage que le serveur attend sur la connexion à la fois pour coder
'
comme en ASCII ie0x27
et pour avoir un caractère dont l'octet final est un ASCII\
ie0x5c
. Comme il se trouve, il y a 5 ces codages pris en charge par 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 d'
SET NAMES
ici. Cela définit le jeu de caractères SUR LE SERVEUR . Il y a une autre façon de le faire, mais nous y arriverons assez tôt.La charge utile
La charge utile que nous allons utiliser pour cette injection commence par la séquence d'octets
0xbf27
. Dansgbk
, c'est un caractère multi-octets invalide; danslatin1
, c'est la chaîne¿'
. Notez que danslatin1
etgbk
,0x27
en soi, est un'
caractère littéral .Nous avons choisi cette charge utile car, si nous l'appelions
addslashes()
, nous insérions un ASCII,\
c'est0x5c
-à- dire avant le'
caractère. Nous finirions donc avec0xbf5c27
, quigbk
est une séquence de deux caractères:0xbf5c
suivi de0x27
. Ou en d'autres termes, un caractère valide suivi d'un caractère non échappé'
. Mais nous n'utilisons pasaddslashes()
. Passons à l'étape suivante ...$ stmt-> execute ()
La chose importante à réaliser ici est que PDO par défaut ne fait pas de véritables instructions préparées. Il les émule (pour MySQL). Par conséquent, PDO construit en interne la chaîne de requête, appelant
mysql_real_escape_string()
(la fonction MySQL C API) sur chaque valeur de chaîne liée.L'appel de l'API C
mysql_real_escape_string()
diffère deaddslashes()
par le fait qu'il connaît le jeu de caractères de connexion. Il peut donc effectuer correctement l'échappement pour le jeu de caractères attendu par le serveur. Cependant, jusqu'à présent, le client pense que nous utilisons toujourslatin1
la connexion, car nous ne l'avons jamais dit autrement. Nous avons dit au serveur que nous utilisonsgbk
, mais le client pense toujours que c'est le caslatin1
.Par conséquent, l'appel à
mysql_real_escape_string()
insère la barre oblique inverse, et nous avons un'
caractère suspendu gratuit dans notre contenu "échappé"! En fait, si nous regardions$var
dans legbk
jeu de caractères, nous verrions:C'est exactement ce dont l'attaque a besoin.
La requête
Cette partie n'est qu'une formalité, mais voici la requête rendue:
Félicitations, vous venez d'attaquer avec succès un programme à l'aide de PDO Prepared Statements ...
La solution simple
Maintenant, il convient de noter que vous pouvez empêcher cela en désactivant les instructions préparées émulées:
Cela se traduira généralement par une véritable instruction préparée (c'est-à-dire que les données sont envoyées dans un paquet distinct de la requête). Cependant, sachez que PDO se repliera silencieusement sur des instructions d'émulation que MySQL ne peut pas préparer nativement: celles qu'il peut sont listées dans le manuel, mais attention à sélectionner la version de serveur appropriée).
La bonne solution
Le problème ici est que nous n'avons pas appelé les API C à la
mysql_set_charset()
place deSET NAMES
. Si nous le faisions, nous serions bien à condition d'utiliser une version de MySQL depuis 2006.Si vous utilisez une version de MySQL plus tôt, alors un bogue dans
mysql_real_escape_string()
signifie que les caractères multi - octets invalides tels que ceux de notre charge utile ont été traités comme les octets pour échapper à des fins même si le client avait été correctement informé de l'encodage de connexion et donc cette attaque serait réussir encore. Le bogue a été corrigé dans MySQL 4.1.20 , 5.0.22 et 5.1.11 .Mais le pire, c'est que
PDO
l'API C n'a pas étémysql_set_charset()
exposée avant la 5.3.6, donc dans les versions précédentes, elle ne peut pas empêcher cette attaque pour chaque commande possible! Il est maintenant exposé en tant que paramètre DSN , qui devrait être utilisé à la place deSET NAMES
...La grâce salvatrice
Comme nous l'avons dit au début, pour que cette attaque fonctionne, la connexion à la base de données doit être codée à l'aide d'un jeu de caractères vulnérable.
utf8mb4
n'est pas vulnérable et peut néanmoins prendre en charge tous les caractères Unicode: vous pouvez donc choisir de l'utiliser à la place, mais il n'est disponible que depuis MySQL 5.5.3. Une alternative estutf8
, qui n'est pas non plus vulnérable et peut prendre en charge l'ensemble du plan multilingue de base Unicode .Alternativement, vous pouvez activer le
NO_BACKSLASH_ESCAPES
mode SQL, qui (entre autres) modifie le fonctionnement demysql_real_escape_string()
. Avec ce mode activé,0x27
sera remplacé par0x2727
plutôt que0x5c27
et donc le processus d'échappement ne peut pas créer de caractères valides dans aucun des encodages vulnérables où ils n'existaient pas auparavant (c'est0xbf27
-à- dire est toujours0xbf27
etc.) - donc le serveur rejettera toujours la chaîne comme invalide . Cependant, voir la réponse de @ eggyal pour une vulnérabilité différente qui peut résulter de l'utilisation de ce mode SQL (mais pas avec PDO).Exemples sûrs
Les exemples suivants sont sûrs:
Parce que le serveur attend
utf8
...Parce que nous avons correctement défini le jeu de caractères pour que le client et le serveur correspondent.
Parce que nous avons désactivé les instructions préparées émulées.
Parce que nous avons correctement défini le jeu de caractères.
Parce que MySQLi fait tout le temps de véritables instructions préparées.
Emballer
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 utilisez des déclarations préparées par PDO ...
Addenda
J'ai lentement travaillé sur un correctif pour modifier la valeur par défaut afin de ne pas émuler, se prépare pour une future version de PHP. Le problème que je rencontre est que BEAUCOUP de tests se cassent quand je fais ça. Un problème est que les préparations émulées ne lanceront que des erreurs de syntaxe lors de l'exécution, mais les vraies préparations généreront des erreurs lors de la préparation. Cela peut donc causer des problèmes (et cela fait partie de la raison pour laquelle les tests fonctionnent).
la source
mysql_real_escape_string
ne traitait pas correctement les cas où la connexion était correctement définie sur BIG5 / GBK. Donc, en fait, même appelermysql_set_charset()
mysql <5.0.22 serait vulnérable à ce bogue! Donc non, ce post est toujours applicable à 5.0.22 (parce que mysql_real_escape_string est uniquement charset loin des appels demysql_set_charset()
, ce qui est ce que ce post parle de contourner) ...NO_BACKSLASH_ESCAPES
peut également introduire de nouvelles vulnérabilités: stackoverflow.com/a/23277864/1014813Les instructions préparées / requêtes paramétrées sont généralement suffisantes pour empêcher l' injection de premier ordre sur cette instruction * . Si vous utilisez SQL dynamique non vérifié ailleurs dans votre application, vous êtes toujours vulnérable à une injection de second ordre .
L'injection de second ordre signifie que les données ont été parcourues une fois dans la base de données avant d'être incluses dans une requête et sont beaucoup plus difficiles à extraire. AFAIK, vous ne voyez presque jamais de vraies attaques de deuxième ordre, car il est généralement plus facile pour les attaquants de s'ingénier socialement, mais vous avez parfois des bogues de deuxième ordre qui surviennent en raison de
'
caractères bénins supplémentaires ou similaires.Vous pouvez effectuer une attaque par injection de second ordre lorsque vous pouvez faire en sorte qu'une valeur soit stockée dans une base de données qui sera ensuite utilisée comme littéral dans une requête. Par exemple, disons que vous entrez les informations suivantes comme nouveau nom d'utilisateur lors de la création d'un compte sur un site Web (en supposant que la base de données MySQL pour cette question):
S'il n'y a pas d'autres restrictions sur le nom d'utilisateur, une instruction préparée garantirait toujours que la requête intégrée ci-dessus ne s'exécute pas au moment de l'insertion et stocke correctement la valeur dans la base de données. Cependant, imaginez que l'application récupère ultérieurement votre nom d'utilisateur dans la base de données et utilise la concaténation de chaînes pour inclure cette valeur dans une nouvelle requête. Vous pourriez voir le mot de passe de quelqu'un d'autre. Étant donné que les premiers noms de la table des utilisateurs sont généralement des administrateurs, vous avez peut-être également donné la ferme. (Notez également: c'est une raison de plus pour ne pas stocker les mots de passe en texte brut!)
Nous voyons, alors, que les instructions préparées sont suffisantes pour une seule requête, mais en elles-mêmes, elles ne sont pas suffisantes pour se protéger contre les attaques par injection sql dans toute une application, car elles manquent d'un mécanisme pour imposer tous les accès à une base de données dans une application utilise un coffre-fort code. Cependant, utilisées dans le cadre d'une bonne conception d'application - qui peut inclure des pratiques telles que la révision de code ou l'analyse statique, ou l'utilisation d'un ORM, d'une couche de données ou d'une couche de service qui limite le SQL dynamique - les instructions préparées sont le principal outil pour résoudre l'injection SQL problème.Si vous suivez de bons principes de conception d'application, de sorte que votre accès aux données soit séparé du reste de votre programme, il devient facile de faire respecter ou de vérifier que chaque requête utilise correctement le paramétrage. Dans ce cas, l'injection SQL (de premier et de second ordre) est complètement empêchée.
* Il s'avère que MySql / PHP sont (d'accord, étaient) juste stupides à propos de la gestion des paramètres lorsque des caractères larges sont impliqués, et il y a encore un cas rare décrit dans l' autre réponse très votée ici qui peut permettre à l'injection de passer à travers un paramètre requete.
la source
Non, ils ne le sont pas toujours.
Cela dépend si vous autorisez l'entrée de l'utilisateur à être placée dans la requête elle-même. Par exemple:
serait vulnérable aux injections SQL et l'utilisation d'instructions préparées dans cet exemple ne fonctionnera pas, car l'entrée utilisateur est utilisée comme identifiant, pas comme données. La bonne réponse ici serait d'utiliser une sorte de filtrage / validation comme:
Remarque: vous ne pouvez pas utiliser PDO pour lier des données qui sortent du DDL (Data Definition Language), c'est-à-dire que cela ne fonctionne pas:
La raison pour laquelle ce qui précède ne fonctionne pas est parce
DESC
que ceASC
ne sont pas des données . PDO ne peut s'échapper que pour les données . Deuxièmement, vous ne pouvez même pas y mettre de'
guillemets. La seule façon d'autoriser le tri choisi par l'utilisateur est de filtrer manuellement et de vérifier qu'il s'agit deDESC
ouASC
.la source
SELECT * FROM 'table'
peut être faux comme il se doitSELECT * FROM `table`
ou sans baguettes. Ensuite, certaines choses comme d'ORDER BY DESC
oùDESC
vient l'utilisateur ne peuvent pas être simplement échappées. Les scénarios pratiques sont donc plutôt illimités.switch
pour dériver le nom de la table.Oui, c'est suffisant. La façon dont les attaques de type injection fonctionnent, consiste à obtenir un interprète (la base de données) pour évaluer quelque chose, qui aurait dû être des données, comme s'il s'agissait de code. Cela n'est possible que si vous mélangez du code et des données sur le même support (par exemple, lorsque vous créez une requête sous forme de chaîne).
Les requêtes paramétrées fonctionnent en envoyant le code et les données séparément, il ne serait donc jamais possible de trouver un trou dans cela.
Cependant, vous pouvez toujours être vulnérable à d'autres attaques de type injection. Par exemple, si vous utilisez les données dans une page HTML, vous pourriez être sujet à des attaques de type XSS.
la source
Non ce n'est pas suffisant (dans certains cas spécifiques)! Par défaut, PDO utilise des instructions préparées émulées lors de l'utilisation de MySQL comme pilote de base de données. Vous devez toujours désactiver les instructions préparées émulées lorsque vous utilisez MySQL et PDO:
Une autre chose qui devrait toujours être faite est de définir le bon encodage de la base de données:
Voir également cette question connexe: Comment puis-je empêcher l'injection SQL en PHP?
Notez également que cela concerne uniquement le côté base de données des éléments que vous devrez toujours surveiller vous-même lors de l'affichage des données. Par exemple, en utilisant à
htmlspecialchars()
nouveau avec le bon style d'encodage et de citation.la source
Personnellement, je voudrais toujours exécuter une forme d'assainissement sur les données en premier, car vous ne pouvez jamais faire confiance à l'entrée de l'utilisateur, cependant lorsque vous utilisez la liaison des paramètres / espaces réservés, les données entrées sont envoyées au serveur séparément dans l'instruction sql, puis liées ensemble. La clé ici est que cela lie les données fournies à un type spécifique et à une utilisation spécifique et élimine toute possibilité de modifier la logique de l'instruction SQL.
la source
Même si vous voulez empêcher le frontal d'injection SQL, en utilisant des contrôles html ou js, vous devez considérer que les contrôles frontaux sont "contournables".
Vous pouvez désactiver js ou modifier un modèle avec un outil de développement frontal (intégré à Firefox ou Chrome de nos jours).
Donc, afin d'empêcher l'injection SQL, il serait juste de nettoyer le backend de la date d'entrée dans votre contrôleur.
Je voudrais vous suggérer d'utiliser la fonction PHP native filter_input () afin d'assainir les valeurs GET et INPUT.
Si vous voulez aller de l'avant avec la sécurité, pour les requêtes de base de données sensibles, je voudrais vous suggérer d'utiliser une expression régulière pour valider le format des données. preg_match () vous aidera dans ce cas! Mais attention! Le moteur Regex n'est pas si léger. Utilisez-le uniquement si nécessaire, sinon les performances de votre application diminueront.
La sécurité a un coût, mais ne gâchez pas vos performances!
Exemple simple:
si vous voulez vérifier si une valeur reçue de GET est un nombre, moins de 99 si (! preg_match ('/ [0-9] {1,2} /')) {...} est plus lourd de
Ainsi, la réponse finale est: "Non! Les déclarations préparées par PDO n'empêchent pas toutes sortes d'injection SQL"; Il n'empêche pas les valeurs inattendues, juste une concaténation inattendue
la source