Injection SQL qui contourne mysql_real_escape_string ()

644

Existe-t-il une possibilité d'injection SQL même lors de l'utilisation de la mysql_real_escape_string()fonction?

Considérez cet exemple de situation. SQL est construit en PHP comme ceci:

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

J'ai entendu de nombreuses personnes me dire qu'un code comme celui-ci est toujours dangereux et qu'il est possible de pirater même avec la mysql_real_escape_string()fonction utilisée. Mais je ne peux penser à aucun exploit possible?

Injections classiques comme celle-ci:

aaa' OR 1=1 --

ne fonctionnent pas.

Connaissez-vous une injection possible qui passerait par le code PHP ci-dessus?

Richard Knop
la source
34
@ThiefMaster - Je préfère ne pas donner d'erreurs verbeuses comme un utilisateur non valide / un mot de passe invalide ... cela dit aux marchands en force brute qu'ils ont un ID utilisateur valide, et c'est juste le mot de passe qu'ils doivent deviner
Mark Baker
18
C'est horrible du point de vue de la convivialité. Parfois, vous ne pouviez pas utiliser votre pseudo / nom d'utilisateur / adresse e-mail principal et l'oublier après un certain temps ou le site a supprimé votre compte pour inactivité. Ensuite, c'est extrêmement ennuyeux si vous continuez à essayer des mots de passe et peut-être même à bloquer votre IP même si c'est juste votre nom d'utilisateur qui n'est pas valide.
ThiefMaster
50
Veuillez ne pas utiliser de mysql_*fonctions dans le nouveau code . Ils ne sont plus maintenus et le processus de dépréciation a commencé. Vous voyez la boîte rouge ? Découvrezplutôt les instructions préparées et utilisez PDO ou MySQLi - cet article vous aidera à décider lesquelles. Si vous choisissez PDO, voici un bon tutoriel .
tereško
13
@machineaddict, depuis la version 5.5 (qui a été publiée récemment), les mysql_*fonctions produisent déjà un E_DEPRECATEDavertissement. L' ext/mysqlextension n'a pas été maintenue depuis plus de 10 ans. Êtes-vous vraiment si délirant?
tereško
13
@machineaddict Ils viennent de supprimer cette extension sur PHP 7.0 et ce n'est pas encore 2050.
GGG

Réponses:

379

Considérez la requête suivante:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()ne vous protégera pas contre cela. Le fait que vous utilisez des guillemets simples ( ' ') autour de vos variables dans votre requête est ce qui vous protège contre cela. Ce qui suit est également une option:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
Wesley van Opdorp
la source
9
Mais ce ne serait pas un vrai problème, car mysql_query()n'exécute pas plusieurs instructions, non?
Pekka
11
@Pekka, Bien que l'exemple habituel soit DROP TABLE, dans la pratique, l'attaquant est plus susceptible de le faire SELECT passwd FROM users. Dans ce dernier cas, la deuxième requête est généralement exécutée à l'aide d'une UNIONclause.
Jacco
58
(int)mysql_real_escape_string- Cela n'a aucun sens. Cela ne diffère pas du (int)tout. Et ils produiront le même résultat pour chaque entrée
zerkms
28
Il s'agit plus d'une mauvaise utilisation de la fonction qu'autre chose. Après tout, il est nommé mysql_real_escape_string, non mysql_real_escape_integer. Ce n'est pas destiné à être utilisé avec des champs entiers.
NullUserException
11
@ircmaxell, Pourtant, la réponse est totalement trompeuse. De toute évidence, la question porte sur le contenu des citations. "Les citations ne sont pas là" n'est pas la réponse à cette question.
Pacerier
629

La réponse courte est oui, oui, il existe un moyen de se déplacermysql_real_escape_string() .

