Aller au contenu

Consentement OAuth, applications furtives & service principals

Tandis que le phishing par consentement utilisateur (étudié dans la Fiche 23) abuse un collaborateur pour obtenir des permissions déléguées, la manipulation d’applications au niveau administratif représente l’échelon suprême de la persistance cloud. En compromettant ou en usurpant des rôles d’administration applicative (Application Administrator ou Cloud Application Administrator), les attaquants passent de sessions utilisateurs éphémères à de véritables backdoors programmatiques autonomes à l’échelle du tenant.

Les cybercriminels déploient cette persistance selon deux vecteurs majeurs :

  1. L’usurpation de service principals existants : l’injection de nouveaux secrets clients (client secrets) ou de certificats asymétriques sur des applications légitimes déjà pourvues de privilèges étendus (outils de sauvegarde, scanners de vulnérabilités, passerelles CI/CD).
  2. Le déploiement d’applications fantômes (rogue apps) : la création de nouvelles inscriptions d’applications dotées de permissions applicatives globales (Directory.ReadWrite.All, RoleManagement.ReadWrite.Directory, Mail.ReadWrite) approuvées par consentement administrateur généralisé.

Ces backdoors s’appuient sur le flux OAuth 2.0 Client Credentials Grant (RFC 6749 Section 4.4). Elles fonctionnent sans aucune présence humaine, n’émettent aucune sollicitation MFA, contournent l’Accès Conditionnel interactif et n’apparaissent que dans les journaux spécifiques des Service Principals.

Ce guide détaille l’architecture des backdoors applicatives, analyse les journaux d’audit et les ServicePrincipalSignInLogs, et fournit des requêtes de détection KQL ainsi que des scripts PowerShell d’éradication.


1. Vecteurs architecturaux : injection de secret vs déploiement d’app fantôme

