Detection rules › Kusto

Lookout - Device Compliance and Security Status Changes (v2)

Status
available
Severity
medium
Time window
30m
Source
github.com/Azure/Azure-Sentinel

'Monitors device compliance status changes and security posture degradation using Lookout Mobile Risk API v2 enhanced device fields. Detects devices becoming non-compliant, security status changes, and potential device compromise indicators with detailed device context and MDM integration data.'

MITRE ATT&CK coverage

TacticTechniques
Defense EvasionT1629 Impair Defenses, T1655 Masquerading
DiscoveryT1418 Software Discovery

Rule body kusto

id: 9c5b6d8f-3a02-4e9b-af4c-2d7e9b1f5a8c
name: Lookout - Device Compliance and Security Status Changes (v2)
description: |
  'Monitors device compliance status changes and security posture degradation using Lookout Mobile Risk API v2 enhanced device fields. Detects devices becoming non-compliant, security status changes, and potential device compromise indicators with detailed device context and MDM integration data.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: LookoutAPI
    dataTypes:
      - LookoutEvents
queryFrequency: 10m
queryPeriod: 30m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
  - DefenseEvasion
  - Persistence
relevantTechniques:
  - T1418
  - T1629
  - T1655
query: |
  LookoutEvents
  | where EventType == "DEVICE"
  | where DeviceComplianceStatus in ("Non-Compliant", "Partial") 
     or DeviceSecurityStatus in ("THREATS_HIGH", "THREATS_MEDIUM")
     or ChangeType == "UPDATE"
  | extend 
      DeviceRiskScore = case(
          DeviceSecurityStatus == "THREATS_HIGH", 9,
          DeviceSecurityStatus == "THREATS_MEDIUM", 6,
          DeviceSecurityStatus == "THREATS_LOW", 3,
          DeviceComplianceStatus == "Non-Compliant", 7,
          DeviceComplianceStatus == "Partial", 4,
          1
      ),
      ComplianceReason = case(
          isempty(DeviceCheckinTime), "No Recent Check-in",
          DeviceActivationStatus != "ACTIVE", "Inactive Device",
          isempty(ClientLookoutSDKVersion), "Missing Security Client",
          "Configuration Issue"
      ),
      PlatformRisk = case(
          DevicePlatform == "ANDROID" and DeviceOSVersion matches regex @"^[1-9]\..*", "Outdated Android",
          DevicePlatform == "IOS" and DeviceOSVersion matches regex @"^1[0-4]\..*", "Outdated iOS",
          DevicePlatform == "UNKNOWN", "Unknown Platform",
          "Current"
      )
  | extend MDMIntegrationStatus = case(
      isnotempty(MDMConnectorId) and isnotempty(MDMExternalId), "Fully Integrated",
      isnotempty(MDMConnectorId), "Partial Integration", 
      "Not Integrated"
  )
  | extend SecurityPosture = case(
      DeviceRiskScore >= 8, "Critical",
      DeviceRiskScore >= 6, "High",
      DeviceRiskScore >= 4, "Medium",
      "Low"
  )
  | project
      TimeGenerated,
      EventId,
      DeviceGuid,
      DevicePlatform,
      DeviceOSVersion,
      DeviceManufacturer,
      DeviceModel,
      DeviceEmailAddress,
      DeviceActivationStatus,
      DeviceSecurityStatus,
      DeviceComplianceStatus,
      DeviceRiskScore,
      SecurityPosture,
      ComplianceReason,
      PlatformRisk,
      DeviceCheckinTime,
      DeviceActivatedAt,
      DeviceDeactivatedAt,
      DeviceGroupGuid,
      ClientLookoutSDKVersion,
      ClientOTAVersion,
      ClientPackageName,
      ClientPackageVersion,
      MDMConnectorId,
      MDMConnectorUuid,
      MDMExternalId,
      MDMIntegrationStatus,
      ActorType,
      ActorGuid,
      ChangeType
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: DeviceEmailAddress
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceGuid
      - identifier: OSFamily
        columnName: DevicePlatform
      - identifier: OSVersion
        columnName: DeviceOSVersion
customDetails:
  DevicePlatform: DevicePlatform
  DeviceSecStatus: DeviceSecurityStatus
  DevCompliance: DeviceComplianceStatus
  DeviceRiskScore: DeviceRiskScore
  SecurityPosture: SecurityPosture
  ComplianceReason: ComplianceReason
  PlatformRisk: PlatformRisk
  MDMIntegration: MDMIntegrationStatus
  ClientSDKVersion: ClientLookoutSDKVersion
  DeviceManufacturer: DeviceManufacturer
  DeviceModel: DeviceModel
alertDetailsOverride:
  alertDisplayNameFormat: "Device Compliance Issue: {{SecurityPosture}} Risk on {{DevicePlatform}} Device"
  alertDescriptionFormat: "{{SecurityPosture}} posture with {{DeviceComplianceStatus}} compliance"
  alertTacticsColumnName: SecurityPosture
  alertSeverityColumnName: SecurityPosture
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByEntities:
      - Account
      - Host
    groupByAlertDetails:
      - DeviceGuid
    groupByCustomDetails:
      - SecurityPosture
      - DevicePlatform
