SOCLIFE
SOCLIFE

Evidence-led security analysis

Published knowledge

Search SOC//LIFE

CasesCASE-004

SOC investigation

OAuth Consent Grant Gives a Third-Party App Persistent Access

A legitimate Microsoft consent screen was abused to authorize an unverified third-party application, creating persistent cloud access that password-only remediation would not remove.

Based on publicly reported attack activity. User identities and workstation names have been anonymized.

User
denis@example.com
Initial alert
Unexpected consent to an unverified OAuth application
Severity
High

Case story

What happened

A user authorized the unverified Share-File Point Document application through a legitimate Microsoft consent screen, creating a grant that required application-focused remediation rather than password reset alone.

  1. Consent link opens a legitimate Microsoft authorization flow

    The target follows an OAuth consent link and is shown a Microsoft-hosted application permission prompt.

  2. Unverified third-party app receives user consent

    The user authorizes Share-File Point Document for the requested delegated permissions.

  3. Application gains token-backed access

    The authorized application can receive access tokens and refresh tokens for the approved scopes.

  4. Password-only remediation is insufficient

    The consent grant and application permissions must be reviewed and revoked directly.

Investigation

What was checked

Follow how the analyst tested and revised explanations. This is discovery order, not event chronology.
  1. Confirm the consent event

    Microsoft's documented consent-phishing behavior maps directly to the Entra application-consent audit trail.

    Next pivot

    Parse the granted permission scope and identify durable or sensitive delegated access.

    View query
    Q-01

    Recover the OAuth consent event

    What this checks

    Find successful application-consent events for denis@example.com and preserve the app, service-principal, IP, permissions, consent type, and correlation context.

    KQL
    let target_user = "denis@example.com";
    AuditLogs
    | where TimeGenerated >= ago(7d)
    | where LoggedByService =~ "Core Directory"
    | where Category =~ "ApplicationManagement"
    | where OperationName =~ "Consent to application"
    | where Result =~ "success"
    | extend
        InitiatingUser = tolower(tostring(InitiatedBy.user.userPrincipalName)),
        InitiatingIP = tostring(InitiatedBy.user.ipAddress)
    | where InitiatingUser == tolower(target_user)
    | mv-apply TargetResource = TargetResources on (
        where TargetResource.type =~ "ServicePrincipal"
        | extend
            AppDisplayName = tostring(TargetResource.displayName),
            ServicePrincipalId = tostring(TargetResource.id),
            ModifiedProperties = TargetResource.modifiedProperties
    )
    | mv-apply Property = ModifiedProperties on (
        where Property.displayName =~ "ConsentAction.Permissions"
        | extend ConsentFull = trim(@'"', tostring(Property.newValue))
    )
    | parse ConsentFull with * "ConsentType: " ConsentType ", Scope: " GrantScope "]" *
    | project
        TimeGenerated,
        InitiatingUser,
        InitiatingIP,
        AppDisplayName,
        ServicePrincipalId,
        ConsentType,
        GrantScope,
        CorrelationId
    | order by TimeGenerated asc
    SPL
    index=<entra_audit_index> sourcetype=<entra_audit_sourcetype>
    earliest=-7d
    (OperationName="Consent to application" OR ActivityDisplayName="Consent to application")
    | eval
        user=lower(coalesce(user, userPrincipalName, InitiatedByUserPrincipalName)),
        src=coalesce(src, ipAddress, InitiatingIpAddress),
        app_name=coalesce(app_name, TargetResourceDisplayName, AppDisplayName),
        service_principal_id=coalesce(service_principal_id, TargetResourceId, ServicePrincipalId),
        permissions=coalesce(permissions, ConsentActionPermissions, GrantScope),
        consent_type=coalesce(consent_type, ConsentType),
        correlation_id=coalesce(correlation_id, CorrelationId),
        result=lower(coalesce(result, Result))
    | where user="denis@example.com" AND result="success"
    | fields _time user src app_name service_principal_id permissions consent_type correlation_id
    | sort 0 _time
    What to look for

    A successful consent event for the affected user that identifies the target app and preserves the granted scope string.

    Technical details
    Tested signal

    Successful Consent to application activity initiated by the affected user.

    Assumptions
    • Microsoft Entra AuditLogs is retained for the review period.
    • The consent event retains the target-resource permission details required to recover the granted scopes.
    Data requirements and relevant fields
    identity

    Entra application-management audit events with initiating user, source IP, target service principal, modified properties, result, and correlation ID.

    • TimeGenerated
    • LoggedByService
    • Category
    • OperationName
    • Result
    • InitiatedBy
    • TargetResources
    • AdditionalDetails
    • CorrelationId
    KQL schema

    Uses documented Microsoft Entra/Azure Monitor fields; validate connector availability and local retention.

    SPL schema

    Replace index/sourcetype placeholders and map the documented concepts to the local Entra audit or sign-in source.

    Limitations
    • Some export pipelines flatten TargetResources differently.
    • The app display name is not a stable security boundary; preserve the service-principal identifier and CorrelationId.

    The KQL follows Microsoft Entra AuditLogs application-consent semantics. SPL intentionally uses a normalized raw scaffold because consent permission fields are not consistently represented by CIM.

    More reasoning

    Observation

    An unexpected OAuth application consent preceded the persistent-access concern.

    Working explanation

    The affected user may have granted a third-party service principal access through a legitimate Entra consent flow.

    What was checked

    Recover the Consent to application event, target service principal, initiating IP, permissions, consent type, and CorrelationId.

    Interpretation

    The consent event is the primary evidence object and should preserve stable application identifiers rather than rely on display name alone.

    Supporting evidence
  2. Review the granted scopes

    Microsoft notes that consent phishing can yield access and refresh tokens for the requested scopes.

    Next pivot

    Determine whether the application is new and whether other users consented to it.

    View query
    Q-02

    Review the granted permission scope

    What this checks

    Extract the delegated scopes from the consent event and highlight durable or high-value access such as offline access, mail, files, or contacts.

    KQL
    let target_user = "denis@example.com";
    AuditLogs
    | where TimeGenerated >= ago(7d)
    | where LoggedByService =~ "Core Directory"
    | where Category =~ "ApplicationManagement"
    | where OperationName =~ "Consent to application"
    | where Result =~ "success"
    | extend InitiatingUser = tolower(tostring(InitiatedBy.user.userPrincipalName))
    | where InitiatingUser == tolower(target_user)
    | mv-apply TargetResource = TargetResources on (
        where TargetResource.type =~ "ServicePrincipal"
        | extend
            AppDisplayName = tostring(TargetResource.displayName),
            ServicePrincipalId = tostring(TargetResource.id),
            ModifiedProperties = TargetResource.modifiedProperties
    )
    | mv-apply Property = ModifiedProperties on (
        where Property.displayName =~ "ConsentAction.Permissions"
        | extend ConsentFull = trim(@'"', tostring(Property.newValue))
    )
    | parse ConsentFull with * "ConsentType: " ConsentType ", Scope: " GrantScope "]" *
    | extend SensitiveScope =
        GrantScope has "offline_access"
        or GrantScope has_any (
            "Mail.Read",
            "Mail.ReadWrite",
            "Mail.Send",
            "Files.Read.All",
            "Files.ReadWrite.All",
            "Contacts.Read",
            "Contacts.ReadWrite"
        )
    | project TimeGenerated, InitiatingUser, AppDisplayName, ServicePrincipalId, ConsentType, GrantScope, SensitiveScope, CorrelationId
    | order by TimeGenerated desc
    SPL
    index=<entra_audit_index> sourcetype=<entra_audit_sourcetype>
    earliest=-7d
    (OperationName="Consent to application" OR ActivityDisplayName="Consent to application")
    | eval
        user=lower(coalesce(user, userPrincipalName, InitiatedByUserPrincipalName)),
        app_name=coalesce(app_name, TargetResourceDisplayName, AppDisplayName),
        service_principal_id=coalesce(service_principal_id, TargetResourceId, ServicePrincipalId),
        permissions=lower(coalesce(permissions, ConsentActionPermissions, GrantScope)),
        consent_type=coalesce(consent_type, ConsentType),
        correlation_id=coalesce(correlation_id, CorrelationId)
    | where user="denis@example.com"
    | eval sensitive_scope=if(
        like(permissions,"%offline_access%")
        OR like(permissions,"%mail.read%")
        OR like(permissions,"%mail.readwrite%")
        OR like(permissions,"%mail.send%")
        OR like(permissions,"%files.read.all%")
        OR like(permissions,"%files.readwrite.all%")
        OR like(permissions,"%contacts.read%"),
        1,0
    )
    | table _time user app_name service_principal_id consent_type permissions sensitive_scope correlation_id
    | sort 0 - _time
    What to look for

    A consent grant whose scope includes offline_access or access to mail, files, contacts, or outbound mail.

    Technical details
    Tested signal

    The consent event contains durable or sensitive delegated scopes in the granted permission set.

    Assumptions
    • Q-01 identified the relevant user/app consent event.
    • The consent event retains the granted permission scope in its target-resource details.
    Data requirements and relevant fields
    identity

    Application-consent audit events with permission scope, target service principal, user, and correlation context.

    • TimeGenerated
    • OperationName
    • Result
    • InitiatedBy
    • TargetResources
    • CorrelationId
    KQL schema

    Uses documented Microsoft Entra/Azure Monitor fields; validate connector availability and local retention.

    SPL schema

    Replace index/sourcetype placeholders and map the documented concepts to the local Entra audit or sign-in source.

    Limitations
    • Permission risk is contextual; legitimate line-of-business apps can require sensitive scopes.
    • Scope naming can evolve and must be compared with the actual business purpose.

    KQL uses Microsoft Entra/Azure Monitor tables; SPL is a raw normalized adaptation scaffold and requires local field mapping.

    More reasoning

    Observation

    OAuth risk is determined by the permissions the application can exercise after consent.

    Working explanation

    The grant may include offline access or sensitive mail, file, contact, or send permissions.

    What was checked

    Parse the consent permission property and flag durable or high-value delegated scopes.

    Interpretation

    A legitimate consent screen does not make a broad permission request legitimate; business purpose and publisher trust still require validation.

    Supporting evidence
  3. Scope the application across users

    A multi-user footprint materially increases concern and defines the next containment population.

    Next pivot

    Review sign-in context for each affected user and application.

    View query
    Q-03

    Scope other users who consented to the same app

    What this checks

    Determine whether the candidate application received consent from additional identities in the environment.

    KQL
    let target_app = "Share-File Point Document";
    AuditLogs
    | where TimeGenerated >= ago(30d)
    | where OperationName =~ "Consent to application"
    | where Result =~ "success"
    | extend
        InitiatingUser = tolower(tostring(InitiatedBy.user.userPrincipalName)),
        InitiatingIP = tostring(InitiatedBy.user.ipAddress)
    | mv-apply TargetResource = TargetResources on (
        where TargetResource.type =~ "ServicePrincipal"
        | extend
            AppDisplayName = tostring(TargetResource.displayName),
            ServicePrincipalId = tostring(TargetResource.id)
    )
    | where AppDisplayName =~ target_app
    | summarize
        FirstSeen=min(TimeGenerated),
        LastSeen=max(TimeGenerated),
        ConsentEvents=count(),
        Users=make_set(InitiatingUser, 500),
        SourceIPs=make_set(InitiatingIP, 100),
        Correlations=make_set(CorrelationId, 100)
        by AppDisplayName, ServicePrincipalId
    | extend UniqueUsers = array_length(Users)
    | order by UniqueUsers desc
    SPL
    index=<entra_audit_index> sourcetype=<entra_audit_sourcetype>
    earliest=-30d
    (OperationName="Consent to application" OR ActivityDisplayName="Consent to application")
    | eval
        user=lower(coalesce(user, userPrincipalName, InitiatedByUserPrincipalName)),
        src=coalesce(src, ipAddress, InitiatingIpAddress),
        app_name=coalesce(app_name, TargetResourceDisplayName, AppDisplayName),
        service_principal_id=coalesce(service_principal_id, TargetResourceId, ServicePrincipalId),
        correlation_id=coalesce(correlation_id, CorrelationId)
    | where app_name="Share-File Point Document"
    | stats
        min(_time) as first_seen
        max(_time) as last_seen
        count as consent_events
        dc(user) as unique_users
        values(user) as users
        values(src) as source_ips
        values(correlation_id) as correlations
        by app_name service_principal_id
    | convert ctime(first_seen) ctime(last_seen)
    | sort - unique_users
    What to look for

    Multiple users authorizing the same unexpected app or a rapid increase in consent events.

    Technical details
    Tested signal

    Repeated consent to the same service principal across users.

    Assumptions
    • The candidate app name or service-principal identifier is known from Q-01.
    • AuditLogs covers the required historical window.
    Data requirements and relevant fields
    identity

    Consent events with user, source IP, target application/service principal, result, and correlation ID.

    • TimeGenerated
    • OperationName
    • Result
    • InitiatedBy
    • TargetResources
    • CorrelationId
    KQL schema

    Uses documented Microsoft Entra/Azure Monitor fields; validate connector availability and local retention.

    SPL schema

    Replace index/sourcetype placeholders and map the documented concepts to the local Entra audit or sign-in source.

    Limitations
    • Shared legitimate business applications can be consented by many users.
    • Use the service-principal identifier where possible because display names can be spoofed.

    KQL uses Microsoft Entra/Azure Monitor tables; SPL is a raw normalized adaptation scaffold and requires local field mapping.

    More reasoning

    Observation

    A malicious consent campaign can target more than one identity.

    Working explanation

    The same unexpected service principal may have received consent from additional users.

    What was checked

    Search historical Consent to application events for the candidate application and count unique users and source IPs.

    Interpretation

    Use the service-principal identifier when available because display names can be copied or spoofed.

    Supporting evidence
  4. Review app sign-in context

    Application-linked sign-in context helps distinguish a dormant grant from active use and supports scoping before revocation.

    Next pivot

    Revoke the grant and scope every affected identity and resource if the application cannot be justified.

    View query
    Q-04

    Review sign-in context for the authorized app

    What this checks

    Review sign-in events where the affected identity authenticated to the candidate application and preserve source, resource, Conditional Access, and authentication context.

    KQL
    let target_user = "denis@example.com";
    let target_app = "Share-File Point Document";
    SigninLogs
    | where TimeGenerated >= ago(7d)
    | where UserPrincipalName =~ target_user
    | where AppDisplayName =~ target_app
    | project
        TimeGenerated,
        UserPrincipalName,
        AppDisplayName,
        AppId,
        IPAddress,
        ResourceDisplayName,
        ResultType,
        ConditionalAccessStatus,
        AuthenticationRequirement,
        CorrelationId
    | order by TimeGenerated asc
    SPL
    index=<identity_index> sourcetype=<entra_user_signin_sourcetype>
    earliest=-7d
    | eval
        user=lower(coalesce(user, UserPrincipalName, user_principal_name)),
        app_name=coalesce(app_name, AppDisplayName),
        app_id=coalesce(app_id, AppId),
        src=coalesce(src, IPAddress, ip_address),
        resource=coalesce(resource, ResourceDisplayName),
        result=coalesce(result, ResultType),
        conditional_access=coalesce(conditional_access, ConditionalAccessStatus),
        auth_requirement=coalesce(auth_requirement, AuthenticationRequirement),
        correlation_id=coalesce(correlation_id, CorrelationId)
    | where user="denis@example.com" AND app_name="Share-File Point Document"
    | fields _time user app_name app_id src resource result conditional_access auth_requirement correlation_id
    | sort 0 _time
    What to look for

    Sign-ins to the newly authorized application that establish when and from where the app was used by the affected user.

    Technical details
    Tested signal

    User sign-ins tied to the newly consented application.

    Assumptions
    • SigninLogs is available in the connected Log Analytics/Sentinel workspace.
    • The application display name is replaced with the candidate value returned by the consent event.
    Data requirements and relevant fields
    authentication

    Entra user sign-ins with user, app, app ID, source IP, resource, result, Conditional Access, authentication requirement, and correlation ID.

    • TimeGenerated
    • UserPrincipalName
    • AppDisplayName
    • AppId
    • IPAddress
    • ResourceDisplayName
    • ResultType
    • ConditionalAccessStatus
    • AuthenticationRequirement
    • CorrelationId
    KQL schema

    Uses documented Microsoft Entra/Azure Monitor fields; validate connector availability and local retention.

    SPL schema

    Replace index/sourcetype placeholders and map the documented concepts to the local Entra audit or sign-in source.

    Limitations
    • A consent grant can be abused without generating a distinctive interactive sign-in for every API call.
    • App display names are not unique; preserve AppId and service-principal identifiers during real investigations.

    KQL uses Microsoft Entra/Azure Monitor tables; SPL is a raw normalized adaptation scaffold and requires local field mapping.

    More reasoning

    Observation

    The consent grant establishes authorization, but the analyst still needs to determine when and how the app was used.

    Working explanation

    Sign-in logs may preserve user/app activity and source context around the newly authorized application.

    What was checked

    Review SigninLogs for the affected user and candidate app and preserve AppId, source IP, resource, result, Conditional Access, and CorrelationId.

    Interpretation

    Absence of a distinctive interactive sign-in does not prove the grant was unused; API activity may require additional SaaS or application telemetry.

    Supporting evidence

