☁️Entra IDApplicationsMonitoringRisk ProtectionConditional Access

Entra Service Principal : Détection des Anomalies de Connexion, Angles Morts de l'Identité de Charge de Travail

Les service principals ne peuvent pas effectuer de MFA, échappent à l'Accès Conditionnel classique, et leurs connexions vivent dans un flux de logs séparé. Apprenez à établir une ligne de base et détecter les anomalies sur les identités détenant vos permissions Graph les plus larges.

Younes AZABARPar Younes AZABAR18 min de lecture
Entra Service Principal : Détection des Anomalies de Connexion, Angles Morts de l'Identité de Charge de Travail

Entra Service Principal : Détection des Anomalies de Connexion, Identité de Charge de Travail Définie

Entra Service Principal : la détection des anomalies de connexion, la surveillance de l'identité de charge de travail qui établit une ligne de base sur la façon dont les comptes machine s'authentifient, est le contrôle que la plupart des tenants ne configurent jamais. Tous les autres contrôles d'identité d'un tenant mature — campagnes d'enrôlement MFA, méthodes d'authentification résistantes au phishing, Accès Conditionnel basé sur le risque — sont construits autour d'un humain présent au clavier. Les service principals n'ont pas d'humain, et ces contrôles ne les atteignent tout simplement pas.

Microsoft est explicite sur ce qui rend ces identités différentes. Une identité de charge de travail est une identité qui permet à une application ou à un service principal d'accéder à des ressources, et la documentation de Microsoft elle-même liste trois propriétés qui la distinguent d'un compte utilisateur : les identités de charge de travail « Ne peuvent pas effectuer d'authentification multifacteur », « N'ont souvent aucun processus de cycle de vie formel », et « Doivent stocker leurs identifiants ou secrets quelque part ».

En lisant ces trois points ensemble, l'exposition devient évidente. L'identifiant est une chaîne de caractères posée dans une variable de pipeline, un Key Vault, un fichier appsettings, ou une couche d'image de conteneur. Il n'y a aucun second facteur derrière, aucun cycle de vie qui l'expire au dernier jour d'un départ, et — parce qu'une connexion de service principal n'implique aucun utilisateur — rien qui paraisse inhabituel pour une pile d'alerte centrée sur l'humain. Une fois cette chaîne divulguée, l'attaquant s'authentifie exactement comme le fait l'application, et la connexion réussit dès la première tentative.

⚠️

⚠️ Avertissement : Après la fuite d'un secret de service principal, il n'y a pas de mot de passe à spray, pas d'invite MFA à fatiguer, et pas d'utilisateur à phisher. Le seul signal qui reste est la forme des connexions : combien, depuis où, sur quel réseau, avec quel identifiant.

Pourquoi Votre Couverture MFA et Accès Conditionnel Ne S'Applique Pas

L'Accès Conditionnel est le contrôle que les administrateurs supposent tout couvrir. Ce n'est pas le cas — les identités de charge de travail sont l'un des nombreux endroits où sa couverture s'arrête discrètement, aux côtés des erreurs de portée et d'exclusion cataloguées dans Failles d'Accès Conditionnel Azure : Contournement du MFA. La documentation de Microsoft indique que les politiques d'Accès Conditionnel « historiquement, ne s'appliquaient qu'aux utilisateurs lorsqu'ils accèdent à des applications et services », et que la prise en charge des service principals est une capacité distincte appelée Accès Conditionnel pour les identités de charge de travail.

Cette capacité distincte porte des restrictions qui déterminent si votre politique fait réellement quelque chose :