Pour les CAS TRÈS OBSCURES !!!

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 ...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Dans certaines circonstances, cela renverra plus d'une ligne. Décortiquons ce qui se passe ici:

  1. Sélection d'un jeu de caractères

    mysql_query('SET NAMES gbk');

    Pour que cette attaque fonctionne, nous avons besoin du codage que le serveur attend sur la connexion à la fois pour coder 'comme en ASCII ie 0x27 et pour avoir un caractère dont l'octet final est un ASCII \ie 0x5c. Comme il se trouve, il y a 5 ces codages pris en charge par MySQL 5.6 par défaut: big5, cp932, gb2312, gbket sjis. Nous allons sélectionner gbkici.

    Maintenant, il est très important de noter l'utilisation d' SET NAMESici. Cela définit le jeu de caractères SUR LE SERVEUR . Si nous utilisions l'appel à la fonction API C mysql_set_charset(), tout irait bien (sur les versions de MySQL depuis 2006). Mais plus sur pourquoi dans une minute ...

  2. La charge utile

    La charge utile que nous allons utiliser pour cette injection commence par la séquence d'octets 0xbf27. Dans gbk, c'est un caractère multi-octets invalide; dans latin1, c'est la chaîne ¿'. Notez que dans latin1 et gbk , 0x27en 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'est 0x5c-à- dire avant le 'caractère. Nous finirions donc avec 0xbf5c27, qui gbkest une séquence de deux caractères: 0xbf5csuivi de 0x27. Ou en d'autres termes, un caractère valide suivi d'un caractère non échappé '. Mais nous n'utilisons pas addslashes(). Passons à l'étape suivante ...

  3. mysql_real_escape_string ()

    L'appel de l'API C mysql_real_escape_string()diffère de addslashes()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 toujours latin1la connexion, car nous ne l'avons jamais dit autrement. Nous avons dit au serveur que nous utilisons gbk, mais le client pense toujours que c'est le cas latin1.

    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 $vardans le gbkjeu de caractères, nous verrions:

    縗 'OU 1 = 1 / *

    C'est exactement ce dont l'attaque a besoin.

  4. La requête

    Cette partie n'est qu'une formalité, mais voici la requête rendue:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1

Félicitations, vous venez d'attaquer avec succès un programme en utilisant mysql_real_escape_string()...

Le mauvais

Ça s'empire. PDOpar défaut pour émuler des instructions préparées avec MySQL. Cela signifie que du côté client, il effectue essentiellement un sprintf à travers mysql_real_escape_string()(dans la bibliothèque C), ce qui signifie que ce qui suit se traduira par une injection réussie:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Maintenant, il convient de noter que vous pouvez empêcher cela en désactivant les instructions préparées émulées:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

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).

Le moche

J'ai dit au tout début que nous aurions pu empêcher tout cela si nous avions utilisé à la mysql_set_charset('gbk')place de SET NAMES gbk. Et c'est vrai à condition d'utiliser une version 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 PDOl'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 .

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. utf8mb4n'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 est utf8, 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_ESCAPESmode SQL, qui (entre autres) modifie le fonctionnement de mysql_real_escape_string(). Avec ce mode activé, 0x27sera remplacé par 0x2727plutôt que 0x5c27et 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'est 0xbf27-à- dire est toujours 0xbf27etc.) - 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.

Exemples sûrs

Les exemples suivants sont sûrs:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Parce que le serveur attend utf8...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Parce que nous avons correctement défini le jeu de caractères afin que le client et le serveur correspondent.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Parce que nous avons désactivé les instructions préparées émulées.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Parce que nous avons correctement défini le jeu de caractères.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Parce que MySQLi fait tout le temps de véritables instructions préparées.

Emballer

Si vous:

  • Utiliser les versions modernes de MySQL (fin 5.1, tous 5.5, 5.6, etc.) ET mysql_set_charset() / $mysqli->set_charset()/ PDO paramètre de jeu de caractères DSN (en PHP ≥ 5.3.6)

