Aller au contenu

Microsoft Sentinel Cheatsheet

Microsoft Sentinel is a cloud-native Security Information and Event Management (SIEM) and Security Orchestration, Automation, and Response (SOAR) solution that provides intelligent security analytics and Renseignement sur les Menaces across the enterprise. Built on Azure, Sentinel delivers scalable security monitoring, advanced threat detection, and automated réponse aux incidents capabilities that enable security teams to detect, investigate, and respond to threats at cloud scale.

Platform Overview

Core Architecture

Microsoft Sentinel operates as a fully managed service within the Azure ecosystem, leveraging Azure Monitor Logs as its underlying data platform. The architecture consists of several integrated components that work together to provide comprehensive security operations capabilities.

The data ingestion layer supports a wide variety of data sources through native connectors, REST APIs, and custom integrations. Sentinel can collect data from Microsoft services (Office 365, Azure AD, Windows Security Events), third-party security tools, cloud platforms, network devices, and custom applications.

The analytics engine utilizes machine learning algorithmes, behavioral analytics, and rule-based detection to identify potential security threats. Sentinel's detection capabilities include built-in analytics rules, custom KQL (Kusto Query Language) queries, machine learning models for anomaly detection, and Renseignement sur les Menaces integration.

clé Features

# Core Platform Capabilities
- Cloud-native SIEM and SOAR platform
- Advanced threat detection and analytics
- Security orchestration and automated response
- Renseignement sur les Menaces integration
- Machine learning and behavioral analytics
- Custom dashboards and workbooks
- Incident management and investigation
- chasse aux menaces capabilities

Data Connectors and Ingestion

Microsoft service Connectors

# Azure Active Directory connector
Connect-AzAccount
Set-AzContext -SubscriptionId "<subscription-id>"

# Enable Azure AD connector via PowerShell
$resourceGroupName = "sentinel-rg"
$workspaceName = "sentinel-workspace"
$dataConnectorId = "AzureActiveDirectory"

New-AzSentinelDataConnector -ResourceGroupName $resourceGroupName -WorkspaceName $workspaceName -Id $dataConnectorId -Kind "AzureActiveDirectory"

# Office 365 connector configuration
$office365Config = @\\\\{
    tenantId = "<tenant-id>"
    dataTypes = @\\\\{
        exchange = @\\\\{ state = "Enabled" \\\\}
        sharePoint = @\\\\{ state = "Enabled" \\\\}
        teams = @\\\\{ state = "Enabled" \\\\}
    \\\\}
\\\\}

# Azure Security Center connector
$ascConfig = @\\\\{
    subscriptionId = "<subscription-id>"
    dataTypes = @\\\\{
        alerts = @\\\\{ state = "Enabled" \\\\}
        recommendations = @\\\\{ state = "Enabled" \\\\}
    \\\\}
\\\\}

Third-Party Connectors

# Syslog connector configuration
# Configure rsyslog on Linux systems
sudo nano /etc/rsyslog.d/95-omsagent.conf

# Add configuration for Sentinel
*.* @@<workspace-id>.ods.opinsights.azure.com:514

# Restart rsyslog service
sudo systemctl restart rsyslog

# CEF (Common Event Format) connector
# Configure log forwarding for CEF events
| logger -p local4.warn -t CEF "CEF:0 | Microsoft | ATA | 1.9.0.0 | AbnormalSensitiveGroupMembershipChangeSuspiciousActivity | Abnormal modification of sensitive groups | 5 | start=2018-12-12T18:52:58.0000000Z app=GroupMembershipChangeEvent suser=krbtgt msg=krbtgt has been added to the following sensitive groups: Domain Admins." |

# Custom REST API connector
curl -X POST "https://<workspace-id>.ods.opinsights.azure.com/api/logs?api-version=2016-04-01" \
  -H "autorisation: Sharedclé <workspace-id>:<shared-clé>" \
  -H "Content-Type: application/json" \
  -H "Log-Type: CustomSecurityLog" \
  -d '\\\\{
    "timestamp": "2023-01-01T12:00:00Z",
    "severity": "High",
    "source_ip": "192.168.1.100",
    "event_type": "suspicious_login",
    "user": "admin@company.com"
  \\\\}'

Azure Monitor Agent configuration

# Install Azure Monitor Agent
$vmName = "security-vm"
$resourceGroupName = "security-rg"
$location = "East US"