ContrainteCe qu'indique la documentation de Microsoft
Licence« Des licences Workload Identities Premium sont requises pour créer ou modifier des politiques d'Accès Conditionnel ciblant des service principals. » Les politiques existantes continuent de fonctionner dans les annuaires non licenciés mais ne peuvent pas être modifiées.
Portée« La politique peut s'appliquer aux service principals mono-tenant enregistrés dans votre tenant. Les applications SaaS Microsoft et tierces, y compris les applications multi-tenant, ne sont pas couvertes par ces politiques. Les identités managées ne sont pas couvertes par la politique. »
Affectation par groupe« Bien que les service principals puissent être ajoutés à des groupes, les politiques d'Accès Conditionnel affectées à un groupe contenant un service principal ne sont pas appliquées à ce service principal. » Il doit être affecté directement en tant qu'identité de charge de travail.
Contrôles disponiblesSous Accorder, « Bloquer l'accès est la seule option disponible. » Il n'y a pas de « exiger le MFA » comme repli.

La règle d'affectation par groupe mérite d'être soulignée, car elle échoue silencieusement. Un administrateur qui place des service principals dans un groupe de sécurité déjà ciblé par une politique d'Accès Conditionnel verra une politique qui semble correctement scopée dans le portail et qui n'applique absolument rien à ces service principals. Rien ne génère d'erreur. Rien n'avertit. La politique n'est simplement pas évaluée pour eux.

Les conditions prises en charge sont volontairement étroites : bloquer les service principals « en dehors des plages d'IP publiques connues », bloquer « en fonction du risque détecté par Microsoft Entra ID Protection », et les contextes d'authentification. Localisation et risque — pas l'appareil, pas le MFA, pas la conformité.

Il existe aussi une asymétrie entre détecter le risque et le bloquer, facile à inverser. ID Protection « détecte le risque sur les applications mono-tenant, SaaS non-Microsoft, et multi-tenant », tandis que l'application de l'Accès Conditionnel n'atteint que les service principals mono-tenant. Les identités managées échappent aux deux. Ainsi, pour une application SaaS multi-tenant dans votre tenant, on peut vous signaler qu'elle est compromise sans pouvoir la bloquer avec une politique d'identité de charge de travail — ce qui fait de la détection et de la réponse, et non de la prévention, le contrôle opérant.

Où Vivent Réellement les Connexions des Service Principals

La deuxième raison pour laquelle ces connexions ne sont pas surveillées, c'est que la plupart des requêtes de chasse ne les touchent jamais. Microsoft Entra enregistre quatre types de logs de connexion : connexions utilisateur interactives, connexions utilisateur non interactives, connexions de service principal, et connexions d'identité managée. Un SOC qui a construit ses détections sur les connexions utilisateur ne regarde pas un sous-ensemble filtré d'une même table — il regarde un flux entièrement différent.

Le flux des service principals est autonome. Microsoft le décrit clairement : « Contrairement aux connexions utilisateur interactives et non interactives, les connexions de service principal n'impliquent pas d'utilisateur. Ce sont plutôt des connexions par tout compte non-utilisateur, comme des applications ou des service principals... Dans ces connexions, l'application ou le service fournit son propre identifiant, comme un certificat ou un secret d'application, pour s'authentifier ou accéder à des ressources. »

Lorsqu'elles sont exportées vers un espace de travail Log Analytics via les Paramètres de diagnostic, elles atterrissent dans la table AADServicePrincipalSignInLogs — pas SigninLogs. Si vous n'avez jamais activé cette catégorie de log, les requêtes de la section suivante retournent zéro ligne, et zéro ligne ressemblera exactement à un environnement sain.

La rétention est l'autre moitié du prérequis. La référence de rétention des données de Microsoft donne aux logs de connexion sept jours sur Microsoft Entra ID Free et 30 jours sur P1 ou P2. Une détection comportementale a besoin d'une ligne de base plus une fenêtre récente dans ce budget — on ne peut pas construire un profil de 90 jours à partir d'un log de 30 jours. La configuration de l'export est couverte dans Journalisation Entra ID : Rétention, Paramètres de Diagnostic, Lacunes d'Export SIEM ; considérez-le comme un prérequis strict pour tout ce qui suit.

Voici les colonnes qui portent le signal :

