Niveau 1 — IAM & Auth | Niveau 2 — Gmail
Audit des identités, Super Admins, 2FA, emails de récupération, comptes inactifs. Audit Gmail : transferts automatiques, délégations, filtres de redirection et App Passwords.
Priorités absolues de ce chapitre
- 2FA enforced sur tous les comptes sans exception
- Emails de récupération Super Admins = internes au domaine
- Aucun transfert automatique Gmail actif
- Aucun App Password (Less Secure App) actif
4. Niveau 1 — Identités, Accès et Authentification
4.1 Inventaire des utilisateurs
La première étape est d'obtenir la liste exhaustive des utilisateurs et leurs attributs de sécurité.
def get_all_users(service):
"""Retourne tous les utilisateurs du domaine avec leurs attributs de sécurité."""
users = []
page_token = None
while True:
result = service.users().list(
domain=DOMAIN,
maxResults=500,
pageToken=page_token,
projection="full",
orderBy="email",
).execute()
users.extend(result.get("users", []))
page_token = result.get("nextPageToken")
if not page_token:
break
return users
Attributs à extraire et analyser :
| Attribut API | Signification | Alerte si |
|---|---|---|
isAdmin | Super Admin | > 4 comptes |
isDelegatedAdmin | Admin délégué | Lister et justifier |
suspended | Compte suspendu | Présent sans offboarding |
isEnrolledIn2Sv | 2FA enrollée | False = CRITIQUE |
isEnforcedIn2Sv | 2FA obligatoire enforced | False = non conforme CIS |
lastLoginTime | Dernière connexion | > 90 jours = inactif |
recoveryEmail | Email de récupération | Externe = risque bypass 2FA |
recoveryPhone | Téléphone de récupération | Externe = risque SIM swap |
4.2 Analyse des comptes Super Admin
Le CIS Benchmark recommande un maximum de 4 Super Admins (idéalement 2-3). Chaque compte Super Admin compromis donne un accès total au tenant.
Points de contrôle :- Nombre de Super Admins (≤ 4)
- Les Super Admins utilisent-ils des comptes dédiés (non utilisés au quotidien) ?
- Chaque Super Admin dispose-t-il d'une clé de sécurité physique (FIDO2/YubiKey) ?
- Les Super Admins ont-ils des emails de récupération internes au domaine ?
def audit_super_admins(users):
super_admins = [u for u in users if u.get("isAdmin")]
risks = []
for sa in super_admins:
recovery = sa.get("recoveryEmail", "")
if recovery and not recovery.endswith(f"@{DOMAIN}"):
risks.append({
"email": sa["primaryEmail"],
"risk": "Email de récupération externe",
"recovery": recovery,
"severity": "CRITIQUE",
})
return super_admins, risks
4.3 Analyse de la 2FA
Distinction cruciale : Il existe une différence entre 2FA enrollée (l'utilisateur a configuré un second facteur) et 2FA enforced (la politique admin oblige la 2FA — un utilisateur ne peut pas la désactiver).def audit_2fa(users):
findings = {
"no_2fa": [], # Aucune 2FA configurée
"enrolled_not_enforced": [], # 2FA configurée mais pas obligatoire
"fully_enforced": [], # 2FA enforced
}
for user in users:
enrolled = user.get("isEnrolledIn2Sv", False)
enforced = user.get("isEnforcedIn2Sv", False)
if not enrolled:
findings["no_2fa"].append(user["primaryEmail"])
elif enrolled and not enforced:
findings["enrolled_not_enforced"].append(user["primaryEmail"])
else:
findings["fully_enforced"].append(user["primaryEmail"])
return findings
Types de 2FA — Résistance au phishing :
| Type 2FA | Résistance phishing | Recommandation |
|---|---|---|
| FIDO2 / Passkey / YubiKey | ✅ Totale | Obligatoire pour admins |
| TOTP (Google Authenticator) | ⚠️ Partielle | Vulnérable AITM |
| SMS OTP | ❌ Faible | Vulnérable SIM swap |
| Backup codes | ❌ Très faible | À usage unique uniquement |
4.4 Emails de récupération — Vecteur d'account takeover
L'email de récupération est le talon d'Achille de la 2FA. Si l'email de récupération est compromis, un attaquant peut réinitialiser le mot de passe sans avoir besoin de la 2FA. C'est le vecteur d'attaque le plus sous-estimé en environnement Workspace.
def audit_recovery_info(service, user_emails):
findings = {
"external_recovery_emails": [],
"external_recovery_phones": [],
"no_recovery": [],
}
for email in user_emails:
user = service.users().get(userKey=email, projection="full").execute()
recovery_email = user.get("recoveryEmail", "")
recovery_phone = user.get("recoveryPhone", "")
if recovery_email and not recovery_email.endswith(f"@{DOMAIN}"):
findings["external_recovery_emails"].append({
"user": email,
"recovery_email": recovery_email,
"provider": recovery_email.split("@")[-1],
})
if not recovery_email and not recovery_phone:
findings["no_recovery"].append(email)
return findings
Critères de risque pour les emails de récupération :
| Type | Niveau | Raison |
|---|---|---|
@gmail.com, @outlook.com | 🔴 ÉLEVÉ | Grand public, souvent peu sécurisé |
@yahoo.fr, @hotmail.fr | 🔴 ÉLEVÉ | Breaches historiques massives (Yahoo 2016) |
| Domaine d'une autre entreprise | 🟠 MOYEN | Employé précédent ou freelance |
@domaine.com interne | ✅ OK | Contrôlé par l'organisation |
4.5 Comptes inactifs et suspendus
Un compte inactif non supprimé représente un vecteur d'attaque persistant. Les comptes suspendus non traités signalent l'absence d'une procédure d'offboarding.
from datetime import datetime, timezone, timedelta
def audit_inactive_accounts(users, inactive_days=90):
threshold = datetime.now(timezone.utc) - timedelta(days=inactive_days)
inactive = []
for user in users:
last_login = user.get("lastLoginTime")
if last_login:
last_login_dt = datetime.fromisoformat(
last_login.replace("Z", "+00:00")
)
if last_login_dt < threshold and not user.get("suspended"):
inactive.append({
"email": user["primaryEmail"],
"last_login": last_login,
"days_inactive": (datetime.now(timezone.utc) - last_login_dt).days,
})
suspended = [u for u in users if u.get("suspended")]
return inactive, suspended
4.6 Rôles administratifs
Google Workspace permet de déléguer des rôles granulaires (admin messagerie, admin helpdesk, etc.). Un audit doit lister tous les rôles administratifs et vérifier le respect du principe du moindre privilège.
def audit_admin_roles(service):
"""Liste tous les rôles et leurs assignations."""
roles = service.roles().list(customer="my_customer").execute()
role_assignments = service.roleAssignments().list(
customer="my_customer"
).execute()
findings = {
"custom_roles": [r for r in roles.get("items", []) if not r.get("isSystemRole")],
"assignments": role_assignments.get("items", []),
"super_admin_assignments": [
a for a in role_assignments.get("items", [])
if a.get("roleId") == "SUPER_ADMIN_ROLE_ID"
],
}
return findings
---
5. Niveau 2 — Messagerie Gmail
5.1 Transferts automatiques
Le transfert automatique de messagerie est l'un des risques les plus graves : il crée une exfiltration de données en temps réel, silencieuse et persistante. À auditer sur tous les comptes.
def audit_gmail_forwarding(gmail_service, user_email):
"""Vérifie si un utilisateur a configuré un transfert automatique."""
try:
settings = gmail_service.users().settings().getAutoForwarding(
userId=user_email
).execute()
if settings.get("enabled"):
return {
"email": user_email,
"forwarding_to": settings.get("emailAddress"),
"disposition": settings.get("disposition"),
"severity": "CRITIQUE",
}
except Exception as e:
return {"email": user_email, "error": str(e)}
return None
Contre-mesures :
- Désactiver le transfert automatique pour tous les utilisateurs via la politique admin
- Chemin :
admin.google.com → Apps → Gmail → Paramètres utilisateur → Transfert automatique de courrier - Activer des alertes sur toute activation de transfert
5.2 Délégation de boîte mail
La délégation permet à un utilisateur de gérer la boîte d'un autre. C'est une fonctionnalité légitime (assistante/dirigeant) mais qui crée une surface d'attaque si mal configurée.
def audit_gmail_delegation(gmail_service, user_email):
"""Liste les délégations actives sur un compte."""
try:
delegates = gmail_service.users().settings().delegates().list(
userId=user_email
).execute()
items = delegates.get("delegates", [])
if items:
external = [
d for d in items
if not d.get("delegateEmail", "").endswith(f"@{DOMAIN}")
]
return {
"email": user_email,
"total_delegates": len(items),
"external_delegates": external,
"severity": "CRITIQUE" if external else "INFO",
}
except Exception:
pass
return None
5.3 Filtres Gmail avec actions de redirection
Les filtres Gmail peuvent rediriger silencieusement certains emails vers des adresses externes. Moins visibles que le transfert automatique, ils représentent un risque d'exfiltration ciblée.
def audit_gmail_filters(gmail_service, user_email):
"""Détecte les filtres avec redirection vers l'extérieur."""
risky_filters = []
try:
filters = gmail_service.users().settings().filters().list(
userId=user_email
).execute()
for f in filters.get("filter", []):
action = f.get("action", {})
# Vérifier si le filtre redirige vers une adresse externe
if "addLabelIds" in action or "forward" in str(action).lower():
risky_filters.append({
"filter_id": f.get("id"),
"criteria": f.get("criteria", {}),
"action": action,
})
except Exception:
pass
return risky_filters
5.4 Tokens de session actifs (App Passwords)
Les App Passwords (anciennement "Less Secure Apps") permettent un accès IMAP/POP3 sans 2FA. Leur présence signale qu'un client mail ancien (Outlook, Thunderbird en mode IMAP basique) accède au compte en contournant la 2FA.
def audit_app_passwords(security_service, user_email):
"""Détecte les App Passwords (Less Secure Apps)."""
try:
asps = security_service.asps().list(userKey=user_email).execute()
items = asps.get("items", [])
if items:
return {
"email": user_email,
"count": len(items),
"apps": [a.get("name", "") for a in items],
"oldest": min(a.get("creationTime", "") for a in items),
}
except Exception:
pass
return None
---