Chapitre 3

Niveau 3 — Drive | Niveau 4 — DNS | Niveau 5 — OAuth

Audit des partages Google Drive (publics, externes, service accounts). Vérification SPF/DKIM/DMARC et blacklists. Inventaire et analyse des risques des tokens OAuth et applications tierces.

Niveaux 3, 4 & 5
Drive, DNS et applications OAuth tierces
N3 — Google Drive N4 — DNS & Email N5 — OAuth & Apps tierces

6. Niveau 3 — Google Drive et Partages

6.1 Partages publics ("Anyone with link")

Un fichier partagé avec "Tout le monde avec le lien" est accessible sans authentification. N'importe qui possédant le lien peut accéder au contenu, et ce lien peut circuler indéfiniment.

def audit_public_files(drive_service, user_email):
    """Trouve les fichiers accessibles publiquement."""
    public_files = []
    page_token = None
    while True:
        results = drive_service.files().list(
            q="'me' in owners",
            fields="nextPageToken, files(id, name, mimeType, permissions, size, modifiedTime)",
            pageToken=page_token,
            pageSize=1000,
        ).execute()
        for f in results.get("files", []):
            for perm in f.get("permissions", []):
                if perm.get("type") == "anyone":
                    public_files.append({
                        "file_id": f.get("id"),
                        "name": f.get("name"),
                        "owner": user_email,
                        "permission": perm.get("role"),
                        "size": f.get("size", 0),
                        "modified": f.get("modifiedTime"),
                    })
        page_token = results.get("nextPageToken")
        if not page_token:
            break
    return public_files

6.2 Partages externes

Les partages avec des utilisateurs hors domaine doivent être inventoriés. Les points d'attention sont :

def audit_external_shares(drive_service, domain):
    """Identifie tous les partages vers des utilisateurs extérieurs au domaine."""
    external = []
    page_token = None
    while True:
        results = drive_service.files().list(
            q="'me' in owners and sharedWithMe=false",
            fields="nextPageToken, files(id, name, permissions, mimeType)",
            pageToken=page_token,
            pageSize=1000,
        ).execute()
        for f in results.get("files", []):
            for perm in f.get("permissions", []):
                email = perm.get("emailAddress", "")
                if email and not email.endswith(f"@{domain}"):
                    external.append({
                        "file": f.get("name"),
                        "shared_with": email,
                        "role": perm.get("role"),
                        "type": perm.get("type"),
                        "expiration": perm.get("expirationTime"),
                    })
        page_token = results.get("nextPageToken")
        if not page_token:
            break
    return external

6.3 Analyse volumétrique des fichiers

L'analyse volumétrique permet d'identifier des anomalies : dumps de base de données, archives de données sensibles, doublons massifs, fichiers anciens oubliés.

SENSITIVE_KEYWORDS = [
    "bulletin", "paie", "salaire", "contrat", "password", "mdp", "credential",
    "dump", "backup", "bak", "export", "confidentiel", "secret", "budget",
    "bilan", "paierol", "rh", "ressources-humaines",
]

def analyze_file_sensitivity(filename):
    """Détecte si un fichier a un nom évocateur de données sensibles."""
    name_lower = filename.lower()
    return any(kw in name_lower for kw in SENSITIVE_KEYWORDS)

def audit_drive_files_deep(drive_service, user_email):
    """Analyse approfondie des fichiers Drive d'un utilisateur."""
    stats = {
        "total_files": 0,
        "total_size_bytes": 0,
        "sensitive_files": [],
        "large_files": [],      # > 100 MB
        "old_files": [],        # > 3 ans sans modification
        "sql_files": [],
        "archive_files": [],
    }
    # ... implémentation complète par pagination
    return stats
Indicateurs à surveiller :
IndicateurSeuil d'alerteRisque
Fichiers SQL/BDD> 0Données production exposées
Fichiers anciens (> 3 ans)> 30%Violation Art. 5(1)(e) RGPD
Fichiers > 1 GBToutDump de données potentiel
Fichiers ".msg" Outlook> 100Emails archivés hors contrôle
Noms évocateurs RH/paie> 0Données personnelles sensibles

6.4 Service Accounts avec accès Drive

Des service accounts GCP (souvent créés pour des automatisations ou des applications tierces) peuvent avoir des accès en écriture sur des fichiers Drive. Ce vecteur est souvent oublié.