Response

Actions to take

Contain affected systems, preserve evidence, and scope the same behavior elsewhere.
  • Revoke the illicit delegated consent grant for denis@example.com.
  • Disable or remove the malicious enterprise application or service principal when appropriate.
  • Identify every user who authorized the same application and revoke their grants.
  • Review the exact delegated permissions and the cloud resources those permissions expose.
  • Review sign-in and application activity after the consent event.
  • Reset credentials only when credential compromise is also suspected; do not treat password reset as sufficient remediation for the app grant.
  • Review user-consent policy, verified-publisher requirements, and admin-consent workflow.

Conclusion

What was concluded

Microsoft's consent-phishing reporting shows why an identity investigation cannot stop at passwords and MFA. A user can be shown a legitimate Microsoft-hosted authorization screen and still grant a malicious third-party application durable access to cloud data.

This reconstructed Case follows the defensive sequence: confirm the consent event, recover the granted scopes, scope the application across users, review application-linked sign-in context, and revoke the grant when the integration cannot be justified.

The application name used here, Share-File Point Document, is source-backed Microsoft reporting. No client ID, reply URL, malicious domain, or victim timestamp is invented.

Technical detail

Technical evidence

Stable evidence anchors preserve the fields behind the investigation story.
E-01

E-01Identity telemetry

