<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/include/shared-manual.inc';
$TOC = array();
$TOC_DEPRECATED = array();
$PARENTS = array();
include_once dirname(__FILE__) ."/toc/security.database.inc";
$setup = array (
  'home' => 
  array (
    0 => 'index.php',
    1 => 'PHP Manual',
  ),
  'head' => 
  array (
    0 => 'UTF-8',
    1 => 'fr',
  ),
  'this' => 
  array (
    0 => 'security.database.sql-injection.php',
    1 => 'Injection SQL',
    2 => 'Injection SQL',
  ),
  'up' => 
  array (
    0 => 'security.database.php',
    1 => 'S&eacute;curit&eacute; des bases de donn&eacute;es',
  ),
  'prev' => 
  array (
    0 => 'security.database.storage.php',
    1 => 'Mod&egrave;le de stockage avec chiffrement',
  ),
  'next' => 
  array (
    0 => 'security.errors.php',
    1 => 'Rapport d\'erreurs',
  ),
  'alternatives' => 
  array (
  ),
  'source' => 
  array (
    'lang' => 'fr',
    'path' => 'security/database.xml',
  ),
  'history' => 
  array (
  ),
);
$setup["toc"] = $TOC;
$setup["toc_deprecated"] = $TOC_DEPRECATED;
$setup["parents"] = $PARENTS;
manual_setup($setup);

contributors($setup);

?>
<div id="security.database.sql-injection" class="sect1">
  <h2 class="title">Injection SQL</h2>
  <p class="simpara">
   L&#039;injection SQL est une technique où un attaquant exploite les failles
   dans le code d&#039;application responsable de la création de requêtes SQL dynamiques.
   L&#039;attaquant peut accéder à des sections privilégiées de l&#039;application,
   récupérer toutes les informations de la base de données, altérer des données existantes,
   voire exécuter des commandes dangereuses au niveau système sur l&#039;hôte de la base de données.
   La vulnérabilité se produit lorsque les développeurs concatènent ou
   interpolent une entrée arbitraire dans leurs déclarations SQL.
  </p>
  <p class="para">
   <div class="example" id="example-1">
    <p><strong>Exemple #1 
     Séparation des résultats en pages, et créer des superutilisateurs
     (PostgreSQL)
    </strong></p>
    <div class="example-contents"><p>
     Dans l&#039;exemple suivant, l&#039;entrée de l&#039;utilisateur est directement interpolée dans la
     requête SQL, permettant à l&#039;attaquant d&#039;obtenir un compte superutilisateur dans la base de données.
</p></div>
    <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$offset </span><span style="color: #007700">= </span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #DD0000">'offset'</span><span style="color: #007700">]; </span><span style="color: #FF8000">// Attention, aucune validation!<br /></span><span style="color: #0000BB">$query  </span><span style="color: #007700">= </span><span style="color: #DD0000">"SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET </span><span style="color: #0000BB">$offset</span><span style="color: #DD0000">;"</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">pg_query</span><span style="color: #007700">(</span><span style="color: #0000BB">$conn</span><span style="color: #007700">, </span><span style="color: #0000BB">$query</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
     </div>

    </div>
    Un utilisateur normal clique sur les boutons &#039;suivant&#039; et &#039;précédent&#039;,
    qui sont alors placés dans la variable <var class="varname">$offset</var>,
    encodée dans l&#039;<abbr title="Uniform Resource Locator">URL</abbr>. Le script s&#039;attend à ce que la variable
    <var class="varname">$offset</var> soit alors un nombre. Cependant,
    il est possible de modifier l&#039;<abbr title="Uniform Resource Locator">URL</abbr> en ajoutant une nouvelle valeur,
    au format <abbr title="Uniform Resource Locator">URL</abbr>, comme ceci :
    <div class="informalexample">
     <div class="example-contents">