# Create data collection rule
$dcrConfig = @\\\\{
    location = $location
    properties = @\\\\{
        dataSources = @\\\\{
            windowsEventLogs = @(
                @\\\\{
                    name = "SecurityEvents"
                    streams = @("Microsoft-SecurityEvent")
                    xPathQueries = @(
                        "Security!*[System[(EventID=4624 or EventID=4625 or EventID=4648)]]"
                    )
                \\\\}
            )
            syslog = @(
                @\\\\{
                    name = "SyslogEvents"
                    streams = @("Microsoft-Syslog")
                    facilityNames = @("auth", "authpriv", "démon")
                    logLevels = @("Warning", "Error", "Critical")
                \\\\}
            )
        \\\\}
        destinations = @\\\\{
            logAnalytics = @(
                @\\\\{
                    workspaceResourceId = "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.OperationalInsights/workspaces/<workspace-name>"
                    name = "SentinelWorkspace"
                \\\\}
            )
        \\\\}
        dataFlows = @(
            @\\\\{
                streams = @("Microsoft-SecurityEvent")
                destinations = @("SentinelWorkspace")
            \\\\}
        )
    \\\\}
\\\\}

KQL (Kusto Query Language) for Security Analytics

Basic KQL syntaxe

// Simple table query
SecurityEvent
|take 10

// Time range filtering
SecurityEvent
|where TimeGenerated > ago(1h)

// Field filtering
SecurityEvent
|where EventID == 4624
|where Account contains "admin"

// Multiple conditions
SecurityEvent
|where TimeGenerated > ago(24h)
|where EventID in (4624, 4625, 4648)
|where Account !contains "SYSTEM"

// String operations
SecurityEvent
|where Activity contains "Logon"
|where Account startswith "admin"
|where Computer endswith ".contoso.com"

Advanced Analytics Queries

// Failed login analysis
SecurityEvent
|where EventID == 4625
|where TimeGenerated > ago(1h)
|summarize FailedAttempts = count() by Account, Computer, IpAddress
|where FailedAttempts > 5
|sort by FailedAttempts desc

// escalade de privilèges detection
SecurityEvent
|where EventID == 4672
|where TimeGenerated > ago(24h)
|where Subjectnom d'utilisateur !in ("SYSTEM", "LOCAL service", "NETWORK service")
|summarize ElevationCount = count() by Subjectnom d'utilisateur, Computer
|where ElevationCount > 10
|sort by ElevationCount desc

// mouvement latéral detection
SecurityEvent
|where EventID == 4624
|where LogonType in (3, 10)
|where TimeGenerated > ago(1h)
|summarize UniqueComputers = dcount(Computer) by Account
|where UniqueComputers > 5
|sort by UniqueComputers desc

// Anomalous processus execution
SecurityEvent
|where EventID == 4688
|where TimeGenerated > ago(1h)
|where processus contains "powershell.exe" or processus contains "cmd.exe"
|where commandeLine contains "Invoke-" or commandeLine contains "Download"
|project TimeGenerated, Computer, Account, processus, commandeLine
|sort by TimeGenerated desc

chasse aux menaces Queries

// Suspicious PowerShell activity
SecurityEvent
|where EventID == 4688
|where processus endswith "powershell.exe"
|where commandeLine has_any ("Invoke-Expression", "IEX", "Invoke-WebRequest", "DownloadString", "Encodedcommande")
|extend Decodedcommande = base64_decode_tostring(extract(@"-Encodedcommande\s+([A-Za-z0-9+/=]+)", 1, commandeLine))
|project TimeGenerated, Computer, Account, commandeLine, Decodedcommande
|sort by TimeGenerated desc

// Living off the land techniques
SecurityEvent
|where EventID == 4688
|where processus has_any ("certutil.exe", "bitsadmin.exe", "regsvr32.exe", "rundll32.exe", "mshta.exe")
|where commandeLine has_any ("http", "ftp", "download", "urlcache", "split")
|project TimeGenerated, Computer, Account, processus, commandeLine
|sort by TimeGenerated desc

// Credential dumping detection
SecurityEvent
|where EventID in (4656, 4663)
|where ObjectName has_any ("lsass.exe", "SAM", "SECURITY", "SYSTEM")
|where AccessMask in ("0x1010", "0x1400", "0x100000")
|summarize AccessAttempts = count() by Account, Computer, ObjectName
|where AccessAttempts > 3
|sort by AccessAttempts desc

// DNS tunneling detection
DnsEvents
|where TimeGenerated > ago(1h)
|where QueryType == "TXT" or QueryType == "NULL"
|where Name contains "." and strlen(Name) > 50
|summarize QueryCount = count() by ClientIP, Name
|where QueryCount > 10
|sort by QueryCount desc

Machine Learning Analytics