Microsoft documented OAuth consent phishing in which a target was presented with a legitimate Microsoft-hosted permission prompt for an unverified application named Share-File Point Document.

Account
denis@example.com
App display name
Share-File Point Document
Publisher state
Unverified
Referenced by
E-02

E-02Identity telemetry

After authorization, the application could obtain access tokens with the requested scopes and refresh tokens for persistent access.

Authorization flow
OAuth consent
Token impact
Access tokens and refresh tokens
Permission scope
Requested scopes approved by the user
Referenced by
E-03

E-03Cloud control-plane event

Microsoft guidance states that illicit consent grants give an external application account-level access and are not remediated by password reset or MFA alone.

Remediation focus
Revoke the application consent grant
Password reset only
Insufficient
MFA only
Insufficient
Referenced by
E-04

E-04Identity telemetry

Entra audit logs record Consent to application and related permission-grant operations needed to reconstruct who authorized an app and what changed.

Audit service
Core Directory
Audit category
ApplicationManagement
Primary activity
Consent to application
Referenced by

Detection engineering

Would your SOC catch this behavior?

See the detection built for this investigation.
View detection

Behavior context

ATT&CK and sources

Behavior mapping

MITRE ATT&CK

These mappings describe the behavior examined here. They do not establish attribution.

Review boundary

Sources and limits

Last reviewed
External sources
5

This case documents the available evidence and analytical limits; control effectiveness is environment-specific.