def detect_service_account_shares(permissions):
    """Détecte les partages avec des service accounts GCP."""
    sa_shares = []
    for perm in permissions:
        email = perm.get("emailAddress", "")
        if email.endswith(".gserviceaccount.com"):
            sa_shares.append({
                "service_account": email,
                "role": perm.get("role"),
                "project": email.split("@")[1].replace(".iam.gserviceaccount.com", ""),
            })
    return sa_shares

---

7. Niveau 4 — DNS et Sécurité Email

7.1 SPF — Sender Policy Framework

Le SPF définit quels serveurs sont autorisés à envoyer des emails au nom du domaine.

import dns.resolver

def check_spf(domain):
    """Vérifie et analyse l'enregistrement SPF."""
    try:
        answers = dns.resolver.resolve(domain, "TXT")
        for rdata in answers:
            txt = str(rdata).strip('"')
            if txt.startswith("v=spf1"):
                return {
                    "record": txt,
                    "has_spf": True,
                    "hard_fail": txt.endswith("-all"),
                    "soft_fail": "~all" in txt,
                    "neutral": "?all" in txt,
                    "severity": "OK" if "-all" in txt else "PARTIEL" if "~all" in txt else "CRITIQUE",
                    "recommendation": "Passer de ~all à -all pour le hard fail" if "~all" in txt else None,
                }
    except Exception as e:
        return {"has_spf": False, "error": str(e), "severity": "CRITIQUE"}
Niveaux SPF et impact :
MécanismeComportementNiveau de protection
-all (hard fail)Emails non autorisés rejetés✅ Optimal
~all (soft fail)Emails marqués suspects⚠️ Insuffisant
?all (neutral)Aucune action❌ Inutile
+allTout autorisé🔴 CRITIQUE — à corriger immédiatement

7.2 DKIM — DomainKeys Identified Mail

DKIM ajoute une signature cryptographique aux emails sortants, permettant au destinataire de vérifier que le message n'a pas été altéré.

def check_dkim(domain, selector="google"):
    """Vérifie l'enregistrement DKIM pour le sélecteur spécifié."""
    try:
        dkim_record = f"{selector}._domainkey.{domain}"
        answers = dns.resolver.resolve(dkim_record, "TXT")
        for rdata in answers:
            txt = str(rdata).strip('"')
            if "v=DKIM1" in txt or "k=rsa" in txt:
                key_length = "2048" if len(txt) > 300 else "1024"
                return {
                    "selector": selector,
                    "record": txt[:100] + "...",
                    "key_length": key_length,
                    "severity": "OK" if key_length == "2048" else "PARTIEL",
                    "recommendation": "Clé RSA 1024 obsolète — migrer vers 2048 bits" if key_length == "1024" else None,
                }
    except Exception:
        return {"selector": selector, "has_dkim": False, "severity": "CRITIQUE"}

7.3 DMARC — Domain-based Message Authentication

DMARC définit quoi faire des emails qui échouent SPF et DKIM, et où envoyer les rapports.

def check_dmarc(domain):
    """Vérifie et analyse la politique DMARC."""
    try:
        dmarc_record = f"_dmarc.{domain}"
        answers = dns.resolver.resolve(dmarc_record, "TXT")
        for rdata in answers:
            txt = str(rdata).strip('"')
            if txt.startswith("v=DMARC1"):
                policy = "none"
                if "p=reject" in txt:
                    policy = "reject"
                elif "p=quarantine" in txt:
                    policy = "quarantine"
                has_rua = "rua=" in txt
                return {
                    "record": txt,
                    "policy": policy,
                    "has_reporting": has_rua,
                    "severity": "OK" if policy == "reject" else "PARTIEL" if policy == "quarantine" else "CRITIQUE",
                    "recommendation": None if policy == "reject" else f"Passer de p={policy} à p=reject",
                }
    except Exception:
        return {"has_dmarc": False, "severity": "CRITIQUE",
                "recommendation": "Configurer un enregistrement DMARC immédiatement"}