// User behavior analytics
SigninLogs
|where TimeGenerated > ago(30d)
|where ResultType == "0"
|summarize
    LoginCount = count(),
    UniqueIPs = dcount(IPAddress),
    UniqueLocations = dcount(Location),
    UniqueDevices = dcount(DeviceDetail.deviceId)
    by UserPrincipalName
|extend RiskScore = (UniqueIPs * 2) + (UniqueLocations * 3) + (UniqueDevices * 1)
|where RiskScore > 20
|sort by RiskScore desc

// Anomalous data transfer detection
CommonSecurityLog
|where TimeGenerated > ago(1h)
|where DeviceVendor == "Palo Alto Networks"
|where Activity == "TRAFFIC"
|summarize TotalBytes = sum(SentBytes + ReceivedBytes) by SourceIP, DestinationIP
|extend BytesInMB = TotalBytes / (1024 * 1024)
|where BytesInMB > 1000
|sort by BytesInMB desc

// Behavioral baseline deviation
SecurityEvent
|where TimeGenerated > ago(30d)
|where EventID == 4624
|where LogonType == 2
|summarize
    AvgLoginsPerDay = count() / 30,
    StdDev = stdev(todouble(1))
    by Account
|join kind=inner (
    SecurityEvent
|where TimeGenerated > ago(1d)
|where EventID == 4624
|where LogonType == 2
|summarize TodayLogins = count() by Account
) on Account
|extend Deviation = abs(TodayLogins - AvgLoginsPerDay) / StdDev
|where Deviation > 3
|sort by Deviation desc

Analytics Rules and Detection

Scheduled Analytics Rules

// Create scheduled rule for force brute detection
let threshold = 10;
SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4625
|summarize FailedAttempts = count() by Account, IpAddress, Computer
|where FailedAttempts >= threshold
|extend
    Severity = case(
        FailedAttempts >= 50, "High",
        FailedAttempts >= 25, "Medium",
        "Low"
    ),
    Tactics = "CredentialAccess",
    Techniques = "T1110"
|project Account, IpAddress, Computer, FailedAttempts, Severity, Tactics, Techniques

// Suspicious file creation rule
SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4663
|where ObjectName endswith ".exe" or ObjectName endswith ".dll" or ObjectName endswith ".bat"
|where ObjectName has_any ("temp", "appdata", "programdata")
|where AccessMask == "0x2"
|extend
    Severity = "Medium",
    Tactics = "Execution",
    Techniques = "T1059"
|project TimeGenerated, Computer, Account, ObjectName, Severity, Tactics, Techniques

// escalade de privilèges rule
SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4672
|where Subjectnom d'utilisateur !in ("SYSTEM", "LOCAL service", "NETWORK service")
|where PrivilegeList has_any ("SeDebugPrivilege", "SeTakeOwnershipPrivilege", "SeLoadDriverPrivilege")
|extend
    Severity = "High",
    Tactics = "PrivilegeEscalation",
    Techniques = "T1134"
|project TimeGenerated, Computer, Subjectnom d'utilisateur, PrivilegeList, Severity, Tactics, Techniques

Near Real-Time (NRT) Rules

// Real-time logiciel malveillant detection
SecurityEvent
|where EventID == 4688
|where processus has_any ("mimikatz", "procdump", "pwdump", "fgdump")
|extend
    Severity = "High",
    Tactics = "CredentialAccess",
    Techniques = "T1003"
|project TimeGenerated, Computer, Account, processus, commandeLine, Severity, Tactics, Techniques

// Immediate threat response
SecurityEvent
|where EventID == 4625
|where Account == "Administrator"
|where IpAddress !startswith "192.168." and IpAddress !startswith "10." and IpAddress !startswith "172."
|extend
    Severity = "Critical",
    Tactics = "InitialAccess",
    Techniques = "T1078"
|project TimeGenerated, Computer, Account, IpAddress, Severity, Tactics, Techniques

Machine Learning Rules

// Anomaly detection template
let training_data =
    SecurityEvent
|where TimeGenerated between (ago(30d) .. ago(1d))
|where EventID == 4624
|summarize LoginCount = count() by Account, bin(TimeGenerated, 1h)
|summarize AvgLogins = avg(LoginCount), StdDev = stdev(LoginCount) by Account;
SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4624
|summarize CurrentLogins = count() by Account
|join kind=inner training_data on Account
|extend AnomalyScore = abs(CurrentLogins - AvgLogins) / StdDev
|where AnomalyScore > 3
|extend
    Severity = case(AnomalyScore > 5, "High", "Medium"),
    Tactics = "InitialAccess",
    Techniques = "T1078"
|project Account, CurrentLogins, AvgLogins, AnomalyScore, Severity, Tactics, Techniques

