🟢 Niveau Débutant — Fondamentaux SC-200
30+ leçons pour comprendre les bases de Microsoft Sentinel, KQL et les opérations de sécurité dans le cloud.
Module 1 : Introduction à Microsoft Sentinel
1.1 Qu'est-ce que Microsoft Sentinel ?
Microsoft Sentinel est un SIEM (Security Information and Event Management) cloud-native et une plateforme SOAR (Security Orchestration, Automation and Response). Contrairement aux solutions SIEM traditionnelles, Sentinel est entièrement hébergé dans le cloud Azure.
Pourquoi c'est important : Imaginez que vous devez surveiller 500 serveurs, 2 000 postes et trois applications cloud sans outil central : vous passeriez vos journées à ouvrir dix consoles différentes. Sentinel regroupe tous ces signaux dans un seul workspace interrogeable en KQL. Sans SIEM centralisé, une attaque qui traverse plusieurs systèmes reste invisible parce que personne ne corrèle les indices.
L'analogie qui aide : Imaginez la tour de contrôle d'un aéroport : chaque avion envoie sa position, et les contrôleurs voient tout sur un seul écran radar. Sentinel est cette tour de contrôle pour votre système d'information : chaque log est un avion, et vous les voyez tous au même endroit.
- Collecte de données — Ingestion de logs depuis Azure, Microsoft 365, serveurs on-premise et services tiers
- Détection intelligente — Corrélation d'événements avec des règles analytics et des modèles ML
- Investigation — Analyse interactive avec des graphes d'entités et KQL
- Réponse automatisée — Playbooks Logic App et règles d'automation
- Menace intelligente — Intégration native des IoC Microsoft Threat Intelligence
Démonstration pas à pas : Suivez le raisonnement : un attaquant se connecte à 3h12 depuis une IP du Brésil sur le compte de Marie, puis à 3h15 un fichier suspect apparaît sur son poste. Sans Sentinel, vous avez deux alertes isolées dans deux consoles différentes. Avec Sentinel, une règle analytics corrèle SigninLogs et DeviceFileEvents sur la même entité Account dans une fenêtre de 30 minutes et crée un seul incident de sévérité Haute. C'est cette corrélation automatique qui transforme deux bruits en un signal exploitable.
💡 Architecture Sentinel
Sentinel se compose de : Tables (stockage des logs dans Log Analytics), Règles Analytics (détections), Connecteurs (ingestion de données), Playbooks (automatisation), Workbooks (visualisation) et Fusées d'investigation.
Pièges classiques : 90 % des débutants confondent SIEM et SOAR : le SIEM collecte et détecte, le SOAR automatise la réponse via les playbooks. À l'examen, si la question parle d'isoler automatiquement un poste, la réponse est le playbook (SOAR), pas la règle analytics. Autre piège : croire que Sentinel protège par lui-même — sans connecteurs configurés, il ne voit strictement rien.
À vous de jouer : Question de contrôle : vous activez Sentinel dans un workspace vide, sans connecter aucune source. Combien d'alertes recevrez-vous le premier jour, et pourquoi ?
Voir la réponse
Zéro. Sentinel n'a aucun capteur intégré : il ne peut analyser que les données ingestées dans le workspace. Tant qu'aucun connecteur (Azure AD, Defender, syslog) n'envoie de logs, les règles analytics s'exécutent sur des tables vides et ne déclenchent rien.
1.2 Composants Clés de Sentinel
- Workspace Log Analytics — Conteneur de données Sentinel où tous les logs sont stockés et interrogés via KQL
- Règles Analytics — Détections basées sur des requêtes KQL planifiées ou des matchs de scheduled rules
- Connecteurs de données — Sources d'ingestion : Azure AD, Microsoft 365, pare-feu, serveurs, etc.
- Fusées d'investigation — Graphes interactifs d'entités pour explorer les relations entre objets
- Workbooks — Tableaux de bord personnalisés pour la visualisation et le reporting
- Incidents — Regroupement des alertes en incidents pour l'analyse et la réponse
- Playbooks — Automatisation basée sur Azure Logic Apps pour la réponse aux incidents
Pourquoi c'est important : Connaître les sept composants par coeur vous évite de chercher pendant vingt minutes où cliquer quand un incident arrive à 2h du matin. Chaque composant a un rôle précis : le connecteur apporte la donnée, la règle analytics la transforme en alerte, l'incident regroupe les alertes, le playbook réagit, le workbook affiche. Si vous confondez ces rôles, vous configurez la réponse au mauvais endroit.
L'analogie qui aide : Imaginez une brigade de cuisine : les connecteurs sont les livreurs qui apportent les ingrédients, les tables Log Analytics sont le garde-manger, les règles analytics sont les cuisiniers qui repèrent un plat brûlé, les incidents sont les assiettes à renvoyer, les playbooks sont les commis qui exécutent les ordres automatiquement, et les workbooks sont la carte affichée en salle.
Démonstration pas à pas : Prenez un cas concret et suivez la chaîne : 1) le connecteur Azure AD ingère une connexion suspecte dans SigninLogs ; 2) une règle analytics planifiée exécute sa requête KQL toutes les 5 minutes et génère une alerte ; 3) Sentinel regroupe cette alerte avec deux autres dans un seul incident ; 4) une règle d'automation déclenche un playbook qui isole le poste ; 5) vous visualisez le tout dans un workbook. Retenez l'ordre : Connecteur, Table, Règle, Alerte, Incident, Playbook. L'examen adore tester cet ordre.
Pièges classiques : confondre workbooks (visualisation, tableaux de bord) et playbooks (automatisation, Logic Apps) — un seul mot change, le rôle est opposé. Autre confusion : règles analytics (créent des alertes à partir de requêtes KQL) contre règles d'automation (agissent sur des alertes déjà créées : assigner, fermer, lancer un playbook).
À vous de jouer : Question de contrôle : une alerte critique vient d'arriver et vous voulez isoler automatiquement le poste concerné. Quel composant utilisez-vous : règle analytics, workbook ou playbook ?
Voir la réponse
Le playbook. La règle analytics a déjà fait son travail en créant l'alerte, et le workbook ne fait qu'afficher. Seul le playbook (Azure Logic App) exécute une action de réponse comme l'isolation via l'API Defender for Endpoint.
1.3 Modèle de Responsabilité Partagée
Comme tout service cloud, Sentinel suit le modèle de responsabilité partagée :
Pourquoi c'est important : Le jour où votre direction vous demande qui est responsable si Sentinel tombe en panne un dimanche, vous devez répondre sans hésiter : Microsoft garantit la disponibilité du service, mais la qualité de la détection repose entièrement sur votre configuration. Comprendre cette frontière vous évite de croire à tort que le cloud vous protège par défaut.
L'analogie qui aide : Imaginez que vous louez un appartement sécurisé : le propriétaire (Microsoft) entretient les murs, les serrures de l'immeuble et l'électricité, mais c'est vous qui décidez de fermer votre porte à clé, d'installer une alarme (règles analytics) et de choisir qui a les clés (connecteurs, accès). L'immeuble est sûr, votre appartement ne l'est que si vous l'équipez.
- Microsoft est responsable de : la sécurité de l'infrastructure, la disponibilité du service, la maintenance du SIEM
- Vous êtes responsable de : la configuration des connecteurs, les règles de détection, la gestion des incidents, l'analyse des menaces
Démonstration pas à pas : Raisonnez cas par cas : le workspace Sentinel est indisponible pendant 2 heures à cause d'une panne Azure — responsabilité Microsoft, couverte par le SLA. En revanche, un attaquant se connecte avec un compte compromis parce que vous n'aviez pas activé le connecteur Azure AD — responsabilité de votre équipe. Règle simple à appliquer : tout ce qui est infrastructure et disponibilité relève de Microsoft ; tout ce qui est configuration, contenu et analyse relève de vous.
⚠️ Point important pour l'examen
Sentinel ne détecte pas les menaces par lui-même — il dépend de la qualité des connecteurs configurés et des règles analytics déployées. Un workspace sans données ingestées ne produit aucune alerte.
Pièges classiques : À l'examen, on vous piège avec des formulations comme « Microsoft s'occupe de la détection des menaces dans Sentinel ». C'est faux : Microsoft fournit le moteur, vous fournissez les règles et les données. Retenez aussi que la ligne de partage dépend du service (IaaS, PaaS, SaaS), mais pour Sentinel en SaaS elle passe entre la plateforme (Microsoft) et le contenu (vous).
À vous de jouer : Question de contrôle : un ransomware chiffre des fichiers parce qu'aucune règle analytics ne surveillait les événements 4663. Qui est responsable : Microsoft ou votre équipe SOC ?
Voir la réponse
Votre équipe SOC. La plateforme fonctionnait normalement ; c'est l'absence de règle de détection — donc un défaut de configuration — qui a permis à l'attaque de passer inaperçue. C'est exactement la partie « vous » du modèle partagé.
Module 2 : Kusto Query Language (KQL) — Bases
2.1 Syntaxe de base de KQL
KQL est le langage de requête utilisé dans Log Analytics, Sentinel, Microsoft Defender et Azure Data Explorer. Il est similaire à SQL mais optimisé pour l'analyse de logs.
Pourquoi c'est important : KQL est l'outil que vous utiliserez chaque jour comme analyste SOC : trier un incident, c'est écrire une requête ; chasser une menace, c'est écrire une requête ; prouver une compromission, c'est écrire une requête. Sans la syntaxe de base (table, where, project), vous êtes aveugle dans Sentinel. C'est aussi le sujet le plus interrogé de l'examen SC-200.
L'analogie qui aide : Imaginez que vous donnez des ordres à un bibliothécaire ultra-rapide : « Va au rayon SecurityEvent (la table), ne garde que les livres d'hier sur les échecs de connexion (where), montre-moi seulement le titre, l'auteur et la date (project), et ramène-m'en dix (take) ». Chaque opérateur séparé par un pipe est une instruction simple, et la requête entière est la liste de courses.
// Structure de base d'une requête KQL
SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 4625
| project TimeGenerated, Computer, Account, EventID
| take 10
Démonstration pas à pas : Déroulons la requête d'exemple ligne par ligne : 1) SecurityEvent — vous pointez la table des événements Windows ; 2) where TimeGenerated supérieur à ago(24h) — vous ne gardez que les dernières 24 heures, sinon vous scannez des mois de données ; 3) where EventID égal à 4625 — vous filtrez sur les échecs d'ouverture de session uniquement ; 4) project — vous n'affichez que 4 colonnes utiles au lieu de 40 ; 5) take 10 — vous limitez à 10 lignes pour vérifier le résultat avant d'élargir. Retenez la méthode : filtrer tôt, projeter peu, limiter d'abord.
Pièges classiques : 90 % des erreurs de débutants : utiliser un seul signe égal au lieu de deux dans where (en KQL le test d'égalité s'écrit avec deux signes), oublier le pipe en début de ligne, et confondre take (échantillon rapide, ordre non garanti) avec une vraie limite triée. À l'examen, take ne garantit jamais l'ordre des lignes : si la question exige les 10 derniers événements, il faut order by TimeGenerated desc suivi de take 10, jamais take 10 seul.
À vous de jouer : Question de contrôle : que retourne SecurityEvent suivi de take 10 : les 10 derniers événements, oui ou non, et pourquoi ?
Voir la réponse
Non. take retourne 10 lignes quelconques, sans garantie d'ordre — c'est un échantillon pour tester rapidement une requête. Pour obtenir les 10 derniers événements, vous devez trier d'abord : SecurityEvent, order by TimeGenerated desc, puis take 10.
2.2 Opérateurs de base
where — Filtre les résultats (comme SQL WHERE). Utilise les opérateurs ==, !=, contains, startswith, has
project — Sélectionne les colonnes à afficher (comme SQL SELECT)
take — Retourne N lignes aléatoires (comme SQL SAMPLE)
limit — Identique à take, retourne les N premières lignes
order by — Trie les résultats (asc ou desc)
summarize — Agrège les données (comme SQL GROUP BY)
count — Compte le nombre de résultats
Pourquoi c'est important : Avec sept opérateurs (where, project, take, limit, order by, summarize, count), vous couvrez 80 % des besoins quotidiens d'un analyste : filtrer un incident, compter les échecs par compte, trier par sévérité. L'examen SC-200 teste chacun d'eux avec des questions du type « quel opérateur utiliser pour... », donc les confondre coûte des points faciles.
L'analogie qui aide : Imaginez une passoire à étages en cuisine : where garde seulement les gros morceaux, project choisit les colonnes comme on choisit les couverts à mettre sur la table, summarize fait des tas par catégorie comme on trie des chaussettes par couleur en les comptant, order by range les tas du plus grand au plus petit, et take goûte une cuillerée pour vérifier.
// Exemples de requêtes de base
SecurityEvent
| where EventID == 4624
| summarize count() by Account
| order by count_ desc
SigninLogs
| where ResultType == 0
| summarize count() by UserPrincipalName
| take 20
Démonstration pas à pas : Déroulez cet exemple : SecurityEvent, where EventID égal à 4624, summarize count() by Account, order by count_ desc. Étape 1 : on ne garde que les ouvertures de session réussies (4624). Étape 2 : summarize regroupe les lignes par compte et compte — la table passe de milliers de lignes à une ligne par compte, avec une colonne count_ créée automatiquement (notez le suffixe underscore). Étape 3 : order by trie pour voir en premier le compte le plus actif. Si un compte de service affiche soudain 4 000 connexions, vous venez de repérer une anomalie.
Pièges classiques : Confusions les plus fréquentes à l'examen : project (choisit des colonnes, garde toutes les lignes) contre summarize (réduit les lignes en agrégeant) ; take contre limit (ce sont des synonymes, les deux existent) ; et la colonne créée par count() s'appelle automatiquement count_ avec un underscore — l'oublier dans le order by fait échouer la requête. Retenez aussi que summarize doit précéder le tri, jamais l'inverse.
À vous de jouer : Question de contrôle : vous voulez le nombre de connexions réussies par compte, trié du plus actif au moins actif. Quel enchaînement d'opérateurs utilisez-vous ?
Voir la réponse
where pour filtrer l'EventID 4624, puis summarize count() by Account pour agréger, puis order by count_ desc pour trier. L'ordre compte : on filtre d'abord pour agréger moins de données, on agrège ensuite, on trie en dernier.
2.3 Fonctions de date et heure
ago() — Intervalle relatif : ago(1h), ago(24h), ago(7d)
now() — Heure actuelle
datetime_diff() — Différence entre deux dates
format_datetime() — Formate une date en chaîne
Pourquoi c'est important : En réponse à incident, la première question est toujours « depuis quand ? » : une compromission découverte aujourd'hui a souvent commencé il y a trois semaines. ago(), now() et bin() vous permettent de borner vos requêtes et de regrouper les événements par heure pour voir l'explosion d'activité qui trahit l'attaquant. Sans fenêtrage temporel, vos requêtes scannent trop de données et l'examen vous le reprochera.
L'analogie qui aide : Imaginez que vous visionnez les bandes d'une caméra de surveillance : ago(24h) rembobine de 24 heures, now() est le direct, et bin(TimeGenerated, 1h) découpe la bande en tranches d'une heure pour compter les passages dans chaque tranche. Vous ne regardez jamais trois mois de bande d'un coup — vous découpez.
// Filtrer les dernières 24 heures
SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID in (4624, 4625, 4648)
| summarize count() by EventID, bin(TimeGenerated, 1h)
| render timechart
Démonstration pas à pas : Déroulez la requête d'exemple : where TimeGenerated supérieur à ago(24h) ne garde que la dernière journée ; where EventID in (4624, 4625, 4648) cible trois types d'authentification ; summarize count() by EventID et bin(TimeGenerated, 1h) crée un compteur par type d'événement et par tranche d'une heure (bin arrondit chaque horodatage à l'heure pile) ; render timechart trace la courbe. Concrètement, si la courbe des 4625 (échecs) passe de 5 à 800 entre 2h et 3h du matin, vous visualisez une attaque par force brute.
💡 Astuce KQL
Utilisez has au lieu de contains pour une recherche plus rapide et précise sur des mots complets. has utilise un index, contains fait du matching de sous-chaîne.
Pièges classiques : Erreurs classiques : ago(24h) s'utilise avec l'opérateur supérieur (TimeGenerated plus récent qu'il y a 24 heures) — utiliser l'opérateur inférieur inverse la sélection et retourne tout l'historique ancien ; bin() n'a de sens qu'avec summarize, seul il ne compte rien ; et TimeGenerated est en UTC, donc 2h du matin dans Sentinel peut être 4h à Paris en été — vérifiez toujours le fuseau avant d'affirmer une connexion nocturne.
À vous de jouer : Question de contrôle : vous écrivez where TimeGenerated inférieur à ago(24h). Quels événements obtenez-vous : ceux des dernières 24 heures ou les plus anciens ?
Voir la réponse
Les plus anciens uniquement. ago(24h) vaut « il y a 24 heures », donc TimeGenerated inférieur à ago(24h) sélectionne les événements antérieurs à hier — tout l'historique sauf les dernières 24 heures. Pour les dernières 24 heures, il faut TimeGenerated supérieur à ago(24h).
Module 3 : Log Analytics Workspace
3.1 Structure d'un Workspace
Le Log Analytics Workspace est le conteneur central de données dans Sentinel. Il contient :
Pourquoi c'est important : Le workspace est le coffre-fort de toutes vos preuves : si vous ne comprenez pas où sont stockées les données, vous ne saurez ni dans quelle table chercher, ni pourquoi une requête ne retourne rien, ni comment expliquer à l'audit où sont conservés les logs. C'est aussi la base de la facturation — chaque Go stocké se paie.
L'analogie qui aide : Imaginez un entrepôt logistique : le workspace est le bâtiment (dans une région Azure précise), chaque table est une allée de rayonnages dédiée à un produit (SecurityEvent, SigninLogs), et les clusters sont plusieurs entrepôts gérés ensemble pour payer moins cher le gardiennage. Vous ne cherchez pas un colis au hasard — vous allez à la bonne allée.
- Tables — Chaque source de données crée sa propre table (ex:
SecurityEvent, SigninLogs, AlertInfo)
- Espace de travail — Groupe de tables dans une région Azure spécifique
- Clusters — Groupement de workspaces pour l'optimisation des coûts et des performances
Démonstration pas à pas : Suivez une donnée de bout en bout : un contrôleur de domaine génère un événement 4625, l'agent Azure Monitor l'envoie au workspace SOC-Production situé en Europe Ouest, il atterrit dans la table SecurityEvent avec son TimeGenerated, et votre requête KQL l'interroge là et nulle part ailleurs. Moralité : si votre requête sur SigninLogs ne retourne rien, vérifiez d'abord que le connecteur Azure AD alimente bien ce workspace-ci — beaucoup d'incidents « sans logs » viennent d'une requête exécutée sur le mauvais workspace.
Pièges classiques : À l'examen : un workspace appartient à une région Azure et on ne le déplace pas comme un fichier — la résidence des données est un choix de conformité. Autre confusion : le workspace stocke, Sentinel analyse — Log Analytics peut exister sans Sentinel, mais Sentinel ne fonctionne pas sans workspace. Enfin, plusieurs workspaces peuvent coexister : interroger le mauvais donne zéro résultat alors que les données existent ailleurs.
À vous de jouer : Question de contrôle : votre requête sur SecurityEvent ne retourne rien alors que les serveurs génèrent des événements. Quelle est la première vérification à faire côté workspace ?
Voir la réponse
Vérifier que vous interrogez le bon workspace et que l'agent y envoie bien les données — par exemple en interrogeant la table Heartbeat pour voir si les serveurs émettent. Si Heartbeat est vide aussi, le problème est l'ingestion, pas la requête.
3.2 Tables principales Sentinel
SecurityEvent — Événements de sécurité Windows (EventID 4624, 4625, 4688, etc.)
SigninLogs — Logs de connexion Azure AD (succès, échecs, MFA)
AuditLogs — Logs d'audit Azure AD (création de compte, changement de groupe)
AlertInfo — Informations sur les alertes Microsoft Defender
AlertEvidence — Preuves associées aux alertes Defender
Heartbeat — Témoins de vie des agents connectés au workspace
Syslog — Logs système Linux (sshd, sudo, auth, etc.)
CommonSecurityLog — Logs CEF de pare-feu et appliances réseau (Palo Alto, Fortinet)
Pourquoi c'est important : Chaque question d'investigation a sa table : connexion suspecte vers SigninLogs, malware sur un poste vers DeviceFileEvents, pare-feu vers CommonSecurityLog. Connaître les huit tables principales par coeur vous fait gagner un temps précieux pendant un incident et rapporte des points directs à l'examen, qui teste ces associations en boucle.
L'analogie qui aide : Imaginez les dossiers d'un commissariat : SecurityEvent est le registre des entrées-sorties du bâtiment Windows, SigninLogs le registre des badges Azure AD, AuditLogs le journal des décisions administratives (qui a créé quel compte), Heartbeat la feuille de présence des agents, et CommonSecurityLog les rapports des miradors (pare-feu). Vous n'ouvrez pas le registre des badges pour retrouver un colis.
Démonstration pas à pas : Prenez un scénario et associez chaque indice à sa table : 1) l'utilisateur se connecte depuis le Brésil — SigninLogs (IPAddress, Location) ; 2) dix minutes plus tard, un exécutable apparaît dans Temp — table Defender côté endpoint ; 3) le pare-feu voit une connexion sortante vers une IP inconnue — CommonSecurityLog ; 4) l'agent du poste cesse d'émettre — Heartbeat s'interrompt. Quatre tables, quatre pièces du puzzle : l'analyste qui connaît ses tables reconstruit l'attaque en dix minutes.
Pièges classiques : Confusions qui coûtent cher : AlertInfo (les alertes elles-mêmes) contre AlertEvidence (les preuves jointes : fichiers, IP, processus) — l'examen les mélange volontairement ; SecurityEvent (Windows, EventID) contre Syslog (Linux) — le système d'exploitation détermine la table ; SigninLogs (les connexions) contre AuditLogs (les actions d'administration). Retenez le réflexe : quel système a généré le log ? La réponse donne la table.
À vous de jouer : Question de contrôle : un administrateur crée un compte invité suspect dans Azure AD. Dans quelle table cherchez-vous, et pourquoi pas dans SigninLogs ?
Voir la réponse
Dans AuditLogs, car c'est une action d'administration (création de compte), pas une connexion. SigninLogs ne contient que les authentifications (succès et échecs) : chercher une création de compte dedans ne retournera jamais rien.
3.3 Schéma de données
Chaque table possède un schéma fixe avec des colonnes standard :
Pourquoi c'est important : Toutes vos requêtes KQL reposent sur les noms de colonnes : si vous écrivez Timestamp au lieu de TimeGenerated dans SecurityEvent, la requête échoue. Connaître les quatre colonnes standard (TimeGenerated, TimeProcessed, _ResourceId, _SubscriptionId) vous permet de filtrer par temps, de comprendre le délai d'ingestion et de tracer la source d'un log jusqu'à son abonnement.
L'analogie qui aide : Imaginez le tampon de la poste sur chaque lettre : TimeGenerated est la date écrite par l'expéditeur (quand l'événement s'est produit), TimeProcessed est le cachet d'arrivée au centre de tri (quand Sentinel l'a reçu), et _ResourceId avec _SubscriptionId sont l'adresse de l'expéditeur. Si le cachet d'arrivée est très postérieur à la date d'envoi, le courrier a du retard — en sécurité, ce retard s'appelle la latence d'ingestion.
TimeGenerated — Horodatage de l'événement (UTC)
TimeProcessed — Horodatage du traitement dans Sentinel
_ResourceId — Identifiant de la ressource source
_SubscriptionId — ID de l'abonnement Azure
Démonstration pas à pas : Comparez les deux horodatages sur un cas réel : un événement généré à 14h00 (TimeGenerated) apparaît traité à 14h07 (TimeProcessed) — 7 minutes de latence. C'est normal pour du syslog via Events Hub, mais anormal pour du Defender en direct. En investigation, triez toujours sur TimeGenerated (la réalité de l'attaque), et utilisez l'écart entre TimeProcessed et TimeGenerated pour expliquer à votre responsable pourquoi l'alerte est arrivée en retard.
Pièges classiques : Trois pièges : 1) toutes les tables n'appellent pas l'horodatage TimeGenerated — les tables Defender XDR utilisent Timestamp, et mélanger les deux noms casse les jointures ; 2) les colonnes système commencent par un underscore (_ResourceId) et ne s'affichent pas toujours par défaut ; 3) TimeGenerated est en UTC — filtrer « aujourd'hui » sans convertir peut exclure des événements français de la soirée.
À vous de jouer : Question de contrôle : vous joignez SecurityEvent (TimeGenerated) et DeviceProcessEvents (Timestamp). Pourquoi la jointure sur l'horodatage échoue-t-elle ?
Voir la réponse
Parce que les colonnes ne portent pas le même nom : il faut les harmoniser avec extend ou project-rename avant le join, par exemple en créant une colonne Timestamp à partir de TimeGenerated. Les schémas diffèrent selon la source (Sentinel natif contre Defender XDR), et KQL n'aligne jamais les noms automatiquement.
3.4 Rétention et coût
- Rétention gratuite : 30 jours pour toutes les tables (basique)
- Rétention étendue : Jusqu'à 2 ans (payant, au Go/mois)
- Archivage : Au-delà de 2 ans (Archive tier, coût réduit mais accès lent)
Pourquoi c'est important : Une attaque découverte en mars a souvent commencé en janvier : avec 30 jours de rétention gratuite, les premières traces ont déjà disparu et vous ne pourrez jamais reconstruire l'intrusion. Choisir la bonne rétention (étendue, archive) est donc une décision de sécurité autant que de budget — et l'examen vérifie que vous connaissez les paliers.
L'analogie qui aide : Imaginez les bandes d'un supermarché : 30 jours gratuits, c'est l'enregistreur qui écrase automatiquement les vieilles cassettes ; la rétention étendue, c'est payer un local pour garder deux ans de cassettes accessibles ; l'archivage, c'est mettre les vieilles cassettes dans des cartons à la cave — pas cher, mais il faut du temps pour les ressortir. Vous choisissez selon ce que la loi et vos risques exigent.
Démonstration pas à pas : Chiffrez un cas simple : vous ingérez 100 Go par jour. À 30 jours, vous gardez environ 3 To en interactif sans surcoût. Passer à 12 mois de rétention étendue sur 36 To vous coûte chaque mois le tarif du Go supplémentaire — d'où l'intérêt des Transform Rules qui filtrent le bruit avant ingestion : supprimer 20 % de logs verbeux inutiles, c'est 20 % d'économie sur l'ingestion et la rétention. Retenez le calcul : le coût suit le volume, donc on filtre en amont, pas après.
⚠️ Coût de l'ingestion
Chaque Go de données ingestées a un coût. Utilisez des Transform Rules pour filtrer ou transformer les données AVANT l'ingestion afin de réduire les coûts.
Pièges classiques : À l'examen : la rétention gratuite de base est de 30 jours, pas 90 ; l'archive est bon marché mais nécessite une réhydratation lente avant interrogation — on ne chasse pas une menace en direct dans l'archive ; et le coût vient d'abord de l'ingestion (au Go entrant), la rétention longue n'étant qu'un surcoût. Confondre ingestion et rétention fait rater la question.
À vous de jouer : Question de contrôle : votre RSSI veut pouvoir enquêter sur des incidents vieux de 18 mois tout en limitant la facture. Quelle stratégie proposez-vous ?
Voir la réponse
Garder 30 à 90 jours en rétention interactive pour la chasse quotidienne, et placer le reste en rétention étendue ou en archive pour la conformité et le forensique. En parallèle, déployer des Transform Rules pour ne pas payer l'ingestion de logs verbeux inutiles.
Module 4 : Connecteurs de Données
4.1 Types de connecteurs
- Connecteurs Microsoft — Azure AD, Microsoft 365, Defender, Windows, Linux, SQL Server
- Connecteurs de content hub — Solutions pré-construites (Palo Alto, Cisco, Splunk, etc.)
- Connecteurs REST API — Generic Log Collector pour des sources personnalisées
- Connecteurs syslog/CEF — Pour les appliances réseau et serveurs Linux
- Azure Functions — Collecteurs personnalisés pour des sources spécifiques
Pourquoi c'est important : Un SIEM sans données est une coquille vide : choisir le bon type de connecteur (natif Microsoft, Content Hub, REST API, syslog/CEF, Azure Functions) détermine quelles attaques vous verrez. Face à un pare-feu Palo Alto ou une application maison, savoir quel connecteur utiliser fait la différence entre une couverture complète et un angle mort exploité par l'attaquant.
L'analogie qui aide : Imaginez que vous organisez la réception d'un hôtel : les connecteurs Microsoft sont les portes automatiques déjà câblées, le Content Hub est le catalogue de serrures compatibles pour les portes spéciales (Cisco, Palo Alto), l'API REST est la boîte aux lettres où chacun peut déposer un message au format convenu, syslog/CEF est le vieux téléphone de la loge, et Azure Functions est le groom que vous envoyez chercher les colis inhabituels.
Démonstration pas à pas : Suivez trois cas : 1) vous voulez les logs Azure AD — connecteur natif, trois clics dans le portail, aucun agent ; 2) vous avez des pare-feu Fortinet qui parlent CEF — connecteur syslog/CEF via un forwarder Linux qui transmet au workspace ; 3) votre application métier expose des logs via une API maison — Azure Function qui appelle l'API toutes les 10 minutes et pousse les logs. Trois sources, trois connecteurs : le réflexe est toujours « la source parle quel protocole ? », et la réponse donne le connecteur.
Pièges classiques : À l'examen : tous les connecteurs du Content Hub ne sont pas en un clic — certains exigent un agent ou un forwarder intermédiaire ; syslog et CEF sont deux formats différents (CEF est structuré, syslog est brut) et la table de destination change (Syslog contre CommonSecurityLog) ; enfin, l'API REST générique a des limites de débit — on ne pousse pas des millions d'événements par seconde sans Events Hub.
À vous de jouer : Question de contrôle : vos pare-feu envoient des logs au format CEF. Quel type de connecteur utilisez-vous et dans quelle table arriveront les données ?
Voir la réponse
Le connecteur syslog/CEF (via un agent ou un forwarder qui transmet au workspace), et les données atterriront dans CommonSecurityLog. Le format CEF structuré est parsé vers cette table, pas vers Syslog qui accueille les logs Linux bruts.
4.2 Connecteurs essentiels pour SC-200
- Microsoft 365 Defender — Alerts et incidents Defender for Endpoint, Identity, Cloud Apps, Office 365
- Azure Active Directory — SigninLogs et AuditLogs
- Windows Security Events — Via agent Azure Monitor ou Events Hub
- Threat Intelligence — Importation de IoC (STIX/TAXII, CSV, APIs)
- Microsoft Defender for Cloud — Alertes et recommandations de sécurité
- Microsoft 365 — Exchange Online, SharePoint, Teams audit logs
Pourquoi c'est important : Le jour de l'examen comme le jour d'un incident réel, on attend de vous les six connecteurs prioritaires : M365 Defender, Azure AD, Windows Security Events, Threat Intelligence, Defender for Cloud, Microsoft 365. Les activer en premier donne une couverture maximale pour un effort minimal, car ce sont eux qui alimentent les tables les plus interrogées (SigninLogs, DeviceEvents, AuditLogs).
L'analogie qui aide : Imaginez que vous sécurisez une maison : vous commencez par la porte d'entrée (Azure AD, les identités), les fenêtres du rez-de-chaussée (Defender, les postes) et le détecteur de fumée (Defender for Cloud, la posture), avant d'acheter des caméras pour le jardin (connecteurs tiers). Les six connecteurs essentiels sont vos portes, fenêtres et détecteurs.
Démonstration pas à pas : Appliquez la priorité à un cas concret : une PME de 200 postes rejoint Sentinel. Semaine 1 : connecteur Azure AD — vous voyez immédiatement 40 échecs de connexion par jour depuis l'étranger. Semaine 2 : connecteur M365 Defender — les alertes endpoint remontent et se corrèlent aux identités. Semaine 3 : Threat Intelligence — les IoC connus s'illuminent dans les logs existants. Chaque connecteur ajoute une couche de visibilité mesurable : comptez les nouvelles tables alimentées pour prouver le progrès à votre direction.
Pièges classiques : activer le connecteur ne suffit pas toujours — Windows Security Events exige aussi l'agent AMA sur les VM et la règle de collecte (DCR) ; autre piège : le connecteur Threat Intelligence n'apporte pas de logs mais des IoC qui enrichissent les logs existants ; enfin, connecter Microsoft 365 (Exchange, SharePoint) n'est pas redondant avec Azure AD — ce sont des tables différentes (OfficeActivity contre SigninLogs).
À vous de jouer : Question de contrôle : vous avez activé le connecteur Windows Security Events mais la table SecurityEvent reste vide. Qu'avez-vous probablement oublié ?
Voir la réponse
De déployer l'agent Azure Monitor (AMA) sur les machines et d'associer une règle de collecte de données (DCR) qui envoie les événements au workspace. Le connecteur côté Sentinel ne fait qu'autoriser la réception : sans agent émetteur, rien n'arrive.
4.3 Méthodes d'ingestion
- Azure Agent (AMA) — Azure Monitor Agent installé sur VM pour Security Events
- Events Hub — Streaming en temps réel depuis Azure ou services tiers
- Function App — Push API pour l'envoi programmé de logs
- Direct Connect — Via le portail Sentinel pour les solutions du Content Hub
- Storage Account — Importation batch de fichiers logs
Pourquoi c'est important : Choisir entre agent AMA, Events Hub, Function App, connexion directe et Storage Account, c'est choisir entre temps réel et batch, entre simplicité et débit. Une erreur ici se paie comptant : du streaming temps réel envoyé par batch quotidien rend toute détection rapide impossible, et un incident détecté avec 24 heures de retard est un incident subi.
L'analogie qui aide : Imaginez l'approvisionnement d'un restaurant : l'agent AMA est le tuyau d'eau courante (flux continu depuis la VM), Events Hub est le tapis roulant des sushis (streaming temps réel à gros débit), la Function App est le livreur qui passe toutes les 10 minutes, la connexion directe est le plat déjà prêt au comptoir (Content Hub), et le Storage Account est la livraison mensuelle de conserves (import batch de fichiers).
Démonstration pas à pas : Comparez deux besoins : 1) 5 000 événements par seconde de pare-feu à analyser en moins de 5 minutes — Events Hub, seul capable d'absorber ce débit en streaming ; 2) des logs d'une application interne exportés chaque nuit en CSV — Storage Account et import batch, simple et suffisant. La règle de décision : débit supérieur à quelques centaines d'événements par seconde ou besoin de détection en minutes implique le streaming (AMA, Events Hub) ; volume modeste et tolérance au délai autorisent le batch (Function, Storage).
💡 Meilleure pratique
Commencez toujours par les connecteurs Microsoft natifs (Azure AD, M365 Defender) avant d'ajouter des connecteurs tiers. Les connecteurs Microsoft sont gratuits (les données ingérées ont un coût, mais pas le connecteur lui-même).
Pièges classiques : Confusions fréquentes : AMA remplace l'ancien agent MMA — à l'examen, toute réponse citant MMA comme solution moderne est fausse ; Events Hub sert au streaming temps réel, pas au stockage longue durée ; et la connexion directe Content Hub ne dispense pas toujours d'infrastructure (certains connecteurs exigent quand même un forwarder). Retenez : AMA pour les VM modernes, Events Hub pour les gros débits temps réel.
À vous de jouer : Question de contrôle : vous devez ingérer 10 000 événements par seconde de pare-feu avec une détection en moins de 5 minutes. Quelle méthode choisissez-vous : Storage Account ou Events Hub, et pourquoi ?
Voir la réponse
Events Hub, car lui seul absorbe un tel débit en streaming avec une latence de quelques minutes. Le Storage Account fonctionne en import batch (fichiers déposés puis lus) : la latence se compterait en heures et la détection rapide serait impossible.
Module 5 : Premières Requêtes KQL
5.1 Lister les connexions échouées
SigninLogs
| where ResultType != 0
| summarize FailedAttempts = count() by UserPrincipalName, IPAddress, ResultDescription
| order by FailedAttempts desc
Pourquoi c'est important : Compter les échecs de connexion par utilisateur est le premier réflexe d'un analyste : c'est ainsi qu'on repère le password spraying (beaucoup de comptes, peu d'essais chacun) et la force brute (un compte, des centaines d'essais). Cette requête est littéralement la première que vous lancerez lors d'un incident d'identité — maîtrisez-la par coeur.
L'analogie qui aide : Imaginez le gardien d'une résidence qui note chaque badge refusé : en fin de journée, il compte les refus par personne et par porte. Trois refus pour un résident distrait, c'est normal ; 300 refus à 3h du matin sur la porte du local technique, c'est une tentative d'intrusion. Votre requête est le carnet du gardien, en automatique.
Démonstration pas à pas : Déroulez la requête : where ResultType différent de 0 ne garde que les échecs (0 vaut succès dans SigninLogs, tout le reste vaut échec) ; summarize FailedAttempts = count() by UserPrincipalName, IPAddress, ResultDescription regroupe par triplet utilisateur-IP-motif et compte — vous passez de 50 000 lignes à quelques dizaines ; order by FailedAttempts desc met le pire en haut. Lecture du résultat : 1 utilisateur avec 400 essais depuis 1 IP égale force brute ; 200 utilisateurs avec 3 essais depuis 1 IP égale password spraying. Le motif se lit dans la forme du tableau.
Pièges classiques : Le piège numéro un : dans SigninLogs, le succès vaut ResultType égal à 0 — donc les échecs sont « différent de 0 », et inverser le test analyse les mauvaises données. Autre piège : oublier de grouper par IPAddress masque le password spraying (les essais sont répartis sur les comptes, seule l'IP commune trahit l'attaquant). Enfin, ResultDescription précise la cause (mauvais mot de passe, compte bloqué) : ne la supprimez pas du project.
À vous de jouer : Question de contrôle : votre tableau montre 300 comptes avec 2 à 3 échecs chacun, tous depuis la même IP en une heure. Quelle attaque suspectez-vous et pourquoi ?
Voir la réponse
Un password spraying : l'attaquant teste quelques mots de passe courants sur un grand nombre de comptes pour rester sous les seuils de verrouillage. L'indice décisif est l'IP source unique combinée à la dispersion sur des centaines de comptes — une force brute classique concentrerait les essais sur un seul compte.
5.2 Détecter les connexions hors horaires
SigninLogs
| where ResultType == 0
| extend Hour = hourofday(TimeGenerated)
| where Hour < 6 or Hour > 22
| project TimeGenerated, UserPrincipalName, IPAddress, Location, AppDisplayName
Pourquoi c'est important : Les attaquants adorent travailler quand vous dormez : une connexion réussie à 3h47 depuis un compte qui ne se connecte que de 9h à 18h est l'un des signaux les plus rentables à surveiller. Cette requête vous apprend en plus deux techniques réutilisables partout : créer une colonne calculée avec extend et extraire l'heure avec hourofday.
L'analogie qui aide : Imaginez une boulangerie qui ouvre de 7h à 13h : si l'alarme signale une entrée à 3h du matin avec un badge valide, vous ne dites pas « tout va bien, le badge est valide » — vous demandez qui est entré et pourquoi. Votre requête est cette alarme : le badge (ResultType égal à 0) est bon, mais l'heure ne colle pas avec les habitudes.
Démonstration pas à pas : Déroulez chaque ligne : where ResultType égal à 0 ne garde que les succès — c'est volontaire, on cherche l'intrus qui possède déjà le bon mot de passe ; extend Hour = hourofday(TimeGenerated) crée une colonne avec l'heure UTC (0 à 23) ; where Hour inférieur à 6 or Hour supérieur à 22 ne garde que la tranche 22h-6h ; project affiche l'essentiel dont Location et l'application. Exemple chiffré : sur 12 000 succès quotidiens, il reste 14 connexions nocturnes, dont une depuis le Nigeria sur le compte du comptable — votre périmètre d'analyse passe de 12 000 à 14 lignes.
Pièges classiques : Deux pièges : 1) hourofday travaille en UTC — 22h UTC vaut minuit à Paris en été, donc ajustez les bornes ou convertissez avant d'accuser quelqu'un de connexion nocturne ; 2) le filtre sur l'heure exige un or — avec un and, aucune heure ne peut être à la fois inférieure à 6 et supérieure à 22, et la requête retourne toujours zéro ligne. Testez toujours une requête censée retourner des résultats.
À vous de jouer : Question de contrôle : pourquoi filtre-t-on sur ResultType égal à 0 alors qu'on cherche une attaque ?
Voir la réponse
Parce qu'on cherche une compromission déjà aboutie : l'attaquant possède un identifiant valide et réussit à se connecter. Les échecs signaleraient une tentative en cours, tandis qu'un succès à 3h du matin signale un compte probablement déjà compromis — c'est plus grave et plus urgent.
5.3 Compteur d'événements de sécurité par Machine
SecurityEvent
| where TimeGenerated > ago(7d)
| summarize TotalEvents = count() by Computer
| order by TotalEvents desc
| render barchart
Pourquoi c'est important : Compter les événements par machine puis tracer un graphique à barres, c'est prendre le pouls de votre parc en une image : la machine qui génère dix fois plus d'événements que les autres est soit compromise, soit mal configurée. Cette requête vous apprend aussi le réflexe render barchart, indispensable pour présenter un incident à des non-techniciens.
L'analogie qui aide : Imaginez le standard téléphonique d'une entreprise : si un poste passe 400 appels par jour quand la moyenne est de 20, vous allez écouter ce qui s'y passe. Votre requête est le relevé du standard : chaque machine est un poste, chaque événement un appel, et le graphique montre d'un coup d'oeil qui sort de la norme.
Démonstration pas à pas : Suivez le pipeline : where TimeGenerated supérieur à ago(7d) cadre une semaine (assez long pour une moyenne, assez court pour rester rapide) ; summarize TotalEvents = count() by Computer compte par machine — ici on nomme explicitement le compteur TotalEvents au lieu du count_ automatique, ce qui rend le graphique lisible ; order by TotalEvents desc trie ; render barchart affiche. Exemple : 180 postes entre 2 000 et 8 000 événements, un serveur à 96 000 — vous venez d'identifier votre suspect numéro un en une requête.
Pièges classiques : sur 7 jours sans filtre EventID, la requête peut brasser des millions de lignes et dépasser les limites — en production, ajoutez un filtre EventID ciblé ; render doit toujours être le dernier opérateur, rien ne peut le suivre ; et le nom donné dans summarize (TotalEvents) devient le nom de colonne — si vous écrivez ensuite order by count_, la requête échoue car la colonne s'appelle TotalEvents.
À vous de jouer : Question de contrôle : après summarize, votre colonne s'appelle TotalEvents mais vous triez avec order by count_ desc. Que se passe-t-il ?
Voir la réponse
La requête échoue : la colonne count_ n'existe pas. Quand vous nommez explicitement le compteur (TotalEvents = count()), c'est ce nom qu'il faut utiliser partout ensuite (order by TotalEvents desc). Le nom automatique count_ n'existe que si vous écrivez count() sans lui donner de nom.
5.4 Détection des changements de groupe
AuditLogs
| where OperationName == "Add member to group"
| where TargetResources[0].type == "Group"
| project TimeGenerated, InitiatedBy.user.userPrincipalName, TargetResources[0].displayName, Result
Pourquoi c'est important : Ajouter un compte à un groupe privilégié (Domain Admins, administrateurs Azure) est l'étape préférée des attaquants pour pérenniser leur accès : une fois membre, ils héritent de tous les droits. Surveiller l'opération « Add member to group » dans AuditLogs, c'est surveiller la porte des privilèges — chaque ajout inattendu est une alerte potentielle de niveau critique.
L'analogie qui aide : Imaginez le registre d'un club très select : chaque nouveau membre doit y être inscrit avec le nom de son parrain. Votre requête lit ce registre chaque matin : si le parrain est inconnu ou si le nouveau membre est arrivé à 4h du matin, vous demandez des explications. Les groupes privilégiés sont ce club, et AuditLogs est le registre.
Démonstration pas à pas : Déroulez la requête : where OperationName égal à Add member to group cible uniquement les ajouts (pas les suppressions ni les créations) ; where TargetResources[0].type égal à Group vérifie que la cible est bien un groupe — TargetResources est un tableau JSON, d'où l'index [0] pour prendre le premier élément ; project affiche qui a fait quoi : l'initiateur, le groupe et le résultat. Exemple : ajout du compte « stagiaire-compta » au groupe « Global Administrators » par un administrateur qui n'a jamais fait ça — escalade de privilèges probable.
Pièges classiques : Le piège principal est la syntaxe des champs imbriqués : TargetResources est un tableau, donc TargetResources.displayName seul ne fonctionne pas — il faut l'index [0]. Autre piège : OperationName est sensible à la casse et au libellé exact (Add member to group) ; une faute de frappe retourne zéro ligne sans erreur explicite. Vérifiez toujours le libellé exact dans un échantillon (take 10) avant de filtrer.
À vous de jouer : Question de contrôle : pourquoi écrit-on TargetResources[0].displayName et pas simplement TargetResources.displayName ?
Voir la réponse
Parce que TargetResources est un tableau (une liste) d'objets JSON, pas un objet unique : il faut d'abord sélectionner un élément avec l'index [0] avant d'accéder à son champ displayName. Sans l'index, KQL ne sait pas dans quel élément chercher et la requête échoue ou retourne vide.
5.5 Jointures (join) entre tables
// Corréler les connexions échouées avec les comptes désactivés
SigninLogs
| where ResultType == 650005 // Compte non trouvé
| join kind=inner (
IdentityInfo
| where AssignedRoles != "[]"
) on UserPrincipalName
| project TimeGenerated, UserPrincipalName, IPAddress, Location
Pourquoi c'est important : Aucune table ne raconte toute l'histoire : SigninLogs voit la connexion, IdentityInfo connaît les rôles, DeviceEvents voit le poste. Le join relie ces fragments en une seule preuve — par exemple prouver que des échecs de connexion visent des comptes administrateurs. Sans jointure, vous passez à côté des attaques qui ne deviennent visibles qu'en croisant deux sources.
L'analogie qui aide : Imaginez deux listes : celle des visiteurs refoulés à l'entrée et celle des détenteurs d'un badge VIP. Chaque liste seule est banale, mais en les croisant vous découvrez que quelqu'un a tenté d'entrer avec un faux badge VIP — c'est exactement ce que fait le join : il relie deux tables sur un point commun (ici UserPrincipalName) pour révéler ce qu'aucune ne montre seule.
Démonstration pas à pas : Déroulez la requête : la partie gauche sélectionne les connexions avec ResultType 650005 (compte inexistant — l'attaquant devine des noms) ; la partie droite entre parenthèses prépare la table des identités filtrée sur les comptes à rôles ; on UserPrincipalName est la clé commune aux deux tables ; kind=inner ne garde que les lignes présentes des deux côtés. Résultat chiffré : 2 000 échecs quotidiens se réduisent à 3 lignes où l'attaquant a deviné des comptes proches de vrais comptes à privilèges — votre priorité d'investigation.
💡 Commencez à pratiquer !
Utilisez le Terminal KQL pour exécuter ces requêtes en simulation. La pratique est essentielle pour maîtriser KQL avant l'examen.
Pièges classiques : oublier le on ou joindre sur des colonnes de noms différents sans les renommer avant (le join exige le même nom des deux côtés) ; choisir le mauvais kind — inner ne garde que les correspondances (c'est ce qu'on veut ici), leftouter garderait tout le bruit ; et joindre des tables volumineuses sans filtrer d'abord par temps, ce qui fait exploser le temps d'exécution. Filtrez toujours avant de joindre, jamais après.
À vous de jouer : Question de contrôle : que se passe-t-il si la colonne s'appelle UserPrincipalName à gauche et UPN à droite ?
Voir la réponse
Le join échoue ou retourne vide car la clé doit porter le même nom des deux côtés. Il faut d'abord renommer une des colonnes, par exemple avec extend UserPrincipalName = UPN ou project-rename, puis joindre sur le nom commun.