DAO et Sécurité

Ludovic Deneuville, Rémi Pépin

Le plan

  • Data Access Object
    • Pourquoi persister des données ?
    • C’est quoi une DAO
    • Exemple de DAO
  • Sécurité Informatique
    • Définition
    • Failles de sécurité
    • Injection SQL
  • Gestion des mots de passe
    • Authentification avec cypher
    • Hashage de mots de passe
    • Exemple

Data access object (DAO)

Un ordinateur

Quels sont ses composants (en gros) ?

  • Un processeur (CPU) : fait UNIQUEMENT du calcul
  • La mémoire RAM : mémoire volatile rapide
  • Disque dur (HDD, SSD) : mémoire longue durée
  • Carte graphique : unité de calcul spécialisée

Comment résoudre ce problème ?

Quel problème ??

Une question à se poser

p = Personnage(prenom="Leia", nom="Organa", age = 35)

C’est quoi une variable Python ?

  • Une référence (le nom de la variable)
  • Un objet associé (sa valeur)

Une question qui en découle

personnage.py
class Personnage:
    def __init__(self, prenom, nom, age):
        self.prenom = prenom
        self.nom = nom
        self.age = age
        self.vaisseaux = []

    def anniversaire(self):
        self.age += 1

C’est quoi un objet Python ?

  • Des attributs (qui peuvent être eux-mêmes des objets)
  • Des méthodes

Pour résumer

  • Nous voulons sauvegarder des couples clef-valeur
  • Avec des valeurs qui peuvent être elles-mêmes constituées de couples clef-valeur

Nous voulons sauvegarder un arbre 🌳

Comment faire cela ?

  • Écrire nos données sur le disque dur dans un fichier
    • csv, parquet, json, xml
  • Utiliser une base de données
    • SQL tables + relations, NoSQL

Lien persistance / application

  • Python : Variables volatiles en RAM
  • Système de persistance : Données stockées sur disque
  • Data Access Objects (DAO) en guise de lien

C’est quoi une DAO ?

C’est quoi une DAO ?

  • Classe technique
  • Une classe DAO par objet métier
  • Expose des méthodes pour communiquer avec la couche de persistance

Quelles méthodes exposer ?

  • Create
  • Read
  • Update
  • Delete

L’intérêt d’une classe à part

Séparation des responsabilités

  • Classe “jetable” 🚮
  • Modifiable sans risque 🔨
  • Parallélisation du travail 🦸‍♀️🧙‍♂️👨‍💼👩‍🔬

Petit recap

Comment se connecter à une DB en python ?

Utilisation d’une bibliothèque dédiée

  • PostgreSQL : psycopg2
  • MySQL : mysql-connector-python
  • Oracle : cx_Oracle
  • MongoDB : pymongo

psycopg2

  • pip install psycopg2-binary

  • connection : permet d’établir la connexion avec la base
    Pas super intéressant à faire, le code est donné

psycopg2 - connection

db_connection.py
import os
import dotenv
import psycopg2

from psycopg2.extras import RealDictCursor
from utils.singleton import Singleton

class DBConnection(metaclass=Singleton):
    """
    Classe de connexion à la base de données
    Elle permet de n'ouvrir qu'une seule et unique connexion
    """

    def __init__(self):
        """Ouverture de la connexion"""
        dotenv.load_dotenv()

        self.__connection = psycopg2.connect(
            host=os.environ["POSTGRES_HOST"],
            port=os.environ["POSTGRES_PORT"],
            database=os.environ["POSTGRES_DATABASE"],
            user=os.environ["POSTGRES_USER"],
            password=os.environ["POSTGRES_PASSWORD"],
            options=f"-c search_path={os.environ['POSTGRES_SCHEMA']}",
            cursor_factory=RealDictCursor,
        )

    @property
    def connection(self):
        return self.__connection

psycopg2 - cursor

  • cursor : encapsule la requête

    from dao.db_connection import DBConnection
    
    with DBConnection().connection as connection:
        with connection.cursor() as cursor:
  • cursor.execute("<une requête SQL>") : permet de faire une requête

  • cursor.fetchone()/fetchall()/fetchmany() : Récupération des résultats

Forme de base