Incident Management and Investigation

Incident Creation and Assignment

# Create incident via PowerShell
$incidentParams = @\\\\{
    ResourceGroupName = "sentinel-rg"
    WorkspaceName = "sentinel-workspace"
    IncidentId = "incident-001"
    Title = "Suspicious Login Activity Detected"
    Description = "Multiple failed login attempts from external IP address"
    Severity = "High"
    Status = "New"
    Owner = @\\\\{
        email = "analyst@company.com"
        assignedTo = "Security Analyst"
        objectId = "<user-object-id>"
    \\\\}
    Labels = @(
        @\\\\{ labelName = "BruteForce"; labelType = "User" \\\\}
        @\\\\{ labelName = "ExternalIP"; labelType = "User" \\\\}
    )
\\\\}

New-AzSentinelIncident @incidentParams

# Update incident status
$updateParams = @\\\\{
    ResourceGroupName = "sentinel-rg"
    WorkspaceName = "sentinel-workspace"
    IncidentId = "incident-001"
    Status = "Active"
    Classification = "TruePositive"
    ClassificationComment = "Confirmed malicious activity"
    ClassificationReason = "SuspiciousActivity"
\\\\}

Update-AzSentinelIncident @updateParams

Investigation Queries

// Timeline reconstruction
let incident_time = datetime(2023-01-01T10:00:00Z);
let suspect_user = "john.doe@company.com";
let time_window = 2h;
union
| (SecurityEvent | where TimeGenerated between ((incident_time - time_window) .. (incident_time + time_window)) | where Account contains suspect_user), |
| (SigninLogs | where TimeGenerated between ((incident_time - time_window) .. (incident_time + time_window)) | where UserPrincipalName == suspect_user), |
| (AuditLogs | where TimeGenerated between ((incident_time - time_window) .. (incident_time + time_window)) | where InitiatedBy.user.userPrincipalName == suspect_user) |
|sort by TimeGenerated asc
|project TimeGenerated, Type, Activity, Account, Computer, IPAddress, Location

// Entity behavior analysis
let entity_ip = "192.168.1.100";
union
    (SecurityEvent|where IpAddress == entity_ip),
    (CommonSecurityLog|where SourceIP == entity_ip or DestinationIP == entity_ip),
    (DnsEvents|where ClientIP == entity_ip),
    (NetworkCommunicationEvents|where LocalIP == entity_ip or RemoteIP == entity_ip)
|summarize
    EventCount = count(),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    EventTypes = make_set(Type),
    UniqueDestinations = dcount(DestinationIP)
    by SourceIP
|sort by EventCount desc

// mouvement latéral tracking
let start_computer = "WORKSTATION-01";
SecurityEvent
|where EventID == 4624
|where LogonType in (3, 10)
|where Computer == start_computer
|extend NextHop = Computer
|join kind=inner (
    SecurityEvent
|where EventID == 4624
|where LogonType in (3, 10)
|where Computer != start_computer
) on Account
|where TimeGenerated1 > TimeGenerated
|project Account, SourceComputer = Computer, DestinationComputer = Computer1, TimeGenerated, TimeGenerated1
|sort by Account, TimeGenerated asc

Evidence Collection

// Collect relevant artifacts
let investigation_timeframe = ago(24h);
let entities = dynamic(["suspicious.user@company.com", "192.168.1.100", "COMPROMISED-PC"]);
union
    (SecurityEvent
|where TimeGenerated > investigation_timeframe
|where Account has_any (entities) or Computer has_any (entities) or IpAddress has_any (entities)),
    (SigninLogs
|where TimeGenerated > investigation_timeframe
|where UserPrincipalName has_any (entities) or IPAddress has_any (entities)),
    (DeviceEvents
|where TimeGenerated > investigation_timeframe
|where DeviceName has_any (entities) or InitiatingprocessusAccountName has_any (entities)),
    (EmailEvents
|where TimeGenerated > investigation_timeframe
|where SenderFromAddress has_any (entities) or RecipientEmailAddress has_any (entities))
|sort by TimeGenerated desc
|take 1000

// File hash analysis
DeviceFileEvents
|where TimeGenerated > ago(7d)
|where SHA256 in ("hash1", "hash2", "hash3")
|summarize
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated),
    DeviceCount = dcount(DeviceName),
    Devices = make_set(DeviceName)
    by SHA256, FileName
|sort by DeviceCount desc

Automation and Orchestration (SOAR)

Logic Apps Integration

