Depuis plus de 15 ans, de nombreux passionnés peuvent développer facilement des sites web dynamiques, voire des systèmes d'information. Pour leurs développements, ces passionnés s'appuient sur des produits de types EASYPHP qui proposent un serveur web (Apache), une base de données (MySQL) relationnelle et un langage (PHP) permettant de transformer le serveur web en serveur d'applications.
Malheureusement nombreux sont les développeurs qui mélangent dans une même page du code HTML, PHP et SQL. Plus précisément, le développeur injecte dans du code SQL des données que l'utilisateur envoie au serveur via son navigateur en partant du principe que ces données ne sont en aucun cas des instructions SQL. C'est dans ce cas que l'on est confronté à une possible attaque par SQL Injection.
Un des remèdes serait donc de séparer le code qui manipule les données (SQL) de celui qui manipule les données en provenance de l'utilisateur. C'est ce que je vais illustrer à travers un exemple sur un serveur de type EASYPHP.
L'exemple que j'ai choisi est celui d'une authentification par login/password. Les données d'authentification sont évidemment stockées dans une table de la base.
La base de données s'appelle user et la table dans laquelle sont stockées les données d'authentification s'appelle utilisateur.
Voici les scripts de création et de peuplement de la table
Code sql : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | -- -- Structure de la table `utilisateur` -- CREATE TABLE `utilisateur` ( `id` int(11) NOT NULL, `nom` varchar(255) NOT NULL, `prenom` varchar(255) NOT NULL, `pwd` varchar(255) NOT NULL, `login` varchar(255) NOT NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1; -- -- Contenu de la table `utilisateur` -- INSERT INTO `utilisateur` (`id`, `nom`, `prenom`, `pwd`, `login`) VALUES (1, 'rambo', 'john', '1234', 'john.rambo'), (2, 'balboa', 'rocky', '5678', 'rocky.balboa'); |
L'utilisateur entrera son login et son mot de passe dans un formulaire HTML5 puis le serveur devra vérifier si les données sont correctes.
Voici la page qui contient le formulaire :
Code html : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!doctype html> <html> <head> </head> <body> <h1> Pas de SQL Injection </h1> <form action="connexion.php" method="post"> <p> <label> Identifiant : </label><input type="text" name="login" /><br/><br/> <label>Mot de passe : </label><input type="password" name="pwd" /><br/><br/> <input type="submit" value="Valider" /> </p> </form> </body> </html> |
Et voici la page PHP en charge de l'authentification de l'utilisateur :
Code html : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!doctype html> <html> <head> </head> <body> <h1> connexion </h1> <?php include_once("Utilisateur.php"); $user = new Utilisateur($_POST["login"]); if ($_POST['pwd'] == $user->getPwd()) { echo ' <p>bonjour '. $user->getPrenom() . ' ' . $user->getNom() . '<p>'; } else { echo '<p>Mot de passe incorrect</p>'; echo '<p>entrer un login et un mot de passe</p>'; } ?> </body> </html> |
On remarque alors qu'il ne s'agit que d'une comparaison de deux chaînes de caractères sans aucun code SQL apparent à l'horizon. En réalité, j'ai fait appel au concept objet pour exécuter mon code SQL dans une classe écrite dans le fichier utilisateur.php.
Le code PHP de notre classe Utilisateur est le suivant :
Code php : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <?php Class Utilisateur { private $login; private $pwd; private $prenom; private $nom; public function __construct($param) { try { $bdd = new PDO('mysql:host=localhost;dbname=user;charset=utf8', 'user', 'user'); } catch (Exception $e) { die('Erreur : ' . $e->getMessage()); } $utilisateurs = $bdd->query("Select * from utilisateur"); while ($user = $utilisateurs->fetch()){ if ($user["login"] == $param){ $this->pwd = $user["pwd"]; $this->login = $param; $this->prenom = $user["prenom"]; $this->nom = $user["nom"]; } } } public function getLogin() { return $this->login; } public function getPwd(){ return $this->pwd; } public function getNom() { return $this->nom; } public function getPrenom() { return $this->prenom; } } ?> |
Cette classe permet, entre autres de masquer, les opérations SQL et ainsi de se protéger de tout SQL Injection. Le développement objet s'avère donc ici vertueux et simple.
Bien entendu, de nombreux frameworks PHP, voire de simples bibliothèques existent pour faire de l'authentification et de la gestion de droits facilement et de façon beaucoup plus complète. Mais ce billet permettra d'expliquer ce qui peut se cacher derrière ces frameworks.
J'ai choisi le PHP pour illustrer mon propos, car la majorité des développeurs de sites passionnés sont en général autour du stack PHP. Néanmoins, les solutions à cette problématique de SQL Injection sont de la même manière, applicables au développement avec JavaScript Java ou C#.
Attention si l'objet de ce billet est de vous sensibiliser au SQL Injection il ne traite en aucun cas des autres failles regroupées sous le terme de XSS (Cross Site Scripting) ni sur les bonnes méthodes d'administration d'un serveur Apache pour préserver certains fichiers cachés … Aussi le code produit dans ce billet n'est en aucun exempt de faille XSS. Nous verrons dans un autre billet comment se protéger contre certaines failles XSS.
J'espère que ce billet vous aidera dans la compréhension de la problématique du SQL Injection en particulier et de la sécurité informatique en général.
Si vous réussissiez à injecter du SQL dans le code que j'ai produit depuis le formulaire n'hésitez pas à le publier en commentaire, c'est toujours un plaisir de progresser à travers le retour de lecteurs plus ingénieux que je ne le suis.