ColonnePourquoi c'est important
AppId, ServicePrincipalNameL'identité à baseliner. AppId est la clé de regroupement stable.
IPAddress, LocationDetails, AutonomousSystemNumberD'où la charge de travail s'est authentifiée. Le réseau d'une charge de travail est généralement bien plus stable que celui d'un humain.
ServicePrincipalCredentialKeyId, ClientCredentialTypeQuel identifiant a été utilisé, et s'il s'agissait d'un secret client ou d'une assertion client.
ResourceDisplayName, ResourceIdentityCe qu'elle a atteint. De nouvelles ressources cibles sont un signal de pivot fort.
ResultType"0" pour un succès. Les échecs relèvent d'une investigation différente.
ConditionalAccessStatusSi une politique d'identité de charge de travail a même été évaluée.
FederatedCredentialIdRenseigné lorsqu'un identifiant d'identité fédérée a été utilisé à la place d'un secret stocké.

La Chaîne d'Attaque Après la Fuite d'un Secret

Étape 1 — L'identifiant s'échappe

Le secret quitte l'environnement : committé dans un dépôt public, capturé depuis un log de build, extrait d'un poste de travail développeur compromis, ou acheté. La détection Leaked Credentials de Microsoft pour les identités de charge de travail existe précisément parce que c'est routinier — elle vérifie les identifiants récupérés « sur GitHub, le dark web, des sites de paste, ou d'autres sources » contre les identifiants valides du tenant. Le même service couvre les comptes utilisateur, où l'échec récurrent porte sur la réponse plutôt que sur la détection — voir Entra ID Risk Protection : Identifiants Divulgués, Utilisateurs à Risque Non Corrigés.

Étape 2 — Une authentification qui semble entièrement légitime

L'attaquant effectue un flux OAuth client credentials avec le secret volé. Aucun MFA n'est possible, donc aucun ne manque. Si aucune politique d'identité de charge de travail n'est directement affectée, ConditionalAccessStatus reflète une connexion que rien n'a évaluée. Le résultat est une connexion réussie qui est, octet pour octet, la même opération que l'application réelle effectue mille fois par jour. Seul le contexte environnant diffère.

Étape 3 — Persistance par ajout d'un identifiant

Plutôt que de dépendre d'un secret susceptible d'être tourné, un attaquant disposant de privilèges suffisants ajoute son propre identifiant à l'objet application ou service principal. L'identité dispose désormais de deux identifiants valides, et révoquer celui qui a fuité ne change rien. Cette étape laisse deux traces : une modification d'annuaire dans le log d'audit, et — à chaque connexion suivante — un ServicePrincipalCredentialKeyId qui n'est jamais apparu auparavant pour cet AppId.

Étape 4 — Agir via Graph

Les permissions d'application du service principal constituent désormais le rayon d'impact, et les permissions d'application ne sont contraintes par aucun droit d'utilisateur. Des attributions à l'échelle du tenant comme Directory.ReadWrite.All ou RoleManagement.ReadWrite.Directory transforment un secret divulgué en contrôle sur tout l'annuaire ; voir Entra App Registration : Permissions Graph API Dangereuses pour la manière dont ces attributions sont inventoriées et réduites. La détection Suspicious API Traffic de Microsoft cible cette étape, se déclenchant « lorsqu'un trafic GraphAPI anormal ou une énumération d'annuaire par un service principal est observé ».

Détection

La détection comportementale repose ici sur une propriété qui joue en faveur du défenseur : les charges de travail sont d'une régularité ennuyeuse. Une tâche de synchronisation nocturne s'exécute à la même heure, depuis la même IP de sortie, contre la même ressource, avec le même identifiant. Les humains sont erratiques ; l'automatisation ne l'est pas. Cette régularité rend la déviation significative — et c'est pourquoi les détections équivalentes pour les comptes humains, comme le déplacement impossible et le rejeu de token AiTM, doivent tolérer bien plus de bruit que les requêtes ci-dessous.