\\\\{
    "definition": \\\\{
        "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
        "contentVersion": "1.0.0.0",
        "paramètres": \\\\{\\\\},
        "triggers": \\\\{
            "Microsoft_Sentinel_incident": \\\\{
                "type": "ApiconnexionWebhook",
                "inputs": \\\\{
                    "hôte": \\\\{
                        "connexion": \\\\{
                            "name": "@paramètres('$connexions')['azuresentinel']['connexionId']"
                        \\\\}
                    \\\\},
                    "body": \\\\{
                        "callback_url": "@\\\\{listCallbackUrl()\\\\}"
                    \\\\},
                    "path": "/incident-creation"
                \\\\}
            \\\\}
        \\\\},
        "actions": \\\\{
            "Get_incident": \\\\{
                "type": "Apiconnexion",
                "inputs": \\\\{
                    "hôte": \\\\{
                        "connexion": \\\\{
                            "name": "@paramètres('$connexions')['azuresentinel']['connexionId']"
                        \\\\}
                    \\\\},
                    "method": "get",
                    "path": "/Incidents/subscriptions/@\\\\{encodeURIComponent(triggerBody()?['WorkspaceSubscriptionId'])\\\\}/resourceGroups/@\\\\{encodeURIComponent(triggerBody()?['WorkspaceResourceGroup'])\\\\}/workspaces/@\\\\{encodeURIComponent(triggerBody()?['WorkspaceId'])\\\\}/alerts/@\\\\{encodeURIComponent(triggerBody()?['SystemAlertId'])\\\\}"
                \\\\}
            \\\\},
            "Block_IP_in_firewall": \\\\{
                "type": "Http",
                "inputs": \\\\{
                    "method": "POST",
                    "uri": "https://firewall-api.company.com/block",
                    "headers": \\\\{
                        "autorisation": "Bearer @\\\\{paramètres('firewall_jeton')\\\\}"
                    \\\\},
                    "body": \\\\{
                        "ip_address": "@\\\\{body('Get_incident')?['properties']?['relatedEntities'][0]?['properties']?['address']\\\\}",
                        "reason": "Blocked by Sentinel automation",
                        "duration": "24h"
                    \\\\}
                \\\\}
            \\\\},
            "Send_notification": \\\\{
                "type": "Apiconnexion",
                "inputs": \\\\{
                    "hôte": \\\\{
                        "connexion": \\\\{
                            "name": "@paramètres('$connexions')['teams']['connexionId']"
                        \\\\}
                    \\\\},
                    "method": "post",
                    "path": "/v1.0/teams/@\\\\{encodeURIComponent('security-team-id')\\\\}/channels/@\\\\{encodeURIComponent('incidents')\\\\}/messages",
                    "body": \\\\{
                        "body": \\\\{
                            "content": "🚨 High severity incident detected: @\\\\{body('Get_incident')?['properties']?['title']\\\\}\n\nIncident ID: @\\\\{body('Get_incident')?['properties']?['incidentNumber']\\\\}\nSeverity: @\\\\{body('Get_incident')?['properties']?['severity']\\\\}\nStatus: @\\\\{body('Get_incident')?['properties']?['status']\\\\}\n\nAutomatic response: IP address blocked in firewall"
                        \\\\}
                    \\\\}
                \\\\}
            \\\\}
        \\\\}
    \\\\}
\\\\}

PowerShell Automation

# Automated réponse aux incidents script
param(
    [paramètre(Mandatory=$true)]
    [string]$IncidentId,

    [paramètre(Mandatory=$true)]
    [string]$WorkspaceName,

    [paramètre(Mandatory=$true)]
    [string]$ResourceGroupName
)

# Connect to Azure
Connect-AzAccount -Identity

# Get incident details
$incident = Get-AzSentinelIncident -ResourceGroupName $ResourceGroupName -WorkspaceName $WorkspaceName -IncidentId $IncidentId

# Extract entities
$entities = $incident.RelatedEntities