def dao_function(self, arg1, arg2, ...):
    # Récupération de la connexion à la base
    with DBConnection().connection as connection:
    # Création d'un curseur pour faire une requête
        with connection.cursor() as cursor:
            # On envoie au serveur la requête SQL
            cursor.execute(
                "<une_requete_sql_à_trous>",
                remplisage_des_trous)

            # On récupère le résultat de la requête
            res = cursor.fetchone()  # ou fetchall()/fetchmany()

    # Si la requête renvoie quelque chose
    if res:
      something = "<res mis en forme>"
        
    return something   

Un petit exemple : LivreDao

Créer un livre

def create(self, livre) -> Livre:
    """Pour créer un livre en base"""
    with DBConnection().connection as conn:
        with conn.cursor() as cursor:
            cursor.execute(
                "INSERT INTO livre (isbm, titre, auteur)         "
                "     VALUES (%(isbm)s, %(titre)s, %(auteur)s)   "
                "  RETURNING id_livre;                           ",
                {"isbm": livre.isbm, 
                 "titre": livre.titre, 
                 "auteur": livre.auteur},
            )
            livre.id = cursor.fetchone()["id_livre"]
    return livre

Lister les livres

def find_all(self) -> list[Livre]:
    """Pour récupérer tous les livres en base"""
    with DBConnection().connection as conn:
        with conn.cursor() as cursor:
            cursor.execute(
                "SELECT id_livre,                  "
                "       isbm,                      "
                "       titre,                     "
                "       auteur                     "
                "  FROM livre ;                    "
            )
            livre_bdd = cursor.fetchall()
            
    liste_livres = []

    if livre_bdd:
        for livre in livre_bdd:
            liste_livres.append(
                Livre(
                    id=livre["id_livre"],
                    isbm=livre["isbm"],
                    titre=livre["titre"],
                    auteur=livre["auteur"],
                )
            )
            
    return liste_livres

Trouver un livre