OU

  • N'utilisez pas de jeu de caractères vulnérable pour le codage de connexion (vous utilisez uniquement utf8/ latin1/ ascii/ etc)

Vous êtes sûr à 100%.

Sinon, vous êtes vulnérable même si vous utilisezmysql_real_escape_string() ...

ircmaxell
la source
3
PDO émulant des instructions de préparation pour MySQL, vraiment? Je ne vois aucune raison pour laquelle cela ferait cela puisque le pilote le prend en charge nativement. Non?
netcoder
16
Cela fait. Ils disent que ce n'est pas le cas dans la documentation. Mais dans le code source, il est clairement visible et facile à corriger. Je mets ça sur le compte de l'incompétence des développeurs.
Theodore R. Smith
5
@ TheodoreR.Smith: Ce n'est pas si facile à réparer. J'ai travaillé sur la modification de la valeur par défaut, mais il échoue à une charge de tests de bateau lorsqu'il est commuté. C'est donc un changement plus important qu'il n'y paraît. J'espère toujours l'avoir terminé par 5,5 ...
ircmaxell
14
@shadyyx: Non, la vulnérabilité dont parlait l'article décrit addslashes. J'ai basé cette vulnérabilité sur celle-là. Essayez-le vous-même. Allez chercher MySQL 5.0, exécutez cet exploit et voyez par vous-même. En ce qui concerne la façon de mettre cela dans PUT / GET / POST, c'est TRIVIAL. Les données d'entrée ne sont que des flux d'octets. char(0xBF)est juste un moyen lisible de générer un octet. J'ai démontré cette vulnérabilité en direct devant plusieurs conférences. Faites-moi confiance à ce sujet ... Mais si vous ne le faites pas, essayez-le vous-même. Ça marche ...
ircmaxell
5
@shadyyx: Quant à passer une telle funkiness dans $ _GET ... ?var=%BF%27+OR+1=1+%2F%2Adans l'URL, $var = $_GET['var'];dans le code, et Bob est ton oncle.
cHao
183

TL; DR

mysql_real_escape_string() volonté fournira aucune protection (et pourrait en outre communiquer vos données) si:

  • Le NO_BACKSLASH_ESCAPESmode SQL de MySQL est activé (ce qui pourrait être le cas, sauf si vous sélectionnez explicitement un autre mode SQL à chaque connexion ); et

  • vos littéraux de chaîne SQL sont indiqués entre guillemets ".

Cela a été classé comme bug # 72458 et a été corrigé dans MySQL v5.7.6 (voir la section intitulée " La grâce qui sauve ", ci-dessous).

Ceci est un autre, (peut-être moins?) CAS DE BORD obscur !!!

En hommage à l'excellente réponse @ ircmaxell (vraiment, c'est censé être de la flatterie et non du plagiat!), J'adopterai son format:

L'attaque

Commençant par une démonstration ...

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