IndicateurOù chercherChamps clésCe que ça signifie
Pic de volume de connexionsAADServicePrincipalSignInLogsAppId, décompte dans le tempsCollecte massive ou énumération via un token volé
Pays ou ASN vu pour la première foisAADServicePrincipalSignInLogsLocationDetails, AutonomousSystemNumberLa charge de travail s'est authentifiée depuis une infrastructure qu'elle n'a jamais utilisée
Nouvel identifiant en usageAADServicePrincipalSignInLogsServicePrincipalCredentialKeyId, ClientCredentialTypeUn identifiant ajouté par un attaquant, ou une rotation non gérée
Nouvelle ressource cibleAADServicePrincipalSignInLogsResourceDisplayNameL'identité atteint au-delà de son rôle habituel
Identifiant ajouté à l'objetLogs d'audit EntraModifications d'identifiant application / service principalL'étape de persistance de l'Étape 3
Identifiant divulgué / connexion suspecteDétections d'identité de charge de travail d'ID ProtectionriskyServicePrincipals, servicePrincipalRiskDetectionsCorrélation côté Microsoft que vous n'avez pas à construire

Pic de volume par rapport à une ligne de base par identité

// Workload identity volume spike: last 24h vs the preceding daily baseline
let Recent = 1d;
let Lookback = 30d;
let MinRecent = 20;       // suppress low-volume identities
let MinBaselineDays = 7;  // suppress identities we have not observed long enough
let Baseline =
    AADServicePrincipalSignInLogs
    | where TimeGenerated between (ago(Lookback) .. ago(Recent))
    | where ResultType == "0"
    | summarize Total = count(), Days = dcount(startofday(TimeGenerated)) by AppId
    | where Days >= MinBaselineDays and Total > 0
    | extend BaselineDaily = todouble(Total) / todouble(Days);
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(Recent)
| where ResultType == "0"
| summarize RecentCount = count(), Name = take_any(ServicePrincipalName) by AppId
| where RecentCount >= MinRecent
| join kind=inner (Baseline) on AppId
| extend SpikeRatio = round(RecentCount / BaselineDaily, 1)
| where SpikeRatio >= 5
| project Name, AppId, RecentCount, BaselineDaily = round(BaselineDaily, 1), SpikeRatio
| order by SpikeRatio desc

Pays ou système autonome vu pour la première fois

// A workload identity authenticating from a country or ASN it has never used
let Recent = 1d;
let Lookback = 30d;
let Known =
    AADServicePrincipalSignInLogs
    | where TimeGenerated between (ago(Lookback) .. ago(Recent))
    | where ResultType == "0"
    | extend Country = tostring(parse_json(tostring(LocationDetails)).countryOrRegion)
    | where isnotempty(Country)
    | summarize KnownCountries = make_set(Country),
                KnownASNs = make_set(AutonomousSystemNumber) by AppId;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(Recent)
| where ResultType == "0"
| extend Country = tostring(parse_json(tostring(LocationDetails)).countryOrRegion)
| where isnotempty(Country)
| join kind=inner (Known) on AppId
| where array_length(KnownCountries) > 0
| where not(set_has_element(KnownCountries, Country))
     or not(set_has_element(KnownASNs, AutonomousSystemNumber))
| project TimeGenerated, ServicePrincipalName, AppId, Country,
          ASN = AutonomousSystemNumber, IPAddress, ResourceDisplayName,
          NewCountry = not(set_has_element(KnownCountries, Country))
| order by TimeGenerated desc

Un identifiant jamais utilisé auparavant

// Sign-in using a credential key this workload identity has never presented
let Recent = 1d;
let Lookback = 30d;
let KnownKeys =
    AADServicePrincipalSignInLogs
    | where TimeGenerated between (ago(Lookback) .. ago(Recent))
    | where ResultType == "0" and isnotempty(ServicePrincipalCredentialKeyId)
    | summarize KnownKeyIds = make_set(ServicePrincipalCredentialKeyId) by AppId;
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(Recent)
| where ResultType == "0" and isnotempty(ServicePrincipalCredentialKeyId)
| join kind=inner (KnownKeys) on AppId
| where not(set_has_element(KnownKeyIds, ServicePrincipalCredentialKeyId))
| project TimeGenerated, ServicePrincipalName, AppId,
          ServicePrincipalCredentialKeyId, ClientCredentialType,
          IPAddress, ResourceDisplayName