def find_by_isbm(self, isbm) -> Livre:
    """Pour récupérer un livre depuis son isbm"""
    with DBConnection().connection as conn:
        with conn.cursor() as cursor:
            cursor.execute(
                "SELECT *                          "
                "  FROM livre                      "
                " WHERE isbm = %(isbm)s            ",
                {"isbm": isbm}
            livre_bdd = cursor.fetchone()
            
    livre = None
    if livre_bdd:
        livre = Livre(
            id=livre_bdd["id_livre"],
            isbm=livre_bdd["isbm"],
            titre=livre_bdd["titre"],
            auteur=livre_bdd["auteur"],
        )
    return livre

Conclusion

  • Python travaille en RAM (volatile)
  • Obligation d’avoir un mécanisme de persistance des données
  • DAO : centralise les méthodes pour lire/écrire nos données
  • La couche métier appelle la DAO sans se préoccuper du système de persistance
  • Permet un travail d’équipe efficace 🦸‍♀️🧙‍♂️👨‍💼👩‍🔬

Sécurité informatique

Principes CAID

4 piliers de la sécurité info

  • Confidentialité
  • Authentification
  • Intégrité
  • Disponibilité

Deux bonus

  • Traçabilité
  • Non-répudiation

Confidentialité

Seules les personnes autorisées doivent avoir accès aux informations qui leur sont destinées (notions de droits ou permissions).

Tout accès indésirable doit être empêché.

Mécanismes associés :

  • gestion des droits (annuaires, rôles …)
  • cryptographie

Authentification

Les utilisateurs doivent prouver leur identité en répondant à un “challenge”.

Mécanismes associés :

  • authentification faible (identifiant, mot de passe)
  • authentification forte (données biométriques, multi-facteurs)

Intégrité

Les données doivent être celles que l’on attend, et ne doivent pas être altérées de façons fortuites, illicites ou malveillantes.

Mécanismes associés :

  • signature électronique
  • checksum

Disponibilité

L’accès aux ressources du système d’information doit être permanent et sans faille durant les plages d’utilisation prévues.

Mécanismes associés :

  • redondance des serveurs
  • virtualisation
  • conteneurisation

Traçabilité

Garantit que les accès et tentatives d’accès aux éléments considérés sont tracés et que ces traces sont conservées et exploitables.

Mécanisme associé :

  • journalisation

La non-répudiation

Aucun utilisateur ne doit pouvoir contester les opérations qu’il a réalisées dans le cadre de ses actions autorisées et aucun tiers ne doit pouvoir s’attribuer les actions d’un autre utilisateur.

Mécanismes associés :

  • traçabilité
  • authentification
  • intégrité

Les failles informatiques

Trop de failles !!!

  • Failles physiques “bas niveau”
  • Failles physiques “haut niveau”
  • Injection SQL
  • Injection de données
  • Faille XSS
  • Exécution de code

De quoi faut-il se méfier ?

De vos utilisateurs

Exemple de failles, les injections SQL

Source : https://xkcd.com/

Injection SQL

Consiste à saisir du SQL pour exécuter une autre requête que celle prévue.

Problèmes :

  • Confidentialité
  • Authentification
  • Intégrité
  • Disponibilité

Exemple : s’authentifier sans mot de passe

Requête d’authentification

SELECT * 
  FROM user 
 WHERE name = 'input_name' 
   AND mdp = 'input_mdp';

Vous saisissez

  • Gennysson
  • awsome_password
SELECT * 
  FROM user 
 WHERE name = 'Gennysson' 
   AND mdp = 'awsome_password';

Exemple : s’authentifier sans mot de passe

SELECT * 
  FROM user 
 WHERE name = 'input_name' 
   AND mdp = 'input_mdp';

Vous saisissez

  • Gennysson
  • ' OR 1=1; --
SELECT * 
  FROM user 
 WHERE name = 'Gennysson' 
   AND mdp = '' OR 1=1; --';

Exemple : supprimer une table

SELECT * 
  FROM user 
 WHERE name = 'input_name' 
   AND mdp = 'input_mdp';

Vous saisissez

  • Gennysson
  • '; DROP TABLE user CASCADE; --
SELECT * 
  FROM user 
 WHERE name='Gennysson' 
   AND mdp=''; DROP TABLE user CASCADE; --;

Comment se protéger ?

  • Échapper les caractères spéciaux
  • Utiliser une requête préparée

La bibliothèque que vous utiliserez ne fait que de l’échappement de caractères spéciaux 😨

Cross Site Scripting

Consiste à injecter du code provoquant des actions sur le navigateur. Cela peut permettre :

  • Des redirections de page (phishing)
  • Du vol d’information
  • Des actions sur le site
  • Rendre le site difficile à utiliser

Comment se protéger ?

  • Ne jamais insérer des données brutes
  • Échapper les caractères spéciaux
  • Vérifier vos données

Tip

Les bibliothèques web le font souvent pour vous !

To sum up : injection

  • Ne jamais faire confiance aux utilisateurs, vérifier / nettoyer leurs inputs
  • Ne jamais faire confiance aux utilisateurs, vérifier / nettoyer leurs inputs
  • Ne jamais faire confiance aux utilisateurs, vérifier / nettoyer leurs inputs

Gestion des mots de passe

Votre application doit-elle stocker des mots de passe en clair?

Votre application doit-elle connaître le mot de passe d’un utilisateur pour l’authentifier ?

Comment on fait ?

Hasher le mot de passe

  • Hashage du mot de passe
  • Stockage du hash en base
  • Quand besoin de comparer on hashe le mdp saisi
  • Et on compare les hashs

Authentification sans persister les mots de passe !!!!

Ajouter du sel pour plus de sécurité

Une base sans sel ajouté

  • Votre base mail/mdp fuite mais les mdp sont hachés
  • Les attaquants doivent bruteforce les mdp
  • Ils commencent par les mdp les plus courants, et les hachent avec les algo de hash courants
  • Puis ils comparent avec la base
  • Forte chance d’avoir plusieurs correspondances

Le sel c’est bon pour la sécurité

Au lieu de hacher et stocker le mdp vous stockez et hachez le mdp ET un élément lié à l’utilisateur de manière déterministe (le sel).

Maintenant même si 2 personnes ont le même mdp, elles auront des hash différents.

Le sel c’est bon pour la sécurité

  • Votre base mail/mdp fuite mais les mdp sont hachés et salés
  • Les attaquants doivent bruteforce les mdp
  • Ils commencent par les mdp les plus courants, et les hachent avec les algo de hash courants
  • Puis ils comparent avec la base.
  • Il y a pas de match car vos mdp sont salés
  • Et trouver un mdp ne permet de trouver les autres

Exemple de hashage de mdp

import hashlib
 
def hash_password(password, idep):
    salt = idep
    return hashlib.sha256(salt.encode() + password.encode()).hexdigest()
    
print(hash_password("awsome_password", "Gennysson"))

To sum up the security part

  • Toujours vérifier les inputs
  • Ne jamais faire confiance aux utilisateurs
  • Plusieurs niveaux de sécurité
  • Pas besoin de stocker les mots de passe en clair