Cela renverra tous les enregistrements de la testtable. Une dissection:

  1. Sélection d'un mode SQL

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');

    Comme documenté sous String Literals :

    Il existe plusieurs façons d'inclure des guillemets dans une chaîne:

    • Un « '» à l'intérieur d'une chaîne entre guillemets « '» peut s'écrire « ''».

    • Un « "» à l'intérieur d'une chaîne entre guillemets « "» peut s'écrire « ""».

    • Faites précéder le caractère de citation d'un caractère d'échappement (« \»).

    • Un " '" à l'intérieur d'une chaîne entre guillemets " "" ne nécessite aucun traitement spécial et n'a pas besoin d'être doublé ou échappé. De la même manière, " "" à l'intérieur d'une chaîne entre guillemets " '" ne nécessite aucun traitement spécial.

    Si le mode SQL du serveur inclut NO_BACKSLASH_ESCAPES, alors la troisième de ces options - qui est l'approche habituelle adoptée par mysql_real_escape_string()- n'est pas disponible: l'une des deux premières options doit être utilisée à la place. Notez que l'effet de la quatrième puce est que l'on doit nécessairement connaître le caractère qui sera utilisé pour citer le littéral afin d'éviter de fusionner ses données.

  2. La charge utile

    " OR 1=1 -- 

    La charge utile lance cette injection littéralement avec le "personnage. Pas d'encodage particulier. Pas de caractères spéciaux. Pas d'octets bizarres.

  3. mysql_real_escape_string ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');

    Heureusement, mysql_real_escape_string()vérifie le mode SQL et ajuste son comportement en conséquence. Voir libmysql.c:

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }

    Ainsi, une fonction sous-jacente différente,, escape_quotes_for_mysql()est invoquée si le NO_BACKSLASH_ESCAPESmode SQL est utilisé. Comme mentionné ci-dessus, une telle fonction doit savoir quel caractère sera utilisé pour citer le littéral afin de le répéter sans provoquer la répétition littérale de l'autre caractère de citation.

    Cependant, cette fonction suppose arbitrairement que la chaîne sera citée en utilisant le 'caractère guillemet simple . Voir charset.c:

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == '\'')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= '\'';
          *to++= '\'';
        }

    Ainsi, il laisse les "caractères entre guillemets intacts (et double tous les 'caractères entre guillemets ) quel que soit le caractère réel utilisé pour citer le littéral ! Dans notre cas, il $varreste exactement le même que l'argument qui a été fourni à mysql_real_escape_string()- c'est comme si aucune fuite n'avait eu lieu du tout .

  4. La requête

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

    Quelque chose d'une formalité, la requête rendue est:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1

Comme l'a dit mon confrère: félicitations, vous venez d'attaquer avec succès un programme en utilisant mysql_real_escape_string()...

Le mauvais

mysql_set_charset()ne peut pas aider, car cela n'a rien à voir avec les jeux de caractères; pas plus mysqli::real_escape_string(), car c'est juste un wrapper différent autour de cette même fonction.

Le problème, s'il n'est pas déjà évident, est que l'appel à mysql_real_escape_string() ne peut pas savoir avec quel caractère le littéral sera cité, car il appartient au développeur de décider plus tard. Donc, en NO_BACKSLASH_ESCAPESmode, il n'y a littéralement aucun moyen que cette fonction puisse échapper en toute sécurité à chaque entrée pour une utilisation avec des guillemets arbitraires (au moins, non sans doubler les caractères qui ne nécessitent pas de doubler et donc de fusionner vos données).

Le moche

Ça s'empire. NO_BACKSLASH_ESCAPESpeut ne pas être si rare dans la nature en raison de la nécessité de son utilisation pour la compatibilité avec le SQL standard (par exemple, voir la section 5.3 de la spécification SQL-92 , à savoir la <quote symbol> ::= <quote><quote>production de grammaire et le manque de signification particulière donnée à la barre oblique inverse). De plus, son utilisation a été explicitement recommandée comme solution de contournement au bogue (corrigé depuis longtemps) décrit par la publication d'ircmaxell. Qui sait, certains administrateurs de base de données peuvent même le configurer pour qu'il soit activé par défaut afin de décourager l'utilisation de méthodes d'échappement incorrectes comme addslashes().

De plus, le mode SQL d'une nouvelle connexion est défini par le serveur en fonction de sa configuration (qu'un SUPERutilisateur peut modifier à tout moment); ainsi, pour être certain du comportement du serveur, vous devez toujours spécifier explicitement le mode souhaité après la connexion.

La grâce salvatrice

Tant que vous définissez toujours explicitement le mode SQL pour qu'il n'inclue pas NO_BACKSLASH_ESCAPESou ne cite pas les littéraux de chaîne MySQL en utilisant le caractère guillemet simple, ce bogue ne peut pas escape_quotes_for_mysql()afficher sa tête laide: respectivement , ne sera pas utilisé, ou son hypothèse sur les caractères guillemet nécessitant une répétition sera être correct.