Section intitulée « 1. Vecteurs architecturaux : injection de secret vs déploiement d’app fantôme »
graph TD
ADMIN[L'Attaquant Détient les Droits Application Admin] --> VECTOR{Stratégie de Persistance}
VECTOR -->|Stratégie A : Cheval de Troie Applicatif| TROJAN[Injection d'un Nouveau Secret sur un SP Existant<br/>Cible : App de Sauvegarde / Monitoring Légitime<br/>Permissions Préexistantes : Directory.ReadWrite.All]
VECTOR -->|Stratégie B : Création d'App Fantôme| ROGUE[Création d'une Nouvelle App & Service Principal<br/>New-MgApplication / New-MgServicePrincipal<br/>Octroi du Consentement Administrateur Global]
TROJAN --> AUTH_FLOW[Flux OAuth 2.0 Client Credentials<br/>POST /token avec client_id + client_secret]
ROGUE --> AUTH_FLOW
AUTH_FLOW --> TOKEN_ISSUE[Émission d'un Jeton JWT Applicatif par le STS<br/>AppId : GUID Légitime ou Pirate<br/>Roles : Permissions Applicatives Globales]
TOKEN_ISSUE --> DIRECT_ACCESS[Accès Direct à l'API Microsoft Graph<br/>Aucun Contexte Utilisateur / Pas de MFA / Totalement Autonome]

1.1 stratégie a : le cheval de troie sur application légitime

Section intitulée « 1.1 stratégie a : le cheval de troie sur application légitime »

C’est la méthode de persistance la plus furtive du cloud. Un tenant d’entreprise héberge en moyenne plusieurs dizaines d’applications d’entreprise approuvées au fil des années, dont beaucoup disposent de privilèges applicatifs considérables.

Plutôt que de créer une nouvelle application visible au SOC, l’attaquant :

  1. Repère les applications existantes détenant des droits applicatifs critiques.
  2. Ajoute un mot de passe d’application (client secret) ou téléverse un certificat RSA sur l’objet application légitime.
  3. Exploite ce secret depuis son infrastructure pour s’authentifier au nom de l’application.

1.2 stratégie b : déploiement d’applications d’entreprise fantômes

Section intitulée « 1.2 stratégie b : déploiement d’applications d’entreprise fantômes »

Si aucune application préexistante ne convient, l’attaquant enregistre une nouvelle application, lui assigne des rôles applicatifs et s’accorde le consentement administrateur :

  • Attribution de RoleManagement.ReadWrite.Directory : permet à l’application d’élever n’importe quel compte au rang de Global Administrator par simple appel d’API.
  • Attribution de Mail.ReadWrite (de type Application) : permet de lire, modifier ou purger les emails de l’intégralité des boîtes aux lettres du tenant, sans restriction à un utilisateur précis.

2. Mécanismes d’authentification : le client credentials grant

Section intitulée « 2. Mécanismes d’authentification : le client credentials grant »

L’exploitation des backdoors de Service Principal s’opère via le flux OAuth 2.0 Client Credentials :

POST /<tenant-id>/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=11111111-2222-3333-4444-555555555555
&client_secret=VALEUR_DU_SECRET_INJECTE_PAR_L_ATTAQUANT
&scope=https://graph.microsoft.com/.default
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/8f3b6a9c-2d1e-4b5a-9f8e-7c6b5a4d3e2f/",
"appid": "11111111-2222-3333-4444-555555555555",
"app_displayname": "Service de Sauvegarde Cloud Entreprise",
"roles": [
"Directory.ReadWrite.All",
"Mail.ReadWrite",
"Files.ReadWrite.All"
],
"idtyp": "app",
"tid": "8f3b6a9c-2d1e-4b5a-9f8e-7c6b5a4d3e2f"
}

Constats forensiques fondamentaux :

  • idtyp: app : confirme qu’il s’agit d’un jeton purement applicatif. Aucun contexte d’utilisateur (upn ou sub utilisateur) n’est présent.
  • roles : contient l’ensemble des permissions applicatives autorisées sur tout le tenant.
  • Ce jeton peut être exploité depuis n’importe quelle adresse IP publique sans validation MFA.

L’investigation impose de corréler deux tables d’audit sous Microsoft Entra ID :

graph TD
BACKDOOR[L'Attaquant Injecte un Secret ou Crée une App] --> AUDIT[Table AuditLogs Entra ID]
EXPLOIT[L'Attaquant se Connecte avec le Secret] --> SP_SIGNIN[Table ServicePrincipalSignInLogs Entra ID]
AUDIT --> A1[Opération : Update application - Certificates and secrets management<br/>InitiatedBy : UPN Admin / IP<br/>Cible : Nom de l'App & AppId]
AUDIT --> A2[Opération : Add app role assignment to service principal<br/>Cible : Nom du rôle e.g. Directory.ReadWrite.All]
SP_SIGNIN --> S1[ServicePrincipalName / AppId<br/>IPAddress : VPS / Proxy Attaquant<br/>AuthenticationProcessingDetails : Client Secret / Certificate]

3.1 trace dans le journal d’audit : injection de secret

Section intitulée « 3.1 trace dans le journal d’audit : injection de secret »

Lors de l’ajout d’un secret client par script ou portail, AuditLogs enregistre :

  • OperationName : "Update application - Certificates and secrets management" OU "Add service principal credentials"
  • Category : ApplicationManagement
  • InitiatedBy : l’identité ou l’application ayant opéré l’injection.
  • TargetResources[0].modifiedProperties : dévoile le nom de la clé, la date de début, d’expiration et son identifiant KeyIdentifier.

4.1 détection de l’ajout de nouveaux secrets OU certificats

Section intitulée « 4.1 détection de l’ajout de nouveaux secrets OU certificats »

Identifier l’injection de tout nouveau secret ou certificat sur une application existante :

AuditLogs
| where TimeGenerated >= ago(30d)
| where OperationName in (
"Update application - Certificates and secrets management",
"Add service principal credentials",
"Add service principal",
"Add application"
)
| extend InitiatorUPN = tostring(InitiatedBy.user.userPrincipalName),
InitiatorIP = tostring(InitiatedBy.user.ipAddress),
InitiatorApp = tostring(InitiatedBy.app.displayName)
| extend TargetAppName = tostring(TargetResources[0].displayName),
AppId = tostring(TargetResources[0].id)
| extend KeyDetails = tostring(TargetResources[0].modifiedProperties)
| project TimeGenerated, OperationName, InitiatorUPN, InitiatorApp, InitiatorIP, TargetAppName, AppId, KeyDetails
| sort by TimeGenerated desc

4.2 détection de l’octroi de rôles applicatifs à haut risque

Section intitulée « 4.2 détection de l’octroi de rôles applicatifs à haut risque »

Repérer lorsqu’une application se voit accorder des privilèges d’administration sensibles :

let HighRiskRoles = dynamic([
"Directory.ReadWrite.All",
"RoleManagement.ReadWrite.Directory",
"AppRoleAssignment.ReadWrite.All",
"Mail.ReadWrite",
"Files.ReadWrite.All",
"User.ReadWrite.All"
]);
AuditLogs
| where TimeGenerated >= ago(30d)
| where OperationName == "Add app role assignment to service principal"
| extend Initiator = tostring(InitiatedBy.user.userPrincipalName)
| extend TargetApp = tostring(TargetResources[0].displayName)
| extend ModifiedProps = TargetResources[0].modifiedProperties
| mv-expand ModifiedProps
| where ModifiedProps.displayName == "AppRole.Value"
| extend GrantedRole = tostring(ModifiedProps.newValue)
| where GrantedRole has_any (HighRiskRoles)
| project TimeGenerated, OperationName, Initiator, TargetApp, GrantedRole
| sort by TimeGenerated desc

4.3 chasse aux connexions anormales de service principals

Section intitulée « 4.3 chasse aux connexions anormales de service principals »

Repérer les authentifications de Service Principals émanant d’hébergeurs VPS ou d’adresses IP inhabituelles :

let HostingASNs = dynamic([14061, 24940, 63949, 16276, 51167]); // DigitalOcean, Hetzner, Linode, OVH, Contabo
ServicePrincipalSignInLogs
| where TimeGenerated >= ago(14d)
| where ResultType == 0
| where AutonomousSystemNumber in (HostingASNs) or Location != "FR" // Ajuster selon le pays légitime
| project TimeGenerated, ServicePrincipalName, AppId, IPAddress, Location,
AutonomousSystemNumber, ResourceDisplayName
| summarize
Signins = count(),
Resources = make_set(ResourceDisplayName),
IPs = make_set(IPAddress)
by ServicePrincipalName, AppId, Location, AutonomousSystemNumber
| sort by Signins desc

5. Playbook forensique d’inventaire et remédiation PowerShell

Section intitulée « 5. Playbook forensique d’inventaire et remédiation PowerShell »

5.1 détection des secrets récemment créés sur tout le tenant

Section intitulée « 5.1 détection des secrets récemment créés sur tout le tenant »

Lister tous les secrets et certificats générés au cours des 30 derniers jours :

Fenêtre de terminal
# Prérequis : Module Microsoft.Graph.Applications
# Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All"
Write-Host "[*] Examen des secrets et certificats applicatifs sur le tenant..." -ForegroundColor Cyan
$recentThreshold = (Get-Date).AddDays(-30)
$allApps = Get-MgApplication -All
$compromisedCandidates = @()
foreach ($app in $allApps) {
# Vérification des secrets (PasswordCredentials)
foreach ($pwd in $app.PasswordCredentials) {
if ($pwd.StartDateTime -ge $recentThreshold) {
$compromisedCandidates += [PSCustomObject]@{
AppDisplayName = $app.DisplayName
AppId = $app.AppId
ObjectId = $app.Id
CredentialType = "Client Secret"
KeyId = $pwd.KeyId
CreatedDate = $pwd.StartDateTime
ExpirationDate = $pwd.EndDateTime
Hint = $pwd.Hint
}
}
}
# Vérification des certificats (KeyCredentials)
foreach ($cert in $app.KeyCredentials) {
if ($cert.StartDateTime -ge $recentThreshold) {
$compromisedCandidates += [PSCustomObject]@{
AppDisplayName = $app.DisplayName
AppId = $app.AppId
ObjectId = $app.Id
CredentialType = "Certificat"
KeyId = $cert.KeyId
CreatedDate = $cert.StartDateTime
ExpirationDate = $cert.EndDateTime
Hint = "Empreinte : $($cert.CustomKeyIdentifier)"
}
}
}
}
if ($compromisedCandidates) {
Write-Warning "[!] $($compromisedCandidates.Count) secret(s) créés depuis moins de 30 jours identifiés :"
$compromisedCandidates | Format-Table AppDisplayName, CredentialType, CreatedDate, ExpirationDate, KeyId
} else {
Write-Host "[+] Aucun secret applicatif suspect récemment créé." -ForegroundColor Green
}

5.2 révocation immédiate d’un secret illégitime

Section intitulée « 5.2 révocation immédiate d’un secret illégitime »
Fenêtre de terminal
# Révocation d'un secret spécifique sur une application compromise :
$appObjectId = "11111111-2222-3333-4444-555555555555"
$rogueKeyId = "99999999-8888-7777-6666-555555555555"
Remove-MgApplicationPassword -ApplicationId $appObjectId -KeyId $rogueKeyId
Write-Host "[+] Secret client malveillant révoqué avec succès." -ForegroundColor Green