| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente |
| nsi:terminales:sql_et_python [2021/08/20 17:23] – goupillwiki | nsi:terminales:sql_et_python [2022/08/29 21:47] (Version actuelle) – goupillwiki |
|---|
| On peut ouvrir ou créer un nouveau fichier base de données : | On peut ouvrir ou créer un nouveau fichier base de données : |
| |
| <code python linenums=2> | <code python linenums:2> |
| c = sqlite3.connect("example.db") | c = sqlite3.connect("example.db") |
| </code> | </code> |
| On peut souhaiter travailler sur une base de données temporaire que l'on n'enregistre pas sur le disque dur. Dans ce cas on cette possibilité : | On peut souhaiter travailler sur une base de données temporaire que l'on n'enregistre pas sur le disque dur. Dans ce cas on cette possibilité : |
| |
| <code python linenums=2> | <code python linenums:2> |
| c = sqlite3.connect(":memory:") | c = sqlite3.connect(":memory:") |
| </code> | </code> |
| |
| Dans notre cas, nous reprenons le fichier {{ :nsi:terminales:films.db |}} avec mes films et acteurs. Placer ce fichier dans le même répertoire que le script python puis : | Dans notre cas, nous reprenons le fichier {{ :nsi:terminales:database:films.db |}} avec mes films et acteurs. Placer ce fichier dans le même répertoire que le script python puis : |
| |
| <code python linenums=2> | <code python linenums:2> |
| c = sqlite3.connect("films.db") | c = sqlite3.connect("films.db") |
| </code> | </code> |
| Nous souhaitons l'activer, nous ajoutons alors la commande : | Nous souhaitons l'activer, nous ajoutons alors la commande : |
| |
| <code python linenums=3> | <code python linenums:3> |
| c.execute("PRAGMA foreign_keys = 1") | c.execute("PRAGMA foreign_keys = 1") |
| </code> | </code> |
| L'exécution des requêtes demande l'utilisation d'un //curseur//. C'est un objet dont le rôle est justement d'exécuter les requêtes. | L'exécution des requêtes demande l'utilisation d'un //curseur//. C'est un objet dont le rôle est justement d'exécuter les requêtes. |
| |
| <code python linenums = 4> | <code python linenums:4> |
| cursor = c.cursor() | cursor = c.cursor() |
| </code> | </code> |
| Le curseur a lui aussi une méthode ''execute'', c'est par elle qu'on lance les requêtes. Exemple : | Le curseur a lui aussi une méthode ''execute'', c'est par elle qu'on lance les requêtes. Exemple : |
| |
| <code python linenums = 5> | <code python linenums:5> |
| cursor.execute("CREATE TABLE ARTISTE (idArtiste INTEGER PRIMARY KEY, nom VARCHAR(20), prénom VARCHAR(20), biographie TEXT, naissance DATE);") | cursor.execute("CREATE TABLE ARTISTE (idArtiste INTEGER PRIMARY KEY, nom VARCHAR(20), prénom VARCHAR(20), biographie TEXT, naissance DATE);") |
| </code> | </code> |
| Écrit tout à la suite, ce n'est pas très lisible. On préfère généralement utiliser plusieurs lignes : | Écrit tout à la suite, ce n'est pas très lisible. On préfère généralement utiliser plusieurs lignes : |
| |
| <code python linenums = 5> | <code python linenums:5> |
| cursor.execute("""CREATE TABLE ARTISTE ( | cursor.execute("""CREATE TABLE ARTISTE ( |
| idArtiste INTEGER PRIMARY KEY, | idArtiste INTEGER PRIMARY KEY, |
| <WRAP important>La fermeture de la connexion de met pas à jour le fichier base de données et il faut donc penser à faire un ''commit'' avant la fermeture.</WRAP> | <WRAP important>La fermeture de la connexion de met pas à jour le fichier base de données et il faut donc penser à faire un ''commit'' avant la fermeture.</WRAP> |
| |
| ==== Injection SQL ==== | <WRAP info>==== Injection SQL ==== |
| |
| <WRAP info>Il n'est pas obligatoire de comprendre ceci. J'indique l'explication pour les plus avancés d'entre vous.</WRAP> | Il n'est pas obligatoire de comprendre ceci. J'indique l'explication pour les plus avancés d'entre vous. |
| |
| Imaginons que l'utilisateur ait le droit de supprimer certains artistes, pas tous, par exemple ceux qu'il a lui même créé. | Imaginons qu'un site permette aux internautes de créer un compte utilisateur. Avec ce compte utilisateur ils peuvent ajouter des items dans la base, par exemple des acteurs. En face chaque acteur qu'ils ont créé, ils voient un bouton avec une poubelle. S'ils appuient dessus, le système envoie une requête au serveur précisant : |
| | * que c'est une demande de suppression, |
| | * l'id de l'item à supprimer, donc l'id de l'artiste. |
| |
| Par exemple, il demande la suppression de l'artiste ''idArtiste = 1'', c'est à dire dans notre cas, Bruce Willis. | Supposons que l'utilisateur clique le bouton à côté de Bruce Willis. On aura ''%%idArtiste = "1"%%''. |
| |
| Le système doit donc fabriquer la requête avec le paramètre ''idArtiste = 1''. Supposons que l'on fasse ceci : | Côté serveur, supposons encore que le script ressemble à : |
| |
| <code python> | <code python> |
| # on sait que idArtiste = 1 | def suppression_artiste(idArtiste): |
| requete = "DELETE FROM ARTISTE WHERE idArtiste = {}".format(idArtiste) | requete = "DELETE FROM ARTISTE WHERE idArtiste = {}".format(idArtiste) |
| # on a donc : | cursor.execute(requete) |
| # requete = "DELETE FROM ARTISTE WHERE idArtiste = 1" | |
| cursor.execute(requete) | |
| # tout se passe bien | |
| </code> | </code> |
| |
| Maintenant imaginons qu'un hacker s'arrange pour transmettre cette requête : | Si l'utilisateur fait les choses normalement, la requête sera bien ''%%"DELETE FROM ARTISTE WHERE idArtiste = 1"%%'' et tout se passera bien. |
| |
| <code python> | Supposons maintenant qu'un utilisateur mal intentionné se crée lui aussi un compte. Puis, comme il est maître de sa propre machine, il force l'envoie d'une requête de suppression mais avec un ''idArtiste'' anormal : il donne ''%%idArtiste = "1 OR 1 = 1"%%''. |
| idArtiste = '1 OR 1 = 1' | |
| </code> | |
| | |
| Le hacker est maître du client sur lequel il exécute sa requête, il peut donc envoyer la requête qu'il veut. Que se passe-t-il si on fait la même chose qu'avant ? | |
| | |
| <code python> | |
| # on sait que idArtiste = '1 OR 1 = 1' | |
| requete = "DELETE FROM ARTISTE WHERE idArtiste = {}".format(idArtiste) | |
| # on a donc : | |
| # requete = "DELETE FROM ARTISTE WHERE idArtiste = 1 OR 1 = 1" | |
| cursor.execute(requete) | |
| # aie aie aie ! | |
| </code> | |
| |
| ''1 = 1'' est toujours vrai, donc la condition du ''WHERE'' sera toujours vraie et on va donc supprimer tous les artistes de la base !!! | Le programme côté serveur fait la même chose et on obtient : ''%%"DELETE FROM ARTISTE WHERE idArtiste = 1 OR 1 = 1"%%''. **Aïe, aïe, aïe !**. La condition de //WHERE// est toujours vraie. **Tous les artistes sont supprimés !** |
| |
| Nous devons donc empêcher que de telles situations se produisent. | Et encore c'est un cas simple. L'utilisateur pourrait écrire pourrait écrire toute une requête, par exemple en envoyant ''%%idArtiste = "1; DELETE FROM FILM WHERE 1 = 1"%%''. |
| |
| Les langages proposent des fonctions, comme le ''execute'' de ''sqlite'' avec le ''?'', qui se chargent de filtrer tous les cas à problème. C'est pourquoi on ne doit surtout pas essayer de compléter la requête nous-même, on doit utiliser les fonctions fournies. | Nous devons donc empêcher que de telles situations se produisent. L'idée est d'empêcher d'insérer des éléments contenant des commandes SQL. On pourrait le faire manuellement bien sûr, en prévoyant tout un tas de tests pour éliminer tous les exemples de commandes dangereuses. Mais le module fournit déjà toutes les fonctionnalités pour cela. Par exemple, le ''execute'' de ''sqlite'' se charge de filtrer tous les cas à problème. C'est pourquoi on ne doit surtout pas essayer de compléter la requête nous-même, on doit utiliser les fonctions fournies. |
| | </WRAP> |