| order by TimeGenerated desc

Associez cette dernière requête à l'entrée du log d'audit enregistrant l'ajout de l'identifiant à l'objet application ou service principal. Une nouvelle clé d'identifiant sans demande de changement approuvée correspondante est le signal à la plus haute fiabilité de cet article.

💡

💡 Astuce : Les seuils ci-dessus (>= 5x, >= 20 connexions récentes, >= 7 jours de ligne de base) sont des garde-fous, pas des lois physiques. Chacun existe pour éliminer un faux positif précis : une toute nouvelle intégration sans historique afficherait sinon un ratio infini, et une seule connexion contre une ligne de base de 0,1/jour se lirait sinon comme un pic décuplé. Ajustez-les sur vos propres données avant d'alerter.

Deux limites méritent d'être reconnues honnêtement. D'abord, une ligne de base plus courte que l'idéal est inévitable quand le log lui-même expire en 7 à 30 jours — si vous n'avez pas exporté vers un espace de travail à rétention plus longue, votre ligne de base est plafonnée par votre licence. Ensuite, un changement de déploiement légitime (une nouvelle région, un agent de build migré, une rotation planifiée) produit exactement la même signature qu'une intrusion. Microsoft reconnaît le même effet dans sa propre détection : « Les connexions initiées après un changement de configuration autorisé peuvent déclencher cette détection. » Ces requêtes produisent des files d'investigation, pas des verdicts.

La détection Suspicious Sign-ins de Microsoft pour les identités de charge de travail vaut la peine d'être activée en complément. Elle « apprend les lignes de base du comportement de connexion des identités de charge de travail de votre tenant », prend « entre 2 et 60 jours », et se déclenche sur des éléments inconnus « adresse IP / ASN, ressource cible, user agent, changement d'IP hébergée/non hébergée, pays de l'IP, type d'identifiant » — le même ensemble de fonctionnalités, corrélé côté Microsoft. Les clients sans Workload Identities Premium « continuent de recevoir toutes les détections avec des détails de rapport limités ».

Remediation / Remédiation

💡

