↳ GitHub sourceAnalytics ruleHigh

Power Apps - Multiple users access a malicious link after launching new app

Description

Identifies a chain of events, where a new Power App is created, followed by mulitple users launching the app within the detection window and clicking on the same malicious URL.
Rule type
Scheduled
Version
3.2.0
Declared status
Available
Query frequency
1h
Query period
14d
Trigger
gt 0

Declared MITRE coverage

Declared sources

Metadata from the source file. No dependencies inferred from KQL.

Connectors

Data types

KQL query

Original query, unchanged.

// Define a threshold (distinct_user_launch_threshold) for
// the minimum number of users who launched an app
// to be in scope of this detection
let distinct_user_launch_threshold = 2;
// Define a threshold for the minumum number of users
// who clicked the same malicious link after launching the app
// to be in scope of this detection
let distinct_user_url_click_threshold = 2;
let query_frequency = 1h;
let query_lookback = 14d;
let new_app_creation_activity = materialize(
    PowerPlatformAdminActivity
    | where TimeGenerated >= ago (query_lookback)
    | where EventOriginalType == "CreatePowerApp"
    | extend Properties = tostring(PropertyCollection)
    | extend SrcIpAddr = extract(@'"enduser.ip_address","Value":"([^"]+)"', 1, Properties)
    | extend SrcIpAddr = iif(SrcIpAddr startswith '::ffff:', replace_string(SrcIpAddr, '::ffff:', ''), SrcIpAddr)
    | extend AppId = extract(@'"powerplatform.analytics.resource.power_app.id","Value":"([^"]+)"', 1, Properties)
    | extend AppId = tolower(replace_string(AppId, '/providers/Microsoft.PowerApps/apps/', ''))
    | extend
        AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties),
        EnvironmentId = extract(@'"powerplatform.analytics.resource.environment.id","Value":"([^"]+)"', 1, Properties)
    | project-rename
        AppCreatedTime = TimeGenerated,
        AppCreator = ActorName,
        AppCreatorIpAddr = SrcIpAddr
    );
let distinct_apps = new_app_creation_activity
    | distinct AppName;
let new_app_launch_activity = materialize(
    new_app_creation_activity
    | join kind=inner (
        PowerPlatformAdminActivity
        | where TimeGenerated >= ago (query_lookback)
        | where EventOriginalType == "LaunchPowerApp"
        | where PropertyCollection has_any (distinct_apps)
        | extend Properties = tostring(PropertyCollection)
        | extend AppName = extract(@'"powerplatform.analytics.resource.power_app.display_name","Value":"([^"]+)"', 1, Properties)
        | summarize FirstAppLaunchTime = min(TimeGenerated) by ActorName, AppName)
        on AppName
    | where FirstAppLaunchTime > AppCreatedTime
    );
let new_app_launch_users = new_app_launch_activity
    | summarize LaunchCount = dcount(ActorName) by AppName
    | where LaunchCount > distinct_user_launch_threshold
    | join kind=inner new_app_launch_activity on AppName
    | summarize
        by
        ActorName,
        FirstAppLaunchTime,
        AppName,
        AppId,
        EnvironmentId,
        AppCreator,
        AppCreatorIpAddr;
let detected_urls = union isfuzzy=true
        (
        SecurityAlert
        | where TimeGenerated >= ago (query_lookback)
        | where Entities has_cs '"Type":"url"'
        | mv-expand todynamic(Entities)
        | where tostring(Entities.Type) == "url"
        | project Url = tostring(Entities.Url), Source = "SecurityAlert"
        ),
        (
        ThreatIntelligenceIndicator
        | where TimeGenerated >= ago(query_lookback)
        | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by IndicatorId
        | where Active == true and ExpirationDateTime > now()
        | where isnotempty(isnotempty(Url))
        | project Url, Source = "ThreatIntelligence"
        )
    | summarize by Url, Source;
let url_click_events = materialize(
    union isfuzzy=true
        (
        UrlClickEvents
        | where TimeGenerated >= ago(query_frequency)
        | where isnotempty(ThreatTypes)
        | join kind=inner (new_app_launch_users) on $left.AccountUpn == $right.ActorName
        | where TimeGenerated between (FirstAppLaunchTime .. (FirstAppLaunchTime + 1h))
        | summarize by ActorName, Url, Source = "MicrosoftDefender"
        ),
        (
        _Im_WebSession
        | where TimeGenerated >= ago(query_frequency)
        | join kind=inner (new_app_launch_users) on $left.SrcUsername == $right.ActorName
        | join kind=inner (detected_urls) on Url
        | where TimeGenerated between (FirstAppLaunchTime .. (FirstAppLaunchTime + 1h))
        | summarize by ActorName, Url, Source
        )
    );
let distinct_url_click_events_count = toscalar(
    url_click_events
    | summarize DistinctUserCount = dcount(ActorName) by Url
    | where DistinctUserCount > distinct_user_url_click_threshold
    | summarize sum(DistinctUserCount)
    );
url_click_events
| summarize DistinctUserCount = dcount(ActorName) by Url
| where DistinctUserCount >= distinct_user_url_click_threshold
| join kind=inner url_click_events on Url
| join kind=inner (new_app_launch_users) on ActorName
| extend
    PowerAppsEntityId = 27593,
    DataverseId = 32780,
    AccountName = tostring(split(ActorName, '@')[0]),
    UPNSuffix = tostring(split(ActorName, '@')[1])
| project
    FirstAppLaunchTime,
    AppCreator,
    AppName,
    AppId,
    ImpactedUser = ActorName,
    AccountName,
    UPNSuffix,
    EnvironmentId,
    Url,
    Source,
    PowerAppsEntityId

Declared entities

CloudApplicationURLAccount

Related content

Links established from declared identifiers and solution manifests.

Source provenance

GitHub

Displayed values come from files in Azure/Azure-Sentinel. They describe the published template, not your workspace configuration.

Source identifier
4bd7e93a-0646-4e02-8dcb-aa16d16618f4
Additional source files 2Solutions/Microsoft Business Applications/Analytic Rules/Power Apps - Multiple users access a malicious link after launching new app.yamlsource ↗Solutions/Microsoft Business Applications/Data/Solution_PowerPlatform.jsonsolution-membership ↗
GSTEP / CATALOG TRACKING

Added to catalog : 16 Sept 2026 · 05:49 UTC
Last change observed : 16 Sept 2026 · 05:49 UTC

GSTEP sync dates, separate from the source content’s publication dates.