# processus each entity
foreach ($entity in $entities) \\\\{
    switch ($entity.Kind) \\\\{
        "Account" \\\\{
            # Disable user account
            $userPrincipalName = $entity.Properties.Name
            Write-Output "Disabling user account: $userPrincipalName"

            # Connect to Azure AD
            Connect-AzureAD
            $user = Get-AzureADUser -Filter "userPrincipalName eq '$userPrincipalName'"
            if ($user) \\\\{
                Set-AzureADUser -ObjectId $user.ObjectId -AccountEnabled $false
                Write-Output "User account disabled successfully"
            \\\\}
        \\\\}

        "Ip" \\\\{
            # Block IP address
            $ipAddress = $entity.Properties.Address
            Write-Output "Blocking IP address: $ipAddress"

            # Add to Azure Firewall block list
            $firewallPolicy = Get-AzFirewallPolicy -ResourceGroupName $ResourceGroupName -Name "SecurityPolicy"
            $blockRule = New-AzFirewallPolicyNetworkRule -Name "Block-$ipAddress" -protocole "Any" -SourceAddress $ipAddress -DestinationAddress "*" -Destinationport "*" -Action "Deny"

            # Update firewall policy
            $ruleCollection = $firewallPolicy.RuleCollectionGroups[0].Properties.RuleCollections[0]
            $ruleCollection.Rules += $blockRule
            Set-AzFirewallPolicy -InputObject $firewallPolicy
            Write-Output "IP address blocked successfully"
        \\\\}

        "hôte" \\\\{
            # Isolate hôte
            $hôteName = $entity.Properties.hôteName
            Write-Output "Isolating hôte: $hôteName"

            # Microsoft Defender for Endpoint isolation
            $isolationRequest = @\\\\{
                Comment = "Automated isolation due to security incident $IncidentId"
                IsolationType = "Full"
            \\\\}

            # Call Defender API to isolate machine
            $headers = @\\\\{
                'autorisation' = "Bearer $($env:DEFENDER_jeton)"
                'Content-Type' = 'application/json'
            \\\\}

            Invoke-RestMethod -Uri "https://api.securitycenter.microsoft.com/api/machines/$hôteName/isolate" -Method Post -Headers $headers -Body ($isolationRequest|ConvertTo-Json)
            Write-Output "hôte isolated successfully"
        \\\\}
    \\\\}
\\\\}

# Update incident with automation results
$updateComment = "Automated response completed: Users disabled, IPs blocked, hôtes isolated"
Add-AzSentinelIncidentComment -ResourceGroupName $ResourceGroupName -WorkspaceName $WorkspaceName -IncidentId $IncidentId -Message $updateComment

# Update incident status
Update-AzSentinelIncident -ResourceGroupName $ResourceGroupName -WorkspaceName $WorkspaceName -IncidentId $IncidentId -Status "Active" -Classification "TruePositive"

Write-Output "réponse aux incidents automation completed successfully"

Custom Connectors

# Custom Renseignement sur les Menaces connector
import requests
import json
import hashlib
import hmac
import base64
import datetime
from azure.identity import DefaultAzureCredential
from azure.clévault.secrets import SecretClient

class SentinelConnector:
    def __init__(self, workspace_id, shared_clé):
        self.workspace_id = workspace_id
        self.shared_clé = shared_clé
        self.log_type = "ThreatIntelligence"

    def build_signature(self, date, content_length, method, content_type, resource):
        x_headers = f"x-ms-date:\\\\{date\\\\}"
        string_to_hash = f"\\\\{method\\\\}\n\\\\{content_length\\\\}\n\\\\{content_type\\\\}\n\\\\{x_headers\\\\}\n\\\\{resource\\\\}"
        bytes_to_hash = bytes(string_to_hash, 'utf-8')
        decoded_clé = base64.b64decode(self.shared_clé)
        encoded_hash = base64.b64encode(hmac.new(decoded_clé, bytes_to_hash, digestmod=hashlib.sha256).digest()).decode()
        autorisation = f"Sharedclé \\\\{self.workspace_id\\\\}:\\\\{encoded_hash\\\\}"
        return autorisation

    def send_data(self, body):
        method = 'POST'
        content_type = 'application/json'
        resource = '/api/logs'
        rfc1123date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
        content_length = len(body)

        signature = self.build_signature(rfc1123date, content_length, method, content_type, resource)

        uri = f"https://\\\\{self.workspace_id\\\\}.ods.opinsights.azure.com\\\\{resource\\\\}?api-version=2016-04-01"

        headers = \\\\{
            'content-type': content_type,
            'autorisation': signature,
            'Log-Type': self.log_type,
            'x-ms-date': rfc1123date
        \\\\}

        response = requests.post(uri, data=body, headers=headers)
        return response.status_code