Pour cette raison, je recommande que toute personne utilisant NO_BACKSLASH_ESCAPESégalement active le ANSI_QUOTESmode, car cela forcera l'utilisation habituelle des littéraux de chaîne entre guillemets simples. Notez que cela n'empêche pas l'injection SQL dans le cas où des littéraux entre guillemets sont utilisés - cela réduit simplement la probabilité que cela se produise (car les requêtes normales et non malveillantes échoueraient).

Dans PDO, sa fonction équivalente PDO::quote()et son émulateur d'instructions préparé font appel à mysql_handle_quoter()- ce qui fait exactement cela: il garantit que le littéral échappé est cité entre guillemets simples, de sorte que vous pouvez être certain que PDO est toujours à l'abri de ce bogue.

Depuis MySQL v5.7.6, ce bug a été corrigé. Voir le journal des modifications :

Fonctionnalité ajoutée ou modifiée

Exemples sûrs

Pris ensemble avec le bogue expliqué par ircmaxell, les exemples suivants sont entièrement sûrs (en supposant que l'on utilise MySQL plus tard que 4.1.20, 5.0.22, 5.1.11; ou que l'on n'utilise pas un codage de connexion GBK / Big5) :

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

... car nous avons explicitement sélectionné un mode SQL qui ne comprend pas NO_BACKSLASH_ESCAPES.

mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

... parce que nous citons notre chaîne littérale avec des guillemets simples.

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

... parce que les instructions préparées par PDO sont immunisées contre cette vulnérabilité (et ircmaxell aussi, à condition que vous utilisiez PHP ≥5.3.6 et que le jeu de caractères ait été correctement défini dans le DSN; ou que l'émulation des instructions préparées ait été désactivée) .

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

... parce que la quote()fonction de PDO échappe non seulement au littéral, mais le cite également (en guillemets simples '); notez que pour éviter le bogue d'ircmaxell dans ce cas, vous devez utiliser PHP≥5.3.6 et avoir correctement défini le jeu de caractères dans le DSN.

$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

... car les instructions préparées par MySQLi sont sûres.

Emballer

Ainsi, si vous:

  • utiliser des instructions natives préparées

OU

  • utiliser MySQL v5.7.6 ou version ultérieure

OU

  • en plus d'utiliser l'une des solutions du résumé d'ircmaxell, utilisez au moins l'une des solutions suivantes:

    • AOP;
    • littéraux de chaîne entre guillemets simples; ou
    • un mode SQL défini explicitement qui n'inclut pas NO_BACKSLASH_ESCAPES

... alors vous devriez être complètement en sécurité (vulnérabilités en dehors de la portée de la chaîne s'échappant).

eggyal
la source
10
Donc, TL; DR serait comme "il y a un mode serveur mysql NO_BACKSLASH_ESCAPES qui peut provoquer une injection si vous n'utilisez pas de guillemets simples.
Votre sens commun
Je ne parviens pas à accéder à bugs.mysql.com/bug.php?id=72458 ; Je reçois juste une page d'accès refusé. Est-il caché au public en raison d'un problème de sécurité? Aussi, ai-je bien compris de cette réponse que vous êtes le découvreur de la vulnérabilité? Si oui, félicitations.
Mark Amery
1
Les gens ne devraient pas utiliser "de chaînes en premier lieu. SQL dit que c'est pour les identifiants. Mais eh ... juste un autre exemple de MySQL disant "vis standards, je ferai ce que je veux". (Heureusement, vous pouvez inclure ANSI_QUOTESdans le mode de correction des bris de citation. Le mépris ouvert des normes, cependant, est un problème plus important qui pourrait nécessiter des mesures plus sévères.)
cHao
2
@DanAllen: ma réponse était un peu plus large, dans la mesure où vous pouvez éviter ce bogue particulier grâce à la quote()fonction de PDO - mais les instructions préparées sont un moyen beaucoup plus sûr et plus approprié d'éviter l'injection en général. Bien sûr, si vous avez directement concaténé des variables non échappées dans votre SQL, vous êtes très certainement vulnérable à l'injection, quelles que soient les méthodes que vous utilisez par la suite.
eggyal
1
@eggyall: Notre système repose sur le 2ème exemple sûr ci-dessus. Il y a des erreurs, où mysql_real_escape_string a été omis. Fixer ceux en mode d'urgence semble être la voie prudente, en espérant que nous ne soyons pas neutralisés avant les corrections. Ma justification est que la conversion en déclarations préparées sera un processus beaucoup plus long qui devra suivre. Est-ce que la raison pour laquelle les instructions préparées sont plus sûres est que les erreurs ne créent pas de vulnérabilités? En d'autres termes, le deuxième exemple ci-dessus est-il correctement mis en œuvre? Est-il aussi sûr que les instructions préparées?
DanAllen
18

Eh bien, il n'y a vraiment rien qui puisse passer par là, à part le %caractère générique. Il pourrait être dangereux si vous utilisiez une LIKEinstruction car l'attaquant pourrait utiliser la même %connexion si vous ne filtrez pas cela et devrait simplement forcer le mot de passe de l'un de vos utilisateurs. Les gens suggèrent souvent d'utiliser des instructions préparées pour le rendre sûr à 100%, car les données ne peuvent pas interférer avec la requête elle-même de cette façon. Mais pour des requêtes aussi simples, il serait probablement plus efficace de faire quelque chose comme$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);

Slava
la source
2
+1, mais les caractères génériques sont pour la clause LIKE, pas l'égalité simple.
Dor
7
Dans quelle mesure considérez-vous un remplacement simple more efficientplutôt que d'utiliser des déclarations préparées? (Les instructions préparées fonctionnent toujours, la bibliothèque peut être rapidement corrigée en cas d'attaques, n'expose pas les erreurs humaines [telles que la mauvaise saisie de la chaîne de remplacement complète] et présente des avantages importants en termes de performances si l'instruction est réutilisée.)
MatBailie
7
@Slava: vous limitez efficacement les noms d'utilisateur et les mots de passe aux caractères des mots uniquement. La plupart des gens qui connaissent la sécurité considéreraient cela comme une mauvaise idée, car cela réduit considérablement l'espace de recherche. Bien sûr, ils considéreraient également comme une mauvaise idée de stocker des mots de passe en texte clair dans la base de données, mais nous n'avons pas besoin d'aggraver le problème. :)
cHao
2
@cHao, ma suggestion concerne uniquement les connexions. Évidemment, vous n'avez pas besoin de filtrer les mots de passe, désolé, ce n'est pas clairement indiqué dans ma réponse. Mais en fait, cela pourrait être une bonne idée. Il serait beaucoup plus difficile d'utiliser brutalement "l'espace des arbres ignorants de la pierre" au lieu de "a4üua3! @V \" ä90; 8f "difficile à mémoriser. Même en utilisant un dictionnaire de, disons 3000 mots pour vous aider, sachant vous avez utilisé exactement 4 mots - ce serait encore environ 3,3 * 10 ^ 12 combinaisons. :)
Slava
2
@Slava: J'ai déjà vu cette idée; voir xkcd.com/936 . Le problème est que les mathématiques ne le confirment pas. Votre exemple de mot de passe de 17 caractères aurait comme 96 ^ 17 possibilités, et c'est si vous avez oublié les trémas et vous êtes limité à ASCII imprimable. C'est environ 4,5x10 ^ 33. Nous parlons littéralement d' un milliard de milliards de fois plus de travail à la force brute. Même un mot de passe ASCII à 8 caractères aurait 7,2 x 10 ^ 15 possibilités - 3 mille fois plus.
cHao