Mini-cours de PHP - MySQL

Partie VI

Ce chapitre est consacré à la sécurité.

À la fin de ce chapitre, vous ne serez pas un pro de la sécurité Web, mais vous saurez comment éviter les plus grosses failles de sécurité.

Authentification HTTP

Attention, le nom d'utilisateur et le mot passe sont envoyés au serveur sans être cryptés. Seul le protocole https crypte les données envoyées au serveur.

.htaccess / .htpasswd

Même sans PHP, il est facile de protéger tout le contenu d'un dossier situé sur un serveur Apache. (contrairement aux sessions PHP).

Pour plus d'info, cliquez ici.

$_SERVER

Ici, l'appel du fichier est réservé aux personnes autorisées.

Vous connaissez les super-variables $_GET et $_POST, voici $_SERVER.

S'affichera une fenêtre d'authentification : mesCodes.php

Commencez par cliquer sur le bouton "Annuler" pour voir le message. Maintenant, tapez ce que vous voulez ou rien du tout. Aucune vérification ne sera faite. Le script se contente de réécrire les codes que vous avez tapé (preuve qu'il les a bien captés).

<?php

if (!isset($_SERVER['PHP_AUTH_USER'])) {
  header('WWW-Authenticate: Basic realm="My Realm"');
  header('HTTP/1.0 401 Unauthorized');
  echo 'Texte utilisé si le visiteur utilise le bouton d\'annulation';
  exit;
}

echo "<p>Bonjour, {$_SERVER['PHP_AUTH_USER']}.</p>";
echo "<p>Votre mot de passe est {$_SERVER['PHP_AUTH_PW']}.</p>";

?>

Remarquez qu'il n'existe pas de else car il existe un exit; dans le if. Notez aussi que exit; == exit();.

Via la fonction header(), il est également possible d'afficher la même demande de codes d'authentification.

Ce code dit : Si l'internaute ne s'est pas encore identifié sur ce site web, afficher la fenêtre demandant les codes. Sinon afficher "Bonjour, ..."

Après avoir cliqué sur le bouton "Valider", les codes tapés sont enregistrés dans le tableau associatif $_SERVER sous les clés nommées : PHP_AUTH_USER et PHP_AUTH_PW. Puis, la page est à nouveau appelée (automatiquement par le navigateur)

Si vous cliquez sur le bouton "Valider", vous êtes identifié ! Même si les champs sont vides. Notez bien que cela n'implique que l'identification soit bonne.

On constate qu'il est facile de personnaliser le message à afficher si l'internaute clique sur le bouton "Annuler".

Si l'identification est réussie, le navigateur vous proposera d'enregistrer vos codes pour vous éviter de devoir les retaper. Attention, si votre ordinateur est partagé ou volé, un autre pourrait alors se faire passer pour vous. Refuser l'enregistrement de vos codes d'authentification, si le site est sensible.

Si vos codes d'authentification sur ce site web n'ont pas été enregistrés,
pour les effacer, il faut fermer le navigateur web.

Pour effacer des codes d'authentification enregistrés, il faut aller dans les paramètres avancés de votre navigateur web et faire les modifications ad hoc.

$_SERVER.


Dans ce nouvel exemple, il faudra taper, comme premier code, : login1 ou login2.

<?php

$logins=array("login1","login2");
$trouve=false;

if(isset($_SERVER['PHP_AUTH_USER'])){
  foreach($logins as $login){
    if ($_SERVER['PHP_AUTH_USER']==$login) {
      $trouve=true; break;
    }
  }
}

if(!$trouve){
  header('WWW-Authenticate: Basic realm="My Realm"');
  header('HTTP/1.0 401 Unauthorized');
  echo 'Veuillez cliquer sur le bouton "Page précédente" de votre navigateur';
  exit;
}

echo 'Bienvenue dans votre espace sécurisé';

?>

Ce code place les bons codes dans un tableau appelé $logins. Si l'utilisateur est identifié et que son premier code correspond à une valeur du tableau, alors l'authentification a réussi.

Remarquez que le code commence par mettre la variable indiquant la réussite d'authentification à false. En cas de réussite, la valeur de cette variable passe à true. Après la section de code consacrée au contrôle, on utilise un if sur cette variable pour savoir la suite à réserver.

Remarquez qu'exceptionnellement, ici, aucun contrôle n'est effectué sur le mot de passe. Vous pouvez donc taper ce que vous voulez (ou rien).

identification.php

Après vous êtes identifié, cliquez à nouveau sur ce lien : mesCodes.php. Vous constaterez que vos codes ne sont plus demandés.

Toutes les pages d'un espace sécurisé doivent être ... sécurisées. Au lieu de recopier le code lié à la sécurisation dans chaque fichier PHP, il suffit de placer ce code dans un fichier, puis inclure le fichier ainsi créé dans tous les fichiers PHP, via la fonction include().

Il est de coutume de préfixer les fichiers destinés à être inclus par : inc_.

<?php

require("inc_identification.php");

echo "Bienvenue sur la page sécurisée n° 1";

?>

La fonction require() a déjà été vue (jour2)

Page sécurisée 1, Page sécurisée 2, ...


Maintenant que vous avez saisi le principe de fonctionnement de cette fenêtre d'authentification affichée par le navigateur web, nous allons adapter le code pour le rendre compatible à une base de données contenant la liste des personnes autorisées. Par exemple, pour créer un espace sécurisé pour les membres de votre association.

Code SQL :

SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = '+00:00';

DROP TABLE IF EXISTS `membres`;

CREATE TABLE `membres` (
  `id` smallint(5) UNSIGNED NOT NULL,
  `login` varchar(20) NOT NULL,
  `pwd` char(40) NOT NULL,
  `statut` tinyint(3) UNSIGNED NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `membres` (`id`, `login`, `pwd`, `statut`) VALUES
(1, 'tintin', 'a3a2ff28ca1a7ae936ce38c5b7af4b8c9d9804c7', 0),
(2, 'toi', '55cbe7fd00627a28668d1d7c9899bdb602dad69d', 1),
(3, 'admin', 'd033e22ae348aeb5660fc2140aec35850c4da997', 50);

ALTER TABLE `membres`
  ADD PRIMARY KEY (`id`),
  ADD UNIQUE KEY `loginUnique` (`login`),
  ADD UNIQUE KEY `pwd` (`pwd`) USING BTREE;

ALTER TABLE `membres`
  MODIFY `id` smallint(5) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;

COMMIT;

Le contenu du champ pwd de la table est surprenant.
a3a2ff28ca1a7ae936ce38c5b7af4b8c9d9804c7 correspond à SHA1("milou")
et 55cbe7fd00627a28668d1d7c9899bdb602dad69d = SHA1("moi")
Ainsi, l'utilisateur tape son mot de passe, mais c'est le résultat de la fonction SHA1() sur son mot de passe qui est enregistré dans la base de données. Ainsi, les mots de passe ne sont donc pas connus du gestionnaire de la base, de l'hébergeur du site (ni de la police). Et, donc si la base de données est piratée, le hacker ne connaîtra pas le mot de passe des membres ...

Il ne faut jamais enregistrer des mots de passe en clair.

<?php

require("maConnexion.php");
$trouve=false;

if(isset($_SERVER['PHP_AUTH_USER'])){

  /* requête paramétrée (pour contrer une attaque par injection SQL)
  Ici, la valeur de $_SERVER['PHP_AUTH_PW'] est le paramètre d'une fonction
  - SHA1() - qui transforme considérablement la valeur fournie par l'utilisateur.
  Ici, la requête préparée est utile, car le champ "login" n'est pas
  lié à une fonction transformatrice */
  $sql="SELECT COUNT(*) AS nb FROM membres WHERE login=:pseudo AND pwd=SHA1(:pwd);";

  /* Attention, il n'y a pas de gestion d'erreur !
  La moindre erreur provoque l'accès à l'espace sécurisé !!! */

  $rep=$bdd->prepare($sql);
  $rep->execute(array('pseudo' => $_SERVER['PHP_AUTH_USER'], 'pwd' => $_SERVER['PHP_AUTH_PW'] ));

  //echo $rep->queryString."<br>"; // affiche la requête préparée

  $ligne=$rep->fetch();
  if ($ligne['nb']!=0) $trouve=true;

}

if(!$trouve){
  header('WWW-Authenticate: Basic realm="My Realm"');
  header('HTTP/1.0 401 Unauthorized');
  echo 'Veuillez cliquer sur le bouton "Page précédente" de votre navigateur';
  exit;
}

echo "Bienvenu ".$_SERVER['PHP_AUTH_USER'].". Vous êtes dans votre espace sécurisé.";

?>

Ce code peut se lire ainsi. Connecte-toi à la DB et met à false la réussite de l'authentification. Si le code d'authentification USER existe, vérifie si les codes d'authentification correspondent à ceux de la DB. Si oui, met à true la réussite de l'authentification. Si la réussite d'authentification est false, afficher la fenêtre d'authentification, sinon afficher un message de bienvenue.

$sql="SELECT COUNT(*) AS nb FROM membres WHERE login=:pseudo AND pwd=SHA1(:pwd);";

Dans ce code, on constate que "pseudo" et "pwd" sont précédés par : pour indiquer le nom d'une clé (d'un tableau associatif). La requête est alors dite préparée (ou paramétrée)

$rep=$bdd->prepare($sql);

La requête préparée devient ensuite le paramètre de la fonction prepare() (et non query()). Ensuite, est "exécuté" un tableau qui contient les vraies valeurs, via execute(), qui remplacera chaque clé par sa valeur.

Pour contrer les attaques par injection SQL, il faut créer une requête préparée.

Un exemple d'attaque par injection SQL sera vu bientôt.

La propriété queryString contient la requête avant le remplacement des clés par les valeurs correspondantes. Cette propriété de l'objet, appelé ici $rep, n'est utilisée pratiquement que pour corriger le code. Via echo, on peut l'afficher. Évidemment, une fois la page publiée, une telle instruction doit être commentée.

  $ligne=$rep->fetch();
  if ($ligne['nb']==1) $trouve=true;

$ligne contient l'unique enregistrement renvoyé par le moteur de la DB. Cet enregistrement contient un nom de colonne - appelé nb (et défini dans la requête SQL). Si le contenu de ce champ vaut 1, cela signifie que les codes d'authentification donnés par l'internaute existent dans la base données. Donc, l'authentification a réussi ($trouve=true;).

if ($ligne['nb']!=0) $trouve=true; => Ce code est moins robuste aux attaques. En effet, si vous avez oublié de créer une requête paramétrée ou préparée, ==1 vous protégera beaucoup mieux que !=0

Regarder le code SQL, ci-dessus, pour trouver les bons codes d'authentification.

Identifiez-vous

Injection SQL : ("), (') et (#)

L'injection est le premier risque de sécurité des applications Web. Les attaques par injection, en particulier l'injection SQL, sont très dangereuses. Ces attaques peuvent entraîner un vol et/ou une perte de données et aller jusqu'à la prise de contrôle complète du système.

Vous savez déjà qu'il faut se méfier du guillemet (").

Ce type d'attaque est inconnue des débutants.

Fichier PHP mal codé :

        <?php

        // --- récupération des données
        $login = $_POST['login'];
        $pwd = $_POST['pwd'];

        // --- connexion à la DB
        require("maConnexion.php");

        function affich($rep,$login,$pwd){
          if ($rep===FALSE) echo("<p>Erreur dans la requête</p>");
          else{
            $ligne=$rep->fetch();
            echo $ligne[0]." --- ".$ligne[1]."<br>";

            if (strlen($ligne[0])>0) {
              echo "<p>Bienvenue dans l'espace sécurisé de ".$ligne[0]."</p>";
              if($pwd!=$ligne[1]) echo"<p class=\"red\">".$ligne[0]
              ." vient de se faire pirater !<br>Je peux me faire passer pour ".$ligne[0]." !</p>";
            }else{
            echo "<p>Mauvais codes</p>";
            }
          }
        }

        echo "<h2>Via une requête non sécurisée</h2>";

        // Requête non sécurisée
        $sql="SELECT login, pwd FROM table2 WHERE login='".$login."' AND pwd='".$pwd."';";
        echo $sql."<br>";
        $rep = $bdd->query($sql);
        affich($rep,$login,$pwd);

        echo "<h2>Via une requête sécurisée</h2>";

        // --- requête préparée
        $rep = $bdd->prepare("SELECT login, pwd FROM table2 WHERE login=? AND pwd=?;");
        $rep->execute(array($login, $pwd));
        echo $rep->queryString."<br>";
        affich($rep,$login,$pwd)

        ?>

Un code qui fonctionne bien peut être vulnérable.

Ce qui pose un problème de sécurité est la chaîne de caractères :

SELECT login, pwd FROM table2 WHERE login='".$login."' AND pwd='".$pwd."';"

En effet, si la valeur de $pwd vaut ' OR '1=1, alors après concaténation, la chaîne sera :

SELECT login, pwd FROM table2 WHERE login='".$login."' AND pwd='' OR '1=1';"

Or, comme la condition '1=1' est toujours vraie, cette requête enverra comme résultat, la première ligne de la table.

Il est donc plus prudent qu'un administrateur du site ne soit pas le premier enregistrement de la table.

Pour tester une attaque par injection SQL, cliquez ici.

Évitez comme dernier caractère d'un mot de passe un signe de ponctuation, car, si vous notez/imprimez votre mot de passe, vous risquerez de le confondre avec un signe de ponctuation et de ne pas l'utiliser dans le champ "mot de passe" ...

Il est possible de se prémunir des attaques par injection SQL via des requêtes préparées (ou via des requêtes paramétrées). Pour plus d'info sur les requêtes préparées, cliquez ici.

// Requête SQL sécurisée
$req = $bdd->prepare("SELECT * FROM table2 WHERE login= ? AND password= ?");
$req->execute(array($login, $password));

Plus d'info sur l'attaque par injection SQL

$_SESSION

Au niveau des requêtes SQL, la donnée la plus intéressante sur un membre est son ID. Nous devons sécuriser chaque page. Et, pour récupérer son ID, nous devrions ré-interroger la base de données via ses identifiants - dont la valeur est stockée dans $_SERVER['PHP_AUTH_USER'] et $_SERVER['PHP_AUTH_PW'] - quasiment à chaque page de son espace. C'est ici qu'entre en jeu les variables de session.

Pour éviter de ré-interroger la base de données, l'ID du membre est stocké dans une variable de session, lors de son identification réussie. Puis, l'existence de cette variable sera vérifiée au début de chaque page.

identification2.php (code complet) :

<?php session_start();

require("maConnexion.php");
$trouve=false;

if(isset($_SERVER['PHP_AUTH_USER'])){

  $sql="SELECT COUNT(*) AS nb, id, statut FROM membres WHERE login=? AND pwd=SHA1(?);";
  $rep=$bdd->prepare($sql);
  $rep->execute(array( $_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'] ));
  $ligne=$rep->fetch();
  if ($ligne['nb']==1) {
    $trouve=true;
    $_SESSION['id']=$ligne['id'];
    $_SESSION['statut']=$ligne['statut'];
  }

}

if(!$trouve){
  header('WWW-Authenticate: Basic realm="My Realm"');
  header('HTTP/1.0 401 Unauthorized');
  echo 'Veuillez cliquer sur le bouton "Page précédente" de votre navigateur'; exit;
}

header("Location:page1.php");

?>

Début de chaque fichier PHP affichant des informations réservée aux membres :

<?php
session_start();// aucune instruction (y compris HTML) avant celle-ci ne sera tolérée par l'interprétateur PHP
if (!isset($_SESSION['id'])){ header("Location:identification2.php"); exit(); }
?>

Ce code dit : Si la variable de session n'existe pas, aller sur la page d'identification.

page1.php (code complet) :

<?php
session_start();
if (!isset($_SESSION['id'])){ header("Location:identification2.php"); exit(); }
if ($_SESSION['statut']<1) exit("Vous n'avez pas le niveau requis.");
?>
Pour retourner au mini-cours PHP-MySQL, cliquez <a href="../jour6.htm#listMembres">ici</a>.<br>
Bienvenue dans la zone sécurisée.<br>

Pour accéder à la zone réservée aux membres,
 cliquez <a href='page2.php'>ici</a>.<br>

<?php if ($_SESSION['statut']<50) exit(); ?>
Pour accéder à la zone réservée aux administrateurs,
 cliquez <a href='page3.php'>ici</a>.

Il est recommandé que le premier enregistrement de la table membres concerne un membre "bidon" (ayant un statut=0)

page2.php (code complet) :

<?php
session_start();
if (!isset($_SESSION['id'])){ header("Location:identification2.php"); exit(); }
if ($_SESSION['statut']<1) exit("Vous n'avez pas le niveau requis.");
?>

Bienvenue dans la zone réservée aux membres.<br />
<a href="page1.php">Zone sécurisée</a>

N'oubliez pas qu'une fois autorisé, un membre peut se transformer en hacker.

page3.php (code complet) :

<?php
session_start();
if (!isset($_SESSION['id'])){ header("Location:identification2.php"); exit(); }
if ($_SESSION['statut']<50) exit("Vous n'avez pas le niveau requis.");
?>

Bienvenue dans la zone réservée aux administrateurs.<br />
<a href="listMembres.php">Liste des membres</a>
<a href="page2.php">Zone réservée aux membres</a>

listMembres.php (code complet) :

<?php
session_start();
if (!isset($_SESSION['id'])){ header("Location:identification2.php"); exit(); }
if ($_SESSION['statut']<50) exit("Vous n'avez pas le niveau requis.");
?>
<!DOCTYPE html>
<html lang="fr-BE" xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr-BE">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex, nofollow" />
    <meta name="description" content="" />
    <meta name="keywords" content="" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" type="text/css" href="../../../cours4.css" />
    <link rel="stylesheet" type="text/css" href="tableauAngular.css" />
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js">
</script>
    <title>
      Liste des membres
    </title>
  </head>
  <body>
    <header>
      <p>
        <a href="page3.php">Zone des administrateurs</a>
      </p>
      <p id="titre">
        Liste des membres
      </p>
    </header>
    <article>
      <div data-ng-app="myApp" data-ng-controller="myCtrl">
        <table border="1">
          <tr>
          <th data-ng-click="sortBy('a')">
              ID <span class="sortorder" data-ng-show="propertyName === 'a'" data-ng-class=
              "{reverse: reverse}"> </span>
            </th>
            <th data-ng-click="sortBy('b')">
              Nom d'utilisateur <span class="sortorder" data-ng-show="propertyName === 'b'" data-ng-class=
              "{reverse: reverse}"> </span>
            </th>
            <th data-ng-click="sortBy('c')">
              Mot de passe <span class="sortorder" data-ng-show="propertyName === 'c'" data-ng-class=
              "{reverse: reverse}"> </span>
            </th>
            <th data-ng-click="sortBy('d')">
              Statut <span class="sortorder" data-ng-show="propertyName === 'd'" data-ng-class=
              "{reverse: reverse}"> </span>
            </th>
          </tr><!-- Placement des enregistrements dans le tableau HTML -->
          <tr data-ng-repeat="membre in membres | orderBy:propertyName:reverse">
            <td>
              {{membre.id}}
            </td>
            <td>
              {{membre.login}}
            </td>
            <td>
              {{membre.pwd}}
            </td>
            <td>
              {{membre.statut}}
            </td>
          </tr>
        </table>
      </div>
      <p class="note">
        NB : Les mots de passe sont inconnus, même du gestionnaire du site.
      </p>
    </article>
    <footer id="pied"></footer><script src="listMembres.js">
</script><script src="../../../adapter.js">
</script>
  </body>
</html>

listMembres.js (code complet) :

var app = angular.module("myApp", []);

app.controller("myCtrl", function($scope, $http) {

  $http.get("listMembresAsk.php")
    .then(function(response) {
    $scope.membres = response.data;
  });

  $scope.propertyName = 'a'; // nom d'un champ = tri initial
  $scope.reverse = false;  // tri ascendant (du plus petit au plus grand)

  $scope.sortBy = function(propertyName) {
    $scope.reverse = ($scope.propertyName === propertyName) ? !$scope.reverse : false;
    $scope.propertyName = propertyName;
  };

});

listMembresAsk.php (code complet) :

<?php

  // Réponse JSON attendue de la part du JS appelant
  header("Content-Type: application/json");

  // Connexion à la DB
  require("maConnexion.php");

  // Création de la requête SQL
  $sql = 'SELECT * FROM membres ORDER BY id';

  // Soumission de la requête SQL
  $rep=$bdd->query($sql);

  // Réponse du présent fichier
  echo json_encode($rep->fetchAll(PDO::FETCH_ASSOC));

  // Fermeture de la connexion MySQL
  $bdd=null;

?>

tableauAngular.css (code complet) :

table{width:100%;}

table, th , td  {
  border: 1px solid grey;
  border-collapse: collapse;
  padding: 5px;
}
tr:nth-child(odd) {
  background-color: #A9A9A9;
}
tr:nth-child(even) {
  background-color: white;
}

.sortorder:after {
  content: '\25b2';   // BLACK UP-POINTING TRIANGLE
}
.sortorder.reverse:after {
  content: '\25bc';   // BLACK DOWN-POINTING TRIANGLE
}

Codes pour accéder à l'espace sécurisé :

tintin --- milou
toi --- moi
admin --- admin

Pour accéder aux pages 1, 2 et 3 de l'espace sécurisé, cliquez ici

Une fois sur la page2.php, copier l'URL de cette page dans le presse-papier. Fermer le navigateur web. Relancez le navigateur et coller l'URL dans la barre des URL du navigateur pour accéder directement à la page 2. Vous constaterez qu'une identification est demandée, car la variable de session n'existe pas.

Ici, le nombre de champ de la table membres et le code PHP est minimaliste. D'autres champs pourraient exister la table, tels que email, prenom, ... Après une identification réussie, d'autres variables de session pourraient être créées pour éviter de ré-interroger la base de données sans cesse.

Le champ statut est utilisé pour distinguer un simple membre d'un administrateur de l'association. Depuis la page centrale, des liens supplémentaires vers des pages PHP pourraient apparaître pour permettre aux administrateurs de lister les membres, ajouter ou supprimer un membre, ...

Vérifications coté serveur

Outre l'authentification du visiteur, il faut aussi veiller à l'intégrité des données injectées dans la base de données. Surtout, lors des opérations INSERT, UPDATE et même DELETE.

Par exemple, on ne peut pas supprimer un membre sans supprimer préalablement tout ce qui lui est relié. Autrement, il pourrait, par exemple, être publié des messages devenus sans auteur ...

Toute donnée reçue doit être re-vérifiée coté serveur, car une personne mal-intentionnée peut créer SON fichier qui appellera VOTRE fichier.

En effet, le code source d'une page web est accessible et donc celui de votre formulaire. Il peut alors facilement connaître l'adresse de votre fichier PHP qui traite les informations envoyées.
<form ... action="traitement.php" ... >

Il lui est aussi possible de faire un copier/coller de votre code-source, puis de supprimer la validation HTML et JS des données ...

Pour contrer les malfaisants, il faut donc retranscrire toute la validation HTML et JS en PHP !

Malgré le fait que tous les contrôles existent côté serveur, il faut les maintenir côté client Pour deux raisons, au moins.

La première est qu'il faut protéger les serveurs, les autoroutes de l'information, économiser l'électricité, ... (cfr GreenIT). Inutile d'envoyer une requête au serveur si on sait qu'elle sera rejetée. Ainsi, via la validation HTML et JS, 99 % de requêtes inutiles ne seront même pas envoyées au serveur.

La seconde raison est qu'il convient de ménager la susceptibilité de vos visiteurs. Un refus émanant d'un serveur fruste le visiteur. Lorsqu'un champ devient rouge, votre visiteur sait qu'il a commis une erreur. Mais, une fois qu'il a réussi à l'envoyer, il supporte difficilement qu'elle soit refusée sans de bons motifs.

D'autre part, la structure de la base de données doit être parfaite et donc être la plus stricte possible. Les requêtes d'insertion, de modification et de suppression seront donc rejetées si elles violent les contraintes fixées par le gestionnaire de la base de données.

Votre code PHP doit tenir compte du fait qu'une requête puisse être rejetée. Généralement, on informe l'utilisateur qu'il y a eu un problème. Si votre code PHP est bien fait, il présente à cet utilisateur des excuses et des informations complémentaires telles que le fichier PHP où est survenu l'erreur, le nom de la fonction en cours, les éventuels paramètres de la fonction, le code d'erreur, ... Ou mieux, si votre code est très bien fait, ces informations vous seront automatiquement envoyées par mail().

Dans un champ de formulaire, ces 5 caractères - ("), ('), (&), (<), (>) - sont si dangereux que la fonction PHP suivante a été créée.

htmlspecialchars()

Pour éviter l'injection de code, par remplacement de caractères spéciaux en entités HTML, il faut utiliser la fonction htmlspecialchars().

$prenom = htmlspecialchars($_POST['prenom']);

Pour tester les conséquences de cette absence de contrôle, lors de la récupération des données, cliquez ici.

Plus d'info sur htmlspecialchars()

Problème d'affichage : (<)

Sans avoir de mauvaises intentions, un utilisateur pourrait dans une zone de texte (textarea), utiliser le caractère (<).

Le caractère (<) pose problème lors de l'affichage, car il est utilisé pour indiquer au navigateur le début d'une balise.

        <?php

        // --- récupération de la donnée
        $phrase=$_POST['phrase'];

        // --- affichage de la donnée
        echo "<p>".$phrase."</p>";
        ?>

Pour tester les conséquences liées à ce caractère spécial, cliquez ici.

Les fonctions isset() et htmlspecialchars() devraient être utilisées quasi systématiquement.

Plus d'info sur la sécurité Web