# Renseignement sur les Menaces integration
def collect_threat_intelligence():
    # Collect from multiple sources
    threat_data = []

    # VirusTotal integration
    vt_api_clé = "your_virustotal_api_clé"
    vt_url = "https://www.virustotal.com/vtapi/v2/file/report"

    file_hashes = ["hash1", "hash2", "hash3"]

    for file_hash in file_hashes:
        params = \\\\{'apiclé': vt_api_clé, 'resource': file_hash\\\\}
        response = requests.get(vt_url, params=params)

        if response.status_code == 200:
            vt_data = response.json()
            threat_data.append(\\\\{
                "timestamp": datetime.datetime.utcnow().isoformat(),
                "source": "VirusTotal",
                "indicator_type": "file_hash",
                "indicator_value": file_hash,
                "threat_type": "logiciel malveillant" if vt_data.get('positives', 0) > 0 else "clean",
                "confidence": vt_data.get('positives', 0) / vt_data.get('total', 1) * 100,
                "raw_data": json.dumps(vt_data)
            \\\\})

    # MISP integration
    misp_url = "https://your-misp-instance.com"
    misp_clé = "your_misp_api_clé"

    misp_headers = \\\\{
        'autorisation': misp_clé,
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    \\\\}

    misp_response = requests.get(f"\\\\{misp_url\\\\}/attributes/restSearch", headers=misp_headers)

    if misp_response.status_code == 200:
        misp_data = misp_response.json()
        for attribute in misp_data.get('response', \\\\{\\\\}).get('Attribute', []):
            threat_data.append(\\\\{
                "timestamp": datetime.datetime.utcnow().isoformat(),
                "source": "MISP",
                "indicator_type": attribute.get('type'),
                "indicator_value": attribute.get('value'),
                "threat_type": attribute.get('category'),
                "confidence": 85,
                "raw_data": json.dumps(attribute)
            \\\\})

    return threat_data

# Send to Sentinel
def main():
    workspace_id = "your_workspace_id"
    shared_clé = "your_shared_clé"

    connector = SentinelConnector(workspace_id, shared_clé)
    threat_data = collect_threat_intelligence()

    if threat_data:
        body = json.dumps(threat_data)
        response_code = connector.send_data(body)
        print(f"Data sent to Sentinel. Response code: \\\\{response_code\\\\}")
    else:
        print("No Renseignement sur les Menaces data to send")

if __name__ == "__main__":
    main()

Workbooks and Visualization

Security Operations Workbook

// Security overview dashboard queries
// Failed logins by location
SigninLogs
|where TimeGenerated > ago(24h)
|where ResultType != "0"
|summarize FailedLogins = count() by Location
|sort by FailedLogins desc
|take 10

// Top attacked users
SecurityEvent
|where TimeGenerated > ago(24h)
|where EventID == 4625
|summarize FailedAttempts = count() by Account
|sort by FailedAttempts desc
|take 10

// Security alerts by severity
SecurityAlert
|where TimeGenerated > ago(7d)
|summarize AlertCount = count() by AlertSeverity
|render piechart

// réponse aux incidents metrics
SecurityIncident
|where TimeGenerated > ago(30d)
|extend TimeToClose = iff(Status == "Closed", ClosedTime - CreatedTime, now() - CreatedTime)
|summarize
    AvgTimeToClose = avg(TimeToClose),
    TotalIncidents = count(),
    OpenIncidents = countif(Status != "Closed")
    by bin(TimeGenerated, 1d)
|render timechart

chasse aux menaces Workbook

// Suspicious processus execution timeline
SecurityEvent
|where TimeGenerated > ago(7d)
|where EventID == 4688
|where processus has_any ("powershell.exe", "cmd.exe", "wscript.exe", "cscript.exe")
|where commandeLine has_any ("Invoke-", "Download", "http", "ftp", "base64")
|summarize processusCount = count() by bin(TimeGenerated, 1h), processus
|render timechart

// Network communication patterns
CommonSecurityLog
|where TimeGenerated > ago(24h)
|where DeviceVendor == "Palo Alto Networks"
|where Activity == "TRAFFIC"
|summarize
    TotalBytes = sum(SentBytes + ReceivedBytes),
    sessionCount = count()
    by SourceIP, DestinationIP
|where TotalBytes > 1000000000  // 1GB threshold
|sort by TotalBytes desc

// DNS query analysis
DnsEvents
|where TimeGenerated > ago(24h)
|where QueryType in ("TXT", "NULL", "CNAME")
|summarize QueryCount = count() by Name, ClientIP
|where QueryCount > 100
|sort by QueryCount desc

// File creation in suspicious locations
SecurityEvent
|where TimeGenerated > ago(24h)
|where EventID == 4663
|where ObjectName has_any ("\\temp\\", "\\appdata\\", "\\programdata\\")
|where ObjectName endswith ".exe" or ObjectName endswith ".dll" or ObjectName endswith ".bat"
|summarize FileCount = count() by Computer, Account, ObjectName
|sort by FileCount desc

Performance Optimization and Best Practices

Query Optimization

// Optimized query structure
SecurityEvent
|where TimeGenerated > ago(1h)  // Filter time first
|where EventID == 4624          // Filter specific events
|where LogonType == 2           // Additional filters early
|project TimeGenerated, Computer, Account, LogonType  // Project only needed columns
|summarize LoginCount = count() by Computer, Account
|where LoginCount > 10
|sort by LoginCount desc

