Query Details

HUNT 20 M365 Guest Elevated Permissions 90d

Query

// Hunt    : M365 - Guest Users with Elevated Permissions (Admin Roles or Full Control) (90d)
// Purpose : Identify external guest accounts (#EXT#) that have been granted elevated
//           roles such as SharePoint Site Collection Administrator, Teams channel/team
//           Owner, or Exchange FullAccess delegation. Guests with elevated permissions
//           represent an elevated insider threat risk and should be reviewed periodically.
//           Includes activity profile post-elevation to flag misuse.
// Tables  : OfficeActivity
// Period  : P90D
// Tactics : Persistence, Privilege Escalation, Collection
// MITRE   : T1098.003, T1069.003, T1530
//==========================================================================================

let LookbackDays = 90d;

// --- Step 1: Elevation events where the RECIPIENT is a guest ---
let GuestElevations = OfficeActivity
    | where TimeGenerated > ago(LookbackDays)
    | where Operation in (
        // SharePoint admin grants
        "SiteCollectionAdminAdded",
        "PermissionLevelAdded",
        "AddedToSecureLink",
        // Exchange delegation
        "Add-MailboxPermission",
        "Add-RecipientPermission",
        // Teams ownership grants
        "MemberRoleChanged",
        "TeamCreated",
        "ChannelAdded")
    // Target is a guest
    | where TargetUserOrGroupName has "#EXT#"
        or TargetUserOrGroupName has "guest"
        or UserId has "#EXT#"
    | extend
        GuestUPN        = tolower(coalesce(
                            iif(TargetUserOrGroupName has "#EXT#", TargetUserOrGroupName, ""),
                            iif(UserId has "#EXT#", UserId, ""))),
        ElevationType   = case(
            Operation in ("SiteCollectionAdminAdded", "PermissionLevelAdded"),
                "SharePointAdmin",
            Operation in ("AddedToSecureLink"),
                "SecureLinkMember",
            Operation in ("Add-MailboxPermission", "Add-RecipientPermission"),
                "MailboxDelegate",
            Operation in ("MemberRoleChanged"),
                "TeamsRoleChange",
            "Other"),
        ElevatedAt      = TimeGenerated,
        TargetResource  = coalesce(Site_Url, OfficeObjectId, TeamName)
    | where isnotempty(GuestUPN)
    | extend ExternalDomain = extract(@"_([^_#]+)#EXT#", 1, tolower(GuestUPN))
    | summarize
        ElevationCount  = count(),
        ElevationTypes  = make_set(ElevationType, 10),
        FirstElevated   = min(ElevatedAt),
        LastElevated    = max(ElevatedAt),
        ElevatedOn      = make_set(TargetResource, 10),
        GrantedByUsers  = make_set(UserId, 5)
        by GuestUPN, ExternalDomain;

// --- Step 2: Post-elevation activity of those same guests ---
let GuestNames = GuestElevations | project GuestUPN;

let PostElevationActivity = OfficeActivity
    | where TimeGenerated > ago(LookbackDays)
    | where UserId has "#EXT#"
    | join kind=inner GuestNames on $left.UserId == $right.GuestUPN
    | extend IsExfilOp = Operation in (
        "FileDownloaded", "FileAccessed", "FileSyncDownloadedFull",
        "MailItemsAccessed", "Send", "AnonymousLinkCreated",
        "SecureLinkCreated", "SharingInvitationCreated")
    | summarize
        TotalPostElevationEvents   = count(),
        LastActivityDate           = max(TimeGenerated),
        ExfilOpCount               = countif(IsExfilOp),
        DistinctSites              = dcount(Site_Url),
        DistinctOps                = dcount(Operation),
        Workloads                  = make_set(RecordType, 5)
        by UserId;

// --- Step 3: Combine ---
GuestElevations
| join kind=leftouter PostElevationActivity
    on $left.GuestUPN == $right.UserId
| extend
    IsActive            = isnotnull(LastActivityDate),
    DaysSinceActivity   = iif(isnotnull(LastActivityDate),
        datetime_diff("day", now(), LastActivityDate),
        datetime_diff("day", now(), LastElevated)),
    ActivityStatus      = case(
        isnull(LastActivityDate),                    "NeverActivePostElevation",
        datetime_diff("day", now(), LastActivityDate) >= 60, "Dormant",
        datetime_diff("day", now(), LastActivityDate) >= 30, "LowActivity",
        "Active")
| extend RiskScore = toint(
      iif(ElevationCount  >= 3, 2, 0)
    + iif(ExfilOpCount    >  0, 2, 0)
    + iif(DistinctSites   >= 5, 1, 0)
    + iif(ElevationTypes has "SharePointAdmin" or ElevationTypes has "MailboxDelegate", 2, 0))
| project
    GuestUPN,
    ExternalDomain,
    ElevationCount,
    ElevationTypes,
    FirstElevated,
    LastElevated,
    ElevatedOn,
    GrantedByUsers,
    TotalPostElevationEvents  = coalesce(TotalPostElevationEvents, 0),
    LastActivityDate,
    DaysSinceActivity,
    ActivityStatus,
    ExfilOpCount              = coalesce(ExfilOpCount, 0),
    DistinctSites             = coalesce(DistinctSites, 0),
    Workloads,
    RiskScore
| sort by RiskScore desc, ElevationCount desc

Explanation

This query is designed to identify and analyze external guest users in Microsoft 365 who have been granted elevated permissions, such as administrative roles or full control, within the last 90 days. The goal is to assess the potential insider threat risk posed by these users and monitor their activities after receiving elevated permissions. Here's a simplified breakdown of the query:

  1. Identify Guest Elevations:

    • The query first looks for events where external guest users (identified by "#EXT#" or "guest" in their usernames) have been granted elevated permissions. These permissions include roles like SharePoint Site Collection Administrator, Teams channel/team Owner, or Exchange FullAccess delegation.
    • It captures details such as the type of elevation, when it occurred, and who granted the permissions.
  2. Monitor Post-Elevation Activity:

    • The query then tracks the activities of these guest users after they have been elevated. It looks for specific operations that might indicate data exfiltration or misuse, such as file downloads or email access.
    • It summarizes the number of activities, the last activity date, and the diversity of operations and sites accessed.
  3. Combine and Assess Risk:

    • The query combines the elevation and activity data to assess the risk associated with each guest user.
    • It calculates a risk score based on factors like the number of elevations, presence of exfiltration operations, and the types of elevated roles.
    • It categorizes users based on their activity status (e.g., active, dormant) and sorts them by risk score to prioritize review.

Overall, this query helps organizations identify potentially risky guest accounts with elevated permissions and monitor their activities to mitigate insider threats.

Details

David Alonso profile picture

David Alonso

Released: March 18, 2026

Tables

OfficeActivity

Keywords

GuestUsersPermissionsSharePointTeamsExchangeActivityRiskScore

Operators

letagoinhasextendtolowercoalesceiifcaseisnotemptyextractsummarizecountmake_setminmaxprojectjoinkindon==isnotnulldatetime_diffnowtointorwherebysortdesc=

Actions