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.
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 :
- Volume total de partages externes
- Destinataires récurrents (identifier les partenaires vs les erreurs)
- Permissions accordées (WRITER est plus risqué que READER)
- Absence de date d'expiration
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 :
| Indicateur | Seuil d'alerte | Risque |
|---|---|---|
| Fichiers SQL/BDD | > 0 | Données production exposées |
| Fichiers anciens (> 3 ans) | > 30% | Violation Art. 5(1)(e) RGPD |
| Fichiers > 1 GB | Tout | Dump de données potentiel |
| Fichiers ".msg" Outlook | > 100 | Emails archivés hors contrôle |
| Noms évocateurs RH/paie | > 0 | Donné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écanisme | Comportement | Niveau 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 |
+all | Tout 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 :
- Application avec un
clientIdinconnu ou non documenté - Scope
mail.google.comaccordé à une app non-Google - Tokens anonymes (apps non identifiables publiquement)
- Nombre de tokens excessif pour un seul utilisateur (> 20)
- Applications de type "AI" avec accès Drive/Gmail complet
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 :
- Les CGU du fournisseur permettent-elles l'entraînement sur les données ?
- Un DPA (Data Processing Agreement) existe-t-il avec le fournisseur d'IA ?
- Les données personnelles de tiers (clients, employés) transitent-elles par ces services ?
---