eventGroupingSettings:
  aggregationKind: AlertPerResult
suppressionEnabled: false
suppressionDuration: PT30M
version: 2.0.3
kind: Scheduled

Stages and Predicates

Stage 1: source

LookoutEvents

Stage 2: where

| where EventType == "DEVICE"

Stage 3: where

| where DeviceComplianceStatus in ("Non-Compliant", "Partial") 
   or DeviceSecurityStatus in ("THREATS_HIGH", "THREATS_MEDIUM")
   or ChangeType == "UPDATE"

Stage 4: extend (3 consecutive steps)

| extend 
    DeviceRiskScore = case(
        DeviceSecurityStatus == "THREATS_HIGH", 9,
        DeviceSecurityStatus == "THREATS_MEDIUM", 6,
        DeviceSecurityStatus == "THREATS_LOW", 3,
        DeviceComplianceStatus == "Non-Compliant", 7,
        DeviceComplianceStatus == "Partial", 4,
        1
    ),
    ComplianceReason = case(
        isempty(DeviceCheckinTime), "No Recent Check-in",
        DeviceActivationStatus != "ACTIVE", "Inactive Device",
        isempty(ClientLookoutSDKVersion), "Missing Security Client",
        "Configuration Issue"
    ),
    PlatformRisk = case(
        DevicePlatform == "ANDROID" and DeviceOSVersion matches regex @"^[1-9]\..*", "Outdated Android",
        DevicePlatform == "IOS" and DeviceOSVersion matches regex @"^1[0-4]\..*", "Outdated iOS",
        DevicePlatform == "UNKNOWN", "Unknown Platform",
        "Current"
    )
| extend MDMIntegrationStatus = case(
    isnotempty(MDMConnectorId) and isnotempty(MDMExternalId), "Fully Integrated",
    isnotempty(MDMConnectorId), "Partial Integration", 
    "Not Integrated"
)
| extend SecurityPosture = case(
    DeviceRiskScore >= 8, "Critical",
    DeviceRiskScore >= 6, "High",
    DeviceRiskScore >= 4, "Medium",
    "Low"
)
ComplianceReason =
ifisempty(DeviceCheckinTime)"No Recent Check-in"
elifDeviceActivationStatus != "ACTIVE""Inactive Device"
elifisempty(ClientLookoutSDKVersion)"Missing Security Client"
else"Configuration Issue"
DeviceRiskScore =
ifDeviceSecurityStatus == "THREATS_HIGH"9
elifDeviceSecurityStatus == "THREATS_MEDIUM"6
elifDeviceSecurityStatus == "THREATS_LOW"3
elifDeviceComplianceStatus == "Non-Compliant"7
elifDeviceComplianceStatus == "Partial"4
else1
PlatformRisk =
ifDevicePlatform == "ANDROID" and DeviceOSVersion matches regex @"^[1-9]\..*""Outdated Android"
elifDevicePlatform == "IOS" and DeviceOSVersion matches regex @"^1[0-4]\..*""Outdated iOS"
elifDevicePlatform == "UNKNOWN""Unknown Platform"
else"Current"

Stage 5: project

| project
    TimeGenerated,
    EventId,
    DeviceGuid,
    DevicePlatform,
    DeviceOSVersion,
    DeviceManufacturer,
    DeviceModel,
    DeviceEmailAddress,
    DeviceActivationStatus,
    DeviceSecurityStatus,
    DeviceComplianceStatus,
    DeviceRiskScore,
    SecurityPosture,
    ComplianceReason,
    PlatformRisk,
    DeviceCheckinTime,
    DeviceActivatedAt,
    DeviceDeactivatedAt,
    DeviceGroupGuid,
    ClientLookoutSDKVersion,
    ClientOTAVersion,
    ClientPackageName,
    ClientPackageVersion,
    MDMConnectorId,
    MDMConnectorUuid,
    MDMExternalId,
    MDMIntegrationStatus,
    ActorType,
    ActorGuid,
    ChangeType

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
ChangeTypeeq
  • UPDATE transforms: cased
DeviceComplianceStatusin
  • Non-Compliant transforms: cased
  • Partial transforms: cased
DeviceSecurityStatusin
  • THREATS_HIGH transforms: cased
  • THREATS_MEDIUM transforms: cased
EventTypeeq
  • DEVICE transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
ActorGuidproject
ActorTypeproject
ChangeTypeproject
ClientLookoutSDKVersionproject
ClientOTAVersionproject
ClientPackageNameproject
ClientPackageVersionproject
ComplianceReasonproject
DeviceActivatedAtproject
DeviceActivationStatusproject
DeviceCheckinTimeproject
DeviceComplianceStatusproject
DeviceDeactivatedAtproject
DeviceEmailAddressproject
DeviceGroupGuidproject
DeviceGuidproject
DeviceManufacturerproject
DeviceModelproject
DeviceOSVersionproject
DevicePlatformproject
DeviceRiskScoreproject
DeviceSecurityStatusproject
EventIdproject
MDMConnectorIdproject
MDMConnectorUuidproject
MDMExternalIdproject
MDMIntegrationStatusproject
PlatformRiskproject
SecurityPostureproject
TimeGeneratedproject