💡 Victoire rapide : Activez la catégorie de log de connexion des service principals dans les Paramètres de diagnostic et routez-la vers un espace de travail Log Analytics. Sans cela, chaque requête ci-dessus retourne zéro ligne — et zéro ligne est indiscernable d'un tenant sain.

  1. Exportez le flux et étendez la fenêtre. Routez les logs de connexion des service principals vers un espace de travail dont la rétention dépasse le défaut de 7 ou 30 jours, afin qu'une ligne de base soit seulement possible. Voir Journalisation Entra ID : Rétention, Paramètres de Diagnostic, Lacunes d'Export SIEM.

  2. Supprimez le secret plutôt que de le protéger. Là où la charge de travail s'exécute sur Azure, utilisez des identités managées pour que la plateforme gère les identifiants. Là où elle s'exécute hors d'Azure — GitHub Actions, Kubernetes, GCP, AWS — utilisez la fédération d'identité de charge de travail, qui échange un token d'un fournisseur d'identité externe contre un token d'accès. Le raisonnement de Microsoft est direct : les identifiants stockés « présentent un risque de sécurité et doivent être stockés en sécurité et tournés régulièrement », tandis que la fédération « élimine le risque de fuite de secrets ou d'expiration de certificats ». Un identifiant qui n'existe pas ne peut pas fuiter.

  3. Scopez une politique d'Accès Conditionnel pour identité de charge de travail — correctement. Créez une politique basée sur la localisation sous Utilisateurs ou identités de charge de travail → Identités de charge de travail, ciblez Toutes les ressources, incluez N'importe quelle localisation et excluez les localisations nommées depuis lesquelles votre charge de travail s'exécute légitimement. Rappelez-vous les trois pièges : cela nécessite Workload Identities Premium, cela doit être affecté au service principal directement plutôt que via un groupe, et Bloquer l'accès est le seul contrôle d'octroi. Enregistrez-la d'abord en mode Rapport seul et examinez les résultats dans la vue Connexions des service principals avant d'appliquer.

  4. Inventoriez et tournez les identifiants. Les recommandations de remédiation de Microsoft pour une identité de charge de travail compromise consistent à inventorier chaque identifiant sur les objets service principal et application, en ajouter un nouveau (certificats x509 préférés), supprimer les identifiants compromis — « Si vous pensez que le compte est à risque, nous recommandons de supprimer tous les identifiants existants » — et faire tourner tous les secrets Key Vault que cette identité pouvait atteindre. L'hygiène de rotation en général est couverte dans Entra App Registration : Rotation des Secrets d'Identifiants.

  5. Réduisez le rayon d'impact avant d'en avoir besoin. Les dégâts qu'un secret divulgué peut causer sont bornés par les permissions d'application qui lui sont accordées. Supprimez les attributions Graph à l'échelle du tenant qui ne sont pas réellement nécessaires, et réexaminez les service principals détenant des rôles d'annuaire — le volet posture statique de ce problème est couvert dans Rôle Admin de Service Principal Entra Sur-Privilégié.

  6. Vérifiez. Relancez les trois requêtes. Un tenant correctement instrumenté retourne des lignes correspondant à vos événements de déploiement connus et rien d'autre ; un tenant qui ne retourne littéralement rien a un problème de journalisation, pas un carnet de santé propre. La place de cette vérification dans une revue complète du tenant est détaillée dans Comment Auditer la Sécurité Microsoft Entra ID (Azure AD) : Guide Pratique de Revue.

Comment EtcSec Détecte Cela

Le moteur d'audit etc-collector embarque deux détecteurs qui couvrent exactement cette télémétrie, tous deux notés Élevé :

  • RISK_SP_SIGNIN_SPIKE — signale un service principal qui s'est connecté bien plus souvent au cours des dernières 24 heures que sur les jours précédents de la fenêtre collectée. Le regroupement se fait par appId, uniquement sur les types d'événements de connexion machine, de sorte qu'une connexion utilisateur interactive qui porterait un appId ne peut pas diluer le taux mesuré d'une application.
  • RISK_UNUSUAL_GEO_ADMIN — signale un compte privilégié qui s'est connecté avec succès depuis un pays qu'il n'avait pas utilisé plus tôt dans la fenêtre collectée. C'est le jumeau humain de la même télémétrie : la même logique de « géographie vue pour la première fois » appliquée aux admins plutôt qu'aux charges de travail.

Les deux sont construits autour des garde-fous décrits ci-dessus plutôt qu'autour d'un ratio brut : une identité avec trop peu d'historique de ligne de base reste silencieuse, une identité avec trop peu de connexions récentes reste silencieuse, un ensemble de pays connus vide reste silencieux, et les connexions sans pays résolu sont ignorées plutôt que comptées comme un nouveau pays. Le filtrage sur le succès diffère entre les deux : RISK_UNUSUAL_GEO_ADMIN ne compte que les connexions réussies, des deux côtés de la comparaison, de sorte qu'il ne chevauche pas RISK_FAILED_SIGNIN_BURST, qui couvre déjà les schémas de tentatives échouées ; RISK_SP_SIGNIN_SPIKE mesure son taux sur chaque connexion machine de la fenêtre, réussie ou échouée.

La fenêtre récente est ancrée sur l'événement collecté le plus récent plutôt que sur l'heure murale, de sorte que rejouer un audit des heures plus tard compare les mêmes jours.

ℹ️

ℹ️ Note : EtcSec vérifie automatiquement cette vulnérabilité lors de chaque audit AD/Azure. Lancez un audit gratuit pour vérifier votre environnement.