// Use summarize instead of distinct when possible
// Less efficient
SecurityEvent
|where TimeGenerated > ago(1h)
|distinct Computer

// More efficient
SecurityEvent
|where TimeGenerated > ago(1h)
|summarize by Computer

// Optimize joins
// Use smaller table on the right side of join
let SmallTable =
    SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4624
|summarize by Account;
SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4625
|join kind=inner SmallTable on Account

Data Retention and Cost Management

// Monitor data ingestion by table
utilisation
|where TimeGenerated > ago(30d)
|where IsBillable == true
|summarize TotalVolumeGB = sum(Quantity) / 1000 by DataType
|sort by TotalVolumeGB desc

// Analyze query costs
// Check scan data volume
SecurityEvent
|where TimeGenerated > ago(1h)
|summarize count()
// This query scans all SecurityEvent data for 1 hour

// Optimize with specific filters
SecurityEvent
|where TimeGenerated > ago(1h)
|where EventID == 4624  // Reduces scan volume significantly
|summarize count()

// Set up data retention policies
// Configure via PowerShell
$retentionPolicy = @\\\\{
    ResourceGroupName = "sentinel-rg"
    WorkspaceName = "sentinel-workspace"
    TableName = "SecurityEvent"
    RetentionInDays = 90
\\\\}

Set-AzOperationalInsightsTable @retentionPolicy

Security and Compliance

// Monitor privileged access to Sentinel
AuditLogs
|where TimeGenerated > ago(24h)
|where Category == "RoleManagement"
|where ActivityDisplayName has_any ("Add member to role", "Remove member from role")
|where cibleResources[0].displayName has "Sentinel"
|project TimeGenerated, InitiatedBy.user.userPrincipalName, ActivityDisplayName, cibleResources[0].displayName

// Track data access patterns
LAQueryLogs
|where TimeGenerated > ago(7d)
|summarize
    QueryCount = count(),
    DataScannedGB = sum(DataScanned_MB) / 1000,
    UniqueQueries = dcount(QueryText)
    by AADEmail
|sort by DataScannedGB desc

// Compliance reporting
SecurityEvent
|where TimeGenerated > ago(30d)
|where EventID in (4624, 4625, 4648, 4672)
|summarize
    TotalEvents = count(),
    SuccessfulLogins = countif(EventID == 4624),
    FailedLogins = countif(EventID == 4625),
    PrivilegeUse = countif(EventID == 4672)
    by bin(TimeGenerated, 1d)
|extend ComplianceScore = (SuccessfulLogins * 1.0) / (SuccessfulLogins + FailedLogins) * 100
|project TimeGenerated, TotalEvents, ComplianceScore

dépannage and Maintenance

Common Issues and Solutions

// Check data connector health
Heartbeat
|where TimeGenerated > ago(1h)
|summarize LastHeartbeat = max(TimeGenerated) by Computer
|where LastHeartbeat < ago(15m)
|project Computer, LastHeartbeat, Status = "Missing"

// Verify data ingestion
utilisation
|where TimeGenerated > ago(1h)
|summarize IngestedMB = sum(Quantity) by DataType
|where IngestedMB == 0
|project DataType, Status = "No data ingested"

// Analytics rule performance
SecurityAlert
|where TimeGenerated > ago(24h)
|summarize AlertCount = count() by AlertName
|join kind=leftouter (
    SecurityIncident
|where TimeGenerated > ago(24h)
|summarize IncidentCount = count() by Title
) on $left.AlertName == $right.Title
|extend FalsePositiveRate = (AlertCount - IncidentCount) * 100.0 / AlertCount
|where FalsePositiveRate > 80
|project AlertName, AlertCount, IncidentCount, FalsePositiveRate

Performance Monitoring

// Query performance analysis
LAQueryLogs
|where TimeGenerated > ago(24h)
|where ResponseCode == 200
|summarize
    AvgDuration = avg(DurationMs),
    MaxDuration = max(DurationMs),
    QueryCount = count()
    by bin(TimeGenerated, 1h)
|render timechart

// Resource utilization
Perf
|where TimeGenerated > ago(1h)
|where ObjectName == "processusor" and CounterName == "% processusor Time"
|summarize AvgCPU = avg(CounterValue) by Computer
|where AvgCPU > 80

// Storage utilization
utilisation
|where TimeGenerated > ago(30d)
|summarize TotalGB = sum(Quantity) / 1000 by Solution
|sort by TotalGB desc

Resources