Chemin de maturation DMARC recommandé :
Phase 1 (Observation) : p=none ; rua=mailto:dmarc@votredomaine.com
    ↓ (2-4 semaines d'analyse des rapports)
Phase 2 (Durcissement) : p=quarantine ; pct=10 → pct=100
    ↓ (2-4 semaines de validation)
Phase 3 (Protection complète) : p=reject

7.4 Enregistrements MX et blacklists

def check_mx_blacklists(domain):
    """Vérifie si les serveurs MX sont sur des blacklists email."""
    blacklists = [
        "zen.spamhaus.org",
        "bl.spamcop.net",
        "dnsbl.sorbs.net",
        "b.barracudacentral.org",
        "dnsbl-1.uceprotect.net",
    ]
    try:
        mx_records = dns.resolver.resolve(domain, "MX")
        mx_host = str(sorted(mx_records, key=lambda r: r.preference)[0].exchange).rstrip(".")
        ip = str(dns.resolver.resolve(mx_host, "A")[0])
        reversed_ip = ".".join(reversed(ip.split(".")))

        results = {"mx_ip": ip, "listed_on": [], "clean_on": []}
        for bl in blacklists:
            try:
                dns.resolver.resolve(f"{reversed_ip}.{bl}", "A")
                results["listed_on"].append(bl)
            except dns.resolver.NXDOMAIN:
                results["clean_on"].append(bl)
        return results
    except Exception as e:
        return {"error": str(e)}

---

8. Niveau 5 — Applications OAuth et Intégrations Tierces

8.1 Inventaire des tokens OAuth

Chaque fois qu'un utilisateur autorise une application tierce ("Connexion avec Google"), un token OAuth est créé. Ces tokens accordent des droits sur les données de l'utilisateur selon les scopes demandés.

SCOPE_RISK_LEVELS = {
    "https://mail.google.com/": ("CRITIQUE", "Accès complet Gmail (lecture + modification + suppression)"),
    "https://www.googleapis.com/auth/gmail.modify": ("ÉLEVÉ", "Modification des emails Gmail"),
    "https://www.googleapis.com/auth/drive": ("CRITIQUE", "Accès complet Google Drive"),
    "https://www.googleapis.com/auth/drive.file": ("MOYEN", "Fichiers Drive créés par l'app"),
    "https://www.googleapis.com/auth/admin.directory.user": ("CRITIQUE", "Gestion complète des utilisateurs"),
    "https://www.googleapis.com/auth/contacts": ("MOYEN", "Accès au carnet d'adresses"),
    "https://www.googleapis.com/auth/calendar": ("MOYEN", "Calendrier complet"),
    "https://www.googleapis.com/auth/admin.directory.group": ("ÉLEVÉ", "Gestion des groupes"),
}

def analyze_token_risks(tokens):
    """Analyse les scopes des tokens OAuth et identifie les risques."""
    risky_tokens = []
    for token in tokens:
        risks = []
        for scope in token.get("scopes", []):
            if scope in SCOPE_RISK_LEVELS:
                level, description = SCOPE_RISK_LEVELS[scope]
                risks.append({"scope": scope, "level": level, "description": description})
        if risks:
            risky_tokens.append({
                "user": token.get("email"),
                "app": token.get("displayText", token.get("clientId", "")),
                "client_id": token.get("clientId"),
                "risks": risks,
                "max_severity": "CRITIQUE" if any(r["level"] == "CRITIQUE" for r in risks) else "ÉLEVÉ",
            })
    return risky_tokens

8.2 Gouvernance des applications OAuth

Politique de whitelisting — Sans liste blanche, tout utilisateur peut autoriser n'importe quelle application OAuth. Cette configuration est la source de nombreux incidents de phishing OAuth ("consent phishing"). Configuration recommandée dans admin.google.com :
Admin Console → Sécurité → Contrôle des API → Contrôle d'accès aux applications Google
→ Activer : "Seules les apps approuvées par l'administrateur peuvent accéder aux données"
→ Créer une liste blanche des applications autorisées
Signaux d'alerte sur un token OAuth :

8.3 Cas particulier — Outils d'IA et données d'entreprise

Avec la prolifération des outils d'IA (Gemini, Copilot, Claude, ChatGPT via plugins), il est fréquent de trouver des tokens OAuth accordant accès à des volumes importants de données d'entreprise. Ces accès posent des questions :

Contrôle à réaliser : Lister toutes les applications OAuth contenant "AI", "GPT", "Gemini", "Claude", "Assistant" dans leur nom, et vérifier les scopes accordés.

---