<div class="sqlcode"><pre class="sqlcode">0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select &#039;crack&#039;, usesysid, &#039;t&#039;,&#039;t&#039;,&#039;crack&#039;
    from pg_shadow where usename=&#039;postgres&#039;;
--</pre>
</div>
     </div>

    </div>
    Si cela arrive, le script donnerait un accès super utilisateur à l&#039;attaquant.
    Il est à noter que la valeur <code class="literal">0;</code> fournit un décalage valide à la requête
    originale et la termine correctement.
   </p>
   <blockquote class="note"><p><strong class="note">Note</strong>: 
    <p class="para">
     C&#039;est une technique répandue que de forcer l&#039;analyseur SQL à ignorer le
     reste de la requête, en utilisant les symboles <code class="literal">--</code> pour
     mettre en commentaires.
    </p>
   </p></blockquote>
   <p class="para">
    Un moyen disponible pour accéder aux mots de passe est de contourner
    la recherche de page. Ce que le pirate doit faire, c&#039;est simplement
    voir si une variable du formulaire est utilisée dans la requête, et
    si elle est mal gérée. Ces variables peuvent avoir été configurées
    dans une page précédente pour être utilisées dans les clauses
    <code class="literal">WHERE, ORDER BY, LIMIT</code> et <code class="literal">OFFSET</code> des
    requêtes <code class="literal">SELECT</code>. Si la base de données supporte
    les commandes <code class="literal">UNION</code>, le pirate peut essayer d&#039;ajouter
    une requête entière pour lister les mots de passe dans n&#039;importe quelle
    table. Utiliser la technique de stocker uniquement des hachages sécurisés
    des mots de passe au lieu des mots de passe eux-mêmes est fortement
    recommandé.
    <div class="example" id="example-2">
     <p><strong>Exemple #2 
      Liste d&#039;articles ... et quelques mots de passe (n&#039;importe quel serveur de bases de données)
     </strong></p>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$query  </span><span style="color: #007700">= </span><span style="color: #DD0000">"SELECT id, name, inserted, size FROM products<br />           WHERE size = '</span><span style="color: #0000BB">$size</span><span style="color: #DD0000">'"</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">odbc_exec</span><span style="color: #007700">(</span><span style="color: #0000BB">$conn</span><span style="color: #007700">, </span><span style="color: #0000BB">$query</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
    </div>

   </div>
   La partie statique de la requête, combinée avec une autre
   requête <code class="literal">SELECT</code>, va révéler les mots de passe :
   <div class="informalexample">
    <div class="example-contents">
<div class="sqlcode"><pre class="sqlcode">&#039;
union select &#039;1&#039;, concat(uname||&#039;-&#039;||passwd) as name, &#039;1971-01-01&#039;, &#039;0&#039; from usertable;
--</pre>
</div>
    </div>

   </div>
   </p>
   <p class="para">
    Les instructions <code class="literal">UPDATE</code> et <code class="literal">INSERT</code> sont également
    susceptibles à de telles attaques
    <div class="example" id="example-3">
     <p><strong>Exemple #3 Modifier un mot de passe ... et gain de droits ! (n&#039;importe quel serveur de bases de données)</strong></p>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$query </span><span style="color: #007700">= </span><span style="color: #DD0000">"UPDATE usertable SET pwd='</span><span style="color: #0000BB">$pwd</span><span style="color: #DD0000">' WHERE uid='</span><span style="color: #0000BB">$uid</span><span style="color: #DD0000">';"</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
    </div>

   </div>
   Un internaute fourbe peut envoyer une valeur telle que
   <code class="literal">&#039; or uid like&#039;%admin%</code> dans <var class="varname">$uid</var>
   pour modifier le mot de passe utilisateur, ou simplement, utiliser la
   variable <var class="varname">$pwd</var> avec la valeur
   <code class="literal">hehehe&#039;, trusted=100, admin=&#039;yes</code>
   pour obtenir des droits supplémentaires. La requête devient alors :
   <div class="informalexample">
    <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br /></span><span style="color: #FF8000">// $uid: ' or uid like '%admin%<br /></span><span style="color: #0000BB">$query </span><span style="color: #007700">= </span><span style="color: #DD0000">"UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';"</span><span style="color: #007700">;<br /><br /></span><span style="color: #FF8000">// $pwd: hehehe', trusted=100, admin='yes<br /></span><span style="color: #0000BB">$query </span><span style="color: #007700">= </span><span style="color: #DD0000">"UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE<br />...;"</span><span style="color: #007700">;<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
    </div>

   </div>
   </p>
   <p class="simpara">
    Bien qu&#039;il reste évident qu&#039;un attaquant doit posséder au moins
    certaines connaissances sur l&#039;architecture de la base de données pour mener une attaque réussie,
    obtenir ces informations est souvent très simple. Par exemple,
    le code peut faire partie d&#039;un logiciel open-source et être disponible publiquement.
    Ces informations peuvent également être divulguées par un code source fermé -
    même s&#039;il est codé, obscurci ou compilé - et même par le code de l&#039;application à travers l&#039;affichage de messages d&#039;erreur.
    D&#039;autres méthodes comprennent l&#039;utilisation de noms de table et de colonne typiques.
    Par exemple, un formulaire de connexion qui utilise une table &#039;users&#039; avec des noms de colonnes
    &#039;id&#039;, &#039;username&#039; et &#039;password&#039;.
   </p>
   <p class="para">
    <div class="example" id="example-4">
     <p><strong>Exemple #4 Attaque du système d&#039;exploitation de l&#039;hôte de base de données (MSSQL Server)</strong></p>
      <div class="example-contents"><p>
       Un exemple effrayant de la manière dont des commandes de niveau système d&#039;exploitation
       peuvent être accessibles sur certains hôtes de base de données.
      </p></div>
     <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br />$query  </span><span style="color: #007700">= </span><span style="color: #DD0000">"SELECT * FROM products WHERE id LIKE '%</span><span style="color: #0000BB">$prod</span><span style="color: #DD0000">%'"</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">mssql_query</span><span style="color: #007700">(</span><span style="color: #0000BB">$query</span><span style="color: #007700">);<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
    </div>

   </div>
   Si le pirate injecte la valeur
   <code class="literal">a%&#039; exec master..xp_cmdshell &#039;net user test testpass /ADD&#039;  --</code>
   dans la variable <var class="varname">$prod</var>, alors la requête
   <var class="varname">$query</var> devient :
   <div class="informalexample">
    <div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /><br />$query  </span><span style="color: #007700">= </span><span style="color: #DD0000">"SELECT * FROM products<br />           WHERE id LIKE '%a%'<br />           exec master..xp_cmdshell 'net user test testpass /ADD' --%'"</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">mssql_query</span><span style="color: #007700">(</span><span style="color: #0000BB">$query</span><span style="color: #007700">);<br /><br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
    </div>

   </div>
   MSSQL Server exécute les requêtes SQL en lot, y compris la commande
   d&#039;ajout d&#039;un nouvel utilisateur à la base de données locale. Si cette
   application fonctionnait en tant que <code class="literal">sa</code> et que le
   service MSSQLSERVER disposait d&#039;un niveau de droits suffisant, le pirate
   disposerait désormais d&#039;un compte avec accès au serveur.
   </p>
   <blockquote class="note"><p><strong class="note">Note</strong>: 
    <p class="para">
     Certains exemples ci-dessus sont spécifiques à certains serveurs de
     bases de données, mais cela n&#039;empêche pas des attaques similaires d&#039;être
     possibles sur d&#039;autres produits. La base de données sera alors
     vulnérable d&#039;une autre manière.
    </p>
   </p></blockquote>
   <p class="para">
    <div class="mediaobject">
     
     <div class="imageobject">
      <img src="images/fa7c5b5f326e3c4a6cc9db19e7edbaf0-xkcd-bobby-tables.png" alt="Un exemple amusant concernant l'injection SQL" width="666" height="205" />
     </div>
     <div class="caption">
      <p class="simpara">
       Image de <a href="http://xkcd.com/327" class="link external">&raquo;&nbsp;xkcd</a>
      </p>
     </div>
    </div>
   </p>
   
   <div class="sect2" id="security.database.avoiding">
    <h3 class="title">Techniques de contournement</h3>
    <p class="para">
     La méthode recommandée pour éviter les injections SQL est de lier toutes les données
     via des requêtes préparées. L&#039;utilisation de requêtes paramétrées n&#039;est pas suffisante
     pour éviter complètement les injections SQL, mais c&#039;est le moyen le plus facile et le plus sûr
     de fournir une entrée aux instructions SQL. Toutes les valeurs littérales dynamiques dans les clauses
     <code class="literal">WHERE</code>, <code class="literal">SET</code> et <code class="literal">VALUES</code> doivent être remplacées
     par des espaces réservés. Les données réelles seront liées pendant
     l&#039;exécution et envoyées séparément de la commande SQL.
</p>
<p class="para">
La liaison de paramètres ne peut être utilisée que pour les données. Les autres parties dynamiques de la requête SQL doivent être filtrées contre une liste connue de valeurs autorisées.
</p>
<p class="para">
<div class="example" id="example-5">
<p><strong>Exemple #5 Éviter les injections SQL en utilisant des requêtes préparées PDO</strong></p>
<div class="example-contents">
<div class="phpcode"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /> </span><span style="color: #FF8000">// La partie SQL dynamique est validée par rapport aux valeurs attendues<br /> </span><span style="color: #0000BB">$sortingOrder </span><span style="color: #007700">= </span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #DD0000">'sortingOrder'</span><span style="color: #007700">] === </span><span style="color: #DD0000">'DESC' </span><span style="color: #007700">? </span><span style="color: #DD0000">'DESC' </span><span style="color: #007700">: </span><span style="color: #DD0000">'ASC'</span><span style="color: #007700">;<br /> </span><span style="color: #0000BB">$productId </span><span style="color: #007700">= </span><span style="color: #0000BB">$_GET</span><span style="color: #007700">[</span><span style="color: #DD0000">'productId'</span><span style="color: #007700">];<br /> </span><span style="color: #FF8000">// Le SQL est préparé avec un espace réservé<br /> </span><span style="color: #0000BB">$stmt </span><span style="color: #007700">= </span><span style="color: #0000BB">$pdo</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">prepare</span><span style="color: #007700">(</span><span style="color: #DD0000">"SELECT * FROM products WHERE id LIKE ? ORDER BY price </span><span style="color: #007700">{</span><span style="color: #0000BB">$sortingOrder</span><span style="color: #007700">}</span><span style="color: #DD0000">"</span><span style="color: #007700">);<br /> </span><span style="color: #FF8000">// La valeur est fournie avec des caractères génériques LIKE<br /> </span><span style="color: #0000BB">$stmt</span><span style="color: #007700">-&gt;</span><span style="color: #0000BB">execute</span><span style="color: #007700">([</span><span style="color: #DD0000">"%</span><span style="color: #007700">{</span><span style="color: #0000BB">$productId</span><span style="color: #007700">}</span><span style="color: #DD0000">%"</span><span style="color: #007700">]);<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div>
 </div>

      </div>
     </p>
      <p class="simpara">
  Les instructions préparées sont fournies
  <a href="pdo.prepared-statements.php" class="link">par PDO</a>,
  <a href="mysqli.quickstart.prepared-statements.php" class="link">par MySQLi</a>,
  et par d&#039;autres bibliothèques de bases de données.
 </p>

 <p class="simpara">
  Les attaques par injection SQL sont principalement basées sur l&#039;exploitation du code qui n&#039;a pas été écrit
  en tenant compte de la sécurité. Il ne faut jamais faire confiance à une entrée, en particulier
  côté client, même si elle provient d&#039;une boîte de sélection,
  d&#039;un champ de saisie masqué ou d&#039;un cookie. Le premier exemple montre qu&#039;une telle requête simple peut causer des désastres.
 </p>

 <p class="para">
  Une stratégie de défense en profondeur implique plusieurs bonnes pratiques de codage :
  <ul class="itemizedlist">
   <li class="listitem">
    <span class="simpara">
     Il ne faut jamais se connecter à la base de données en tant que superutilisateur ou en tant que propriétaire de la base de données.
     Il convient de toujours utiliser des utilisateurs personnalisés avec des privilèges minimaux.
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Il faut vérifier si l&#039;entrée donnée a le type de données attendu. <abbr title="PHP: Hypertext Preprocessor">PHP</abbr> a
     une large gamme de fonctions de validation d&#039;entrée, des plus simples
     trouvées dans <a href="ref.var.php" class="link">Fonctions de variables</a> et
     dans <a href="ref.ctype.php" class="link">Fonctions de type de caractères</a>
     (par exemple <span class="function"><a href="function.is-numeric.php" class="function">is_numeric()</a></span>, <span class="function"><a href="function.ctype-digit.php" class="function">ctype_digit()</a></span>
     respectivement) et jusqu&#039;à la
     prise en charge des <a href="ref.pcre.php" class="link">Expressions régulières compatibles avec Perl</a>.
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Si l&#039;application s&#039;attend à une entrée numérique, il convient de vérifier les données
     avec <span class="function"><a href="function.ctype-digit.php" class="function">ctype_digit()</a></span>, de changer silencieusement son type
     en utilisant <span class="function"><a href="function.settype.php" class="function">settype()</a></span>, ou d&#039;utiliser sa représentation numérique
     avec <span class="function"><a href="function.sprintf.php" class="function">sprintf()</a></span>.
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Si la couche de base de données ne prend pas en charge la liaison de variables, alors
     il faut mettre chaque valeur fournie par l&#039;utilisateur non numérique entre guillemets avec la fonction d&#039;échappement de chaîne spécifique à la base de données (par exemple
     <span class="function"><a href="function.mysql-real-escape-string.php" class="function">mysql_real_escape_string()</a></span>,
     <span class="function"><strong>sqlite_escape_string()</strong></span>, etc.).
     Les fonctions génériques comme <span class="function"><a href="function.addslashes.php" class="function">addslashes()</a></span> ne sont utiles que dans un environnement très spécifique (par exemple MySQL dans un ensemble de caractères à octets uniques avec <var class="varname">NO_BACKSLASH_ESCAPES</var> désactivé), il est donc
     préférable de les éviter.
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Il est particulièrement important de ne pas divulguer d&#039;informations spécifiques à la base de données,
     en particulier sur le schéma, que ce soit de manière légale ou illégale.
     Se référer également à <a href="security.errors.php" class="link">Rapport d&#039;erreurs</a> et
     <a href="ref.errorfunc.php" class="link">Fonctions de gestion et de journalisation des erreurs</a>.
     </span>
       </li>
      </ul>
     </p>

   <p class="simpara">
    À côté de ces conseils, il est recommandé d&#039;enregistrer les requêtes, soit
    dans les scripts, soit dans la base elle-même, si elle le supporte.
    Évidemment, cet enregistrement ne sera pas capable d&#039;empêcher une attaque,
    mais permettra de retrouver la requête qui a fauté. L&#039;historique
    n&#039;est pas très utile par lui-même mais au niveau des informations qu&#039;il
    contient. Plus il y a de détails, mieux c&#039;est.
   </p>
   </div>
 </div><?php manual_footer($setup); ?>