IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

Open source et architecture logicielle

[Actualité] Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection.

Note : 12 votes pour une moyenne de 1,42.
par , 03/01/2017 à 19h37 (9783 Affichages)
À la suite d'une discussion sur DVP au sujet du nombre encore trop important de sites web vulnérables à une attaque de type SQL Injection, je propose dans ce billet de vous montrer comment développer un site web imperméable à ce type de menace. L'objet de ce billet n'est pas de vous expliquer en détail ce qu'est ce type de menace, il y a suffisamment d'articles qui s'en chargent, mais de donner une solution pour s'en affranchir.

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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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 - Visualiser dans une fenêtre à part
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.

Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Viadeo Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Twitter Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Google Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Facebook Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Digg Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Delicious Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog MySpace Envoyer le billet « Comment l'objet permet de développer un site web imperméable à une attaque de type SQL injection. » dans le blog Yahoo

Mis à jour 28/02/2017 à 11h16 par ClaudeLELOUP

Tags: html, php, sécurité, sql
Catégories
HTML / CSS , PHP , Développement Web

Commentaires

  1. Avatar de Bousk
    • |
    • permalink
    Salut,

    je tombe sur ça par hasard depuis la main page de dvp et : tu vas vraiment faire une boucle sur l'ensemble des utilisateurs pour récupérer celui qui t'intéresse dans ta classe ?????
    Bonjour les perfs, sans compter que tu vas tuer ton serveur SQL, à moins d'avoir seulement une poignée d'utilisateurs inscrits.
    Le nom devrait être une clé primaire, au pire unique si l'id est déjà la clé primaire. id qui devrait être en auto-increment. Et la requête devrait retourner uniquement l'enregistrement correspondant au nom cherché, en aucun cas tous les enregistrements de la table...
    Et puis quitte à utiliser un PDO, mieux vaut utiliser les prepared statement pour éviter les SQL injections.
  2. Avatar de Beanux
    • |
    • permalink
    Cela n’empêche pas de faire une injection sur la création d'utilisateur.

    Il n'y a pas de défense contre les injection basé uniquement sur la sécurisation du champ de login.
    Toute saisie utilisateur qui va être joué dans la DB (lu ou écrite), est susceptible de déclencher une injection.


    Je me permet de fournir les quelques options possible pour la défense contres les injections:
    • Les instructions paramétrés
    • Les procédures stockés
    • Une validation des entrées par liste blanche
    • Échapper toutes les entrées utilisateur



    source (en anglais)
    Pour ceux qui ne connaissent pas OWASP, c'est une fondation dédié à la sécurité des application web.
  3. Avatar de bouchery
    • |
    • permalink
    Bonjour,

    Voici mes remarques :
    - jamais de mot de passe en clair dans une BDD (c'est limite interdit par la loi !)
    - login/pwd/nom : Manque de cohésion. On écrit en français, en anglais, mais on ne mélange pas
    - On va éviter le MyIsam et surtout le latin1. InnoDB + UTF-8 serait préférable
    - On ne fait pas d'include_once, mais des require (sans "_once"), et idéalement, on met en place de l'autoload, car "require/include", c'est une mauvaise pratique.
    - Il est interdit d'utiliser la double égalité. Il n'y a de salut que dans l'égalité stricte (===)
    - un else doit être sur la même ligne que que l'accolade de fermeture du if
    - un contructeur de doit pas faire de traitement, juste de l'initialisation (un "fetch" entre dans la catégorie des "traitements")
    - le mot clef "class" commence par un "c" minuscule
    - Une classe devrait être dans un namespace
    - "die" ne devrait jamais être utilisé pour traiter des erreurs
    - un affichage "simple" de "$e->geMessage()" est une faille de sécurité
    - la connexion à la BDD (PDO) devrait être injectée dans le constructeur
    - "$param" ne veut rien dire. "$login" serait plus logique
    - le charset de ta connexion est en utf-8 sur une table que tu as déclarée en "latin1", ce n'est pas cohérent
    - JAMAIS, JAMAIS DE LA VIE, on ne parcours une table pour recherche un utilisateur. Tu ne protèges pas des injections car tu ne construis pas ta requête. Donc la solution n'a rien à voir avec l'intitulé de l'article. Merci de faire un "WHERE" et d'expliquer vraiment ce qu'est l'injection SQL et comment s'en prémunir.
    - Bien évidemment, qui dit "pas de mot de passe en clair" dit "pas de récupération du mot de passe". On le valide via une comparaison de signature, mais on n'initialise JAMAIS une propriété avec
    - Attention au respect des conventions de code standard (espaces, accolades, etc.)
    - On ne fait pas de "SELECT *"

    Je suis en train de préparer une conférence pour faire comprendre aux gens pourquoi PHP a une mauvaise image auprès des développeurs non-php, et cet article est un excellent exemple de tout ce qu'il ne faut pas faire et qui contribue à accroître son image de "langage de merde" (comme ils disent)

    Frédéric
  4. Avatar de autran
    • |
    • permalink
    Salut Bousk, et merci pour ta réponse.

    Sur la forme, ce billet n'avait rien a faire sur la page main. D'ailleurs il n'y est plus.

    En revanche pour le fond, les problématiques d'optimisation que tu évoques quant à une boucle à faire sur l'ensemble des utilisateurs et le risque d’occurrence d'un ER grave en Dispo ne sont analysables qu'à travers le filtre de l'architecture logicielle et du contexte d'exploitation.

    Commençons par le contexte, si mon serveur n'a que quelques utilisateurs la probabilité d’occurrence de cet évènement en utilisation nominale est si faible (négligeable) que même si l'évènement redouté est grave du point de vue de la disponibilité du serveur (écroulement de la base de donnée) le risque reste tout à fait acceptable. Du coup pourquoi se priver de faire une requête sur une toute petite table, et écrire du code propre et beau dans un langage objet comme PHP.

    Venons en maintenant à l'architecture. En effet cette pratique est très répandu de charger une collection d'objets métier depuis le base de données et de la sauvegarder régulièrement au fur et à mesures des changements sans aucun risque pour la bonne santé de la base de données. Je ne suis pas un expert du PHP, je viens de JEE et je peux te dire que je le fais couramment sur les appli que j'ai pu développer en n-tiers.

    Salut Beanux, et merci également pour ta réponse.

    Je suis heureux de pouvoir échanger avec quelqu'un qui pratique le pentest applicatif (OWASP). Je vais donc te faire une réponse de technicien

    Je pense que tu as tort pour le premier scénario de menace que tu décris : En effet les données qui sont entrées pour le login et ou pwd ne sont jamais jouées en SQL mais uniquement dans mon objet métier Utilisateur, c'est le principe que j'expose dans mon billet.

    Quant aux 4 préconisations que tu fais, elles sont presque toutes excellentes à l'exception de l'utilisation des SP que je conseille de ne surtout pas suivre.

    Ces préconisations, issues en grande partie de l'OWASP, se situent essentiellement sur un périmètre que j'ai pris la précaution d'exclure de mon étude. En effet, elle n'avait pour but que de sensibiliser sur une façon de développer qui permet de s'affranchir du SQL injection.
  5. Avatar de oxman
    • |
    • permalink
    Effectivement Frédéric je suis d'accord avec ce que tu dis.
    Autran tu peux répondre à chaque point ?
  6. Avatar de autran
    • |
    • permalink
    Bonjour bouchery et merci pour tes remarques.

    Je reviens au périmètre de mon billet car il permet d’évacuer bon nombre de tes remarques.
    En effet j’ai proposé de montrer comment faire une authentification basée sur une égalité de chaine de caractères dans un langage de développement back (Java PHP JavaScript C#) plutôt que directement en requêtant la base de données pour éviter le SQL injection.
    Pour le langage de Back, j’ai choisi PHP non pas pour sa propreté ni pour mon niveau de maitrise du langage (très faible) mais pour toucher le plus large public possible et un public qui n’est pas sensibilisé aux problèmes de sécurité. Si j’avais choisi des langages de serveurs d’applications tels que JEE ou JavaScript j’aurais touché une population de développeurs moins large et qui connaissait déjà parfaitement les problématiques IT sec.

    Alors bien entendu, en toute rigueur, on n’envoie pas un mot de passe en clair. Mais là encore, mon billet est pédagogiquement centré sur le cœur de la problématique SQL injection. Je n’ai donc pas voulu surcharger le billet et égarer les lecteurs débutants en introduisant de nouvelles notions liées aux exigences de sécurité qui sont mentionnées dans vos commentaires.

    Donc il est vrai que si tu listes les exigences de sécurité du service authentifier et que tu étudies les évènements redoutés sur les biens support de type logiciel de ce service, tu trouveras une foultitude de menaces élémentaires et encore plus de mesures de sécurité dans les 4 grands langages du web (JS Java C# PHP) sous la forme des préconisations que tu listes dans ton commentaire

    @ oxman : je réponds toujours aux commentaires mais uniquement le soir ou le WE car durant la journée, je produis du code en JAVA et JS pour gagner ma vie.

    Comme je vois que vous êtes l’un comme l’autre plus callé que moi en PHP, dans une démarche positive, je vous propose de réécrire mon source pour qu’il soit conforme à vos précos. Qu’en pensez-vous ?
  7. Avatar de bouchery
    • |
    • permalink
    Je n'attends pas de réponse point par point oxman. J'ai juste repris le code, comme je le fais 15 fois par jour lors de mes reviews.
    Ce qui me pose problème, en dehors de l'erreur de conception sur la boucle while et la petite faille de sécurité, c'est qu'à aucun moment, on ne mets en évidence "l'imperméabilité à une attaque d'injection SQL".
    Dans cet exemple, on se "limite" à une recherche d'utilisateur en BDD, sans condition, à travers une boucle, avec un argument étrange sur la performance face à une taille de référentiel utilisateurs faible (sur mes bdd de 30 millions d'utilisateurs, je vais donc éviter). Du coup, qu'en est-il des "protections" apportées sur une requête SQL complexe, qui embarque obligatoirement des conditions ? On fait un gros "select *" sur nos 2 To de données, pour parcourir son contenu à la recherche de nos entités ?

    J'approuve à 100% l'idée d'écrire des "tutos" pour sensibiliser les amateurs de développement aux problématiques de sécurité, et je remercie Autran pour son travail, mais il serait souhaitable de faire ça plus sérieusement. Là, je suis franchement en colère à la lecture de cette article qui devrait être complètement ré-écrit, ou littéralement supprimé, tellement il détruit la réputation d'un langage qui se bat tous les jours pour faire comprendre qu'il n'est pas ce que les gens en pense.

    A moins, qu'il soit là justement pour détruire la réputation de PHP, dans la mesure où il semble avoir été écrit par un développeur Java
  8. Avatar de bouchery
    • |
    • permalink
    Nos commentaires se sont percutés, j'étais en train d'écrire ma réponse en même temps que la tienne.
  9. Avatar de autran
    • |
    • permalink
    Bonjour Bouchery,

    Je comprends ton agacement, mais il ne s'agit pas d'un article, même si un bug l'a fait parvenir en page de main (bien malgré moi). En effet, ce n'est qu'un billet sur mon blog. Et je considère ce blog comme mon laboratoire d'idées. Cela me permet de faire mûrir un certain nombre de réflexions (food for thought). A terme, ces idées ont évidemment vocation à devenir des tutos. Néanmoins certaines finissent à la poubelle comme c'est le cas du dernier billet que j'ai fait la semaine dernière concernant un simulateur de réseau sous Node.js.

    Mais quoi qu'il en soit, je m'efforce de suivre une méthodologie et lorsqu'un tutoriel est publié (environ 5 par an), tous les billets qui m'ont permis de le construire sont supprimés de mon blog.

    J'essaie d'agir en toute transparence et humilité. C'est pour cette raison que j'ai indiqué dans ce billet que si quelqu'un réussissait un SQL injection sur le bout de code que j'ai produit, je serait preneur de l'exploit à condition bien sur que le pentester en apporte la preuve.

    Et de façon plus générale, à l'instar de ce que nous avons lancé à quelques-uns sur DVP pour promouvoir et former en JavaScript, je suis partant pour me lancer sur le sujet de la sécurité.

    Pour ce qui est du PHP, il se trouve que ma carrière de développeur ne m'a pas conduit à devenir un expert de ce langage, mais je le considère au même niveau de maturité que les autres. Le codeur est à mon sens plus important que le langage lui-même.

    A+
  10. Avatar de neimad12
    • |
    • permalink
    Hello

    Juste pour faire remarquer, comme tu dis que cet article traite de "SQL Injection en particulier et de sécurité en général" :

    - la ligne echo ' <p>bonjour '. $user->getPrenom() . ' ' . $user->getNom() . '<p>'; est en soi une faille de sécurité XSS (imagine que j'ai rentré comme nom "<script>alert(document.cookie)</script>"

    - Comme mentionné plus haut, il existe des méthodes (indépendantes du langage de programmation) beaucoup plus efficaces et génériques pour éviter les SQLI : les prepared statements (cf doc pour MySQL).

    Voili voili
  11. Avatar de autran
    • |
    • permalink
    @Neimad,
    j'ai bien précisé dans mon billet que je ne traitais pas XSS mais merci pour cette exemple de faille
  12. Avatar de Benoit_premier
    • |
    • permalink
    Citation Envoyé par bouchery
    Bonjour,

    Voici mes remarques :
    - jamais de mot de passe en clair dans une BDD (c'est limite interdit par la loi !)
    - login/pwd/nom : Manque de cohésion. On écrit en français, en anglais, mais on ne mélange pas
    - On va éviter le MyIsam et surtout le latin1. InnoDB + UTF-8 serait préférable
    - On ne fait pas d'include_once, mais des require (sans "_once"), et idéalement, on met en place de l'autoload, car "require/include", c'est une mauvaise pratique.
    - Il est interdit d'utiliser la double égalité. Il n'y a de salut que dans l'égalité stricte (===)
    - un else doit être sur la même ligne que que l'accolade de fermeture du if
    - un contructeur de doit pas faire de traitement, juste de l'initialisation (un "fetch" entre dans la catégorie des "traitements")
    - le mot clef "class" commence par un "c" minuscule
    - Une classe devrait être dans un namespace
    - "die" ne devrait jamais être utilisé pour traiter des erreurs
    - un affichage "simple" de "$e->geMessage()" est une faille de sécurité
    - la connexion à la BDD (PDO) devrait être injectée dans le constructeur
    - "$param" ne veut rien dire. "$login" serait plus logique
    - le charset de ta connexion est en utf-8 sur une table que tu as déclarée en "latin1", ce n'est pas cohérent
    - JAMAIS, JAMAIS DE LA VIE, on ne parcours une table pour recherche un utilisateur. Tu ne protèges pas des injections car tu ne construis pas ta requête. Donc la solution n'a rien à voir avec l'intitulé de l'article. Merci de faire un "WHERE" et d'expliquer vraiment ce qu'est l'injection SQL et comment s'en prémunir.
    - Bien évidemment, qui dit "pas de mot de passe en clair" dit "pas de récupération du mot de passe". On le valide via une comparaison de signature, mais on n'initialise JAMAIS une propriété avec
    - Attention au respect des conventions de code standard (espaces, accolades, etc.)
    - On ne fait pas de "SELECT *"

    Je suis en train de préparer une conférence pour faire comprendre aux gens pourquoi PHP a une mauvaise image auprès des développeurs non-php, et cet article est un excellent exemple de tout ce qu'il ne faut pas faire et qui contribue à accroître son image de "langage de merde" (comme ils disent)

    Frédéric


    Oui il faut prendre soin de respecter ces precos dans un soucis de securité
    Mis à jour 11/04/2017 à 20h31 par autran
  13. Avatar de autran
    • |
    • permalink
    Merci de faire des remarques constructives plutôt que de paraphraser ou citer mot à mot ce qui est écrit dans les précédents commentaires
    Un commentaire doit apporter une plus-value.
    Par exemple, si vous faites un exploit sur mon code, publiez-le comme élément de preuve de votre théorie ou de vos préconisations
  14. Avatar de gene69
    • |
    • permalink
    Je voudrais pas tirer sur l'ambulance, mais y une erreur dans la conception objet de cette classe. Pour moi créer un objet à moitié initialisée, c'est offrir un plat de moules... et se retrouver avec que des coquilles vide.

    On retrouve cette erreur de design dans les forums ensuite:
    https://www.developpez.net/forums/d1...nee-objet-pdo/

    et point de vue injection SQL, promesse non tenue.
  15. Avatar de autran
    • |
    • permalink
    Merci pour ta parabole culinaire et vive les moules frites !

    Mais tu ne démontres rien en rapport avec le SQL Injection. Hors c'est bien l'objet de mon billet.

    Donc si tu dis qu'il est facile de faire du SQL injection sur mon code, montre le !
    C'est la règle en sécurité : si tu trouve une faille, tu donnes des éléments de preuve.