Detection Engineering Portfolio Case Study

Detecting Cross-Application Credential Writes in Microsoft Entra ID

A replay-tested Microsoft Sentinel analytic for identifying an application identity adding or modifying credentials on a different application.

Microsoft Entra IDMicrosoft SentinelKQLMITRE ATT&CK T1098.001Lab-validated

Purpose. This case study documents the design, controlled replay, evidence review, limitations, and operationalisation of a high-signal identity persistence detection. Tenant-specific identifiers and secrets have been omitted so the document can be shared safely with other analysts.

Detection objectiveCross-app credential writeAlert when App A modifies credentials on App B.
Positive replayPassedWriter app modified the victim app.
Negative replayPassedSelf-management activity was suppressed.

Contents

1. Problem and threat model

Application credentials can provide non-interactive access without a user session or MFA challenge. A compromised service principal that writes a secret or certificate onto another trusted application can establish persistence under a different application identity. Resetting only the original application's credential may therefore fail to remove the planted access route.

Compromised writer application
→
Adds credential to another app
→
Persistence under trusted target identity
Signal-defining condition: ordinary self-rotation should be excluded. The behaviour of interest is an application identity modifying credentials belonging to a different application.

2. Detection logic

  • Source table: AuditLogs.
  • Successful operation whose name starts with Update application and contains Certificates and secrets management.
  • The initiator is an application/service principal, proven by a populated InitiatedBy.app.servicePrincipalId.
  • The target resource type is Application.
  • The initiating application and target application are not treated as the same application.
  • Credential metadata is extracted from the KeyDescription modified property.
Engineering decision: use punctuation-tolerant string matching. The observed operation name contained a Unicode en dash, and exact matching was vulnerable to dash and trailing-space differences.

3. Controlled lab design

The test used two disposable Entra application registrations in an isolated development tenant:

Writer application
TESTING-hacker-app

Authenticated to Microsoft Graph using app-only credentials and initiated the credential write.

Target application
TESTING-victim-app

Received a temporary password credential named DET-CrossApp-Test.

The writer service principal was made an owner of the target application and granted the least-broad lab permission used for the test: Application.ReadWrite.OwnedBy. The victim application was not granted powerful Microsoft Graph permissions because impact simulation was unnecessary for validating this analytic.

Safety boundary: the lab reproduced only the credential-write primitive. It did not use the planted credential to access tenant data or simulate subsequent compromise.

4. Test procedure

  1. Enable telemetry. Route the Entra AuditLogs diagnostic category to the Log Analytics workspace connected to the test Microsoft Sentinel instance.
  2. Prove ingestion. Generate a harmless application change and confirm that the event appears in both the Entra audit interface and the Sentinel AuditLogs table.
  3. Create two test applications. Use a writer application and a separate victim application so actor-to-target relationships are unambiguous.
  4. Establish constrained ownership. Add the writer service principal as an owner of the victim application through Microsoft Graph, because the portal owner picker exposed only users in this test.
  5. Use app-only authentication. Authenticate to Microsoft Graph as the writer application, not as an administrator, ensuring the resulting event is recorded under InitiatedBy.app.
  6. Run the positive test. Add the temporary password credential DET-CrossApp-Test to the victim application's application object.
  7. Run the negative test. Review known self-management events where the writer updates its own credentials and confirm they are excluded.
  8. Inspect raw evidence. Expand InitiatedBy, TargetResources, and modifiedProperties rather than relying only on projected display fields.

5. Observed evidence

TestObserved actorObserved targetExpectedResult
Cross-application credential additionTESTING-hacker-appTESTING-victim-appReturned by analyticPASS
Application self-managementTESTING-hacker-appTESTING-hacker-appSuppressedPASS

The positive replay exposed the following credential metadata in TargetResources[0].modifiedProperties:

{
  "displayName": "KeyDescription",
  "oldValue": "[]",
  "newValue": "[\"[KeyIdentifier=<redacted-guid>,KeyType=Password,KeyUsage=Verify,DisplayName=DET-CrossApp-Test]\"]"
}

The replay also showed that InitiatedBy.app.appId was null, while InitiatedBy.app.servicePrincipalId was populated. The target record exposed the target application's object ID. Because these are different object types, directly comparing actor service-principal object ID with target application object ID would be invalid.

6. Production candidate KQL

AuditLogs
| where OperationName startswith "Update application"
| where OperationName has "Certificates and secrets management"
| where Result =~ "success"
| extend
    ActorAppName = tostring(InitiatedBy.app.displayName),
    ActorSpObjectId = tostring(InitiatedBy.app.servicePrincipalId),
    TargetAppName = tostring(TargetResources[0].displayName),
    TargetAppObjectId = tostring(TargetResources[0].id),
    TargetType = tostring(TargetResources[0].type),
    ModifiedProperties = TargetResources[0].modifiedProperties
| where isnotempty(ActorSpObjectId)
| where TargetType =~ "Application"
// Suppress applications managing their own credentials.
| where tolower(trim(" ", ActorAppName))
    != tolower(trim(" ", TargetAppName))
| mv-apply Property = ModifiedProperties on
    (
        where tostring(Property.displayName) == "KeyDescription"
        | extend
            OldCredentialValue = tostring(Property.oldValue),
            NewCredentialValue = tostring(Property.newValue)
    )
| extend
    CredentialDisplayName = extract(@"DisplayName=([^\]\\""]+)", 1, NewCredentialValue),
    CredentialKeyIdentifier = extract(@"KeyIdentifier=([0-9a-fA-F-]{36})", 1, NewCredentialValue),
    CredentialType = extract(@"KeyType=([^,\]\\""]+)", 1, NewCredentialValue),
    CredentialUsage = extract(@"KeyUsage=([^,\]\\""]+)", 1, NewCredentialValue)
| project
    TimeGenerated,
    ActorAppName,
    ActorSpObjectId,
    TargetAppName,
    TargetAppObjectId,
    CredentialDisplayName,
    CredentialKeyIdentifier,
    CredentialType,
    CredentialUsage,
    OperationName,
    Result,
    CorrelationId,
    InitiatedBy,
    TargetResources
| order by TimeGenerated desc

7. Operational guidance

Suggested rule nameEntra ID - Application Credential Added by Different Service Principal
SeverityHigh
Suggested scheduleRun every 15 minutes with a 30-minute lookback; use alert grouping or event identifiers to manage overlap.
MITRE ATT&CKT1098.001 - Additional Cloud Credentials
Tuning keyApproved actor service-principal object ID + target application object ID relationship.

Analyst triage sequence

  1. Confirm the actor and target are different application identities.
  2. Validate whether the actor is authorised to manage the target and whether a corresponding change record exists.
  3. Review KeyDescription for credential type, identifier, usage, and display name.
  4. Assess permissions and directory roles held by both actor and target.
  5. Review nearby audit activity for ownership, consent, role, or API-permission changes.
  6. Correlate with service-principal sign-in telemetry to establish where the actor authenticated from and whether the target subsequently authenticated.
  7. If unauthorised, remove the planted credential, rotate affected credentials, revoke sessions/tokens where applicable, and investigate both identities.

8. Limitations and improvement path

Current limitation: self-management suppression uses normalised display-name equality because directly comparable immutable application identifiers were not present in the tested audit event. Display names are mutable and not guaranteed to be unique.
  • A malicious or coincidental matching display name could cause a false negative.
  • Legitimate cross-application automation can produce true-positive-but-authorised events.
  • Broadly excluding a trusted writer is dangerous because compromise of that writer would blind the detection.
  • The stronger design is to maintain an identity inventory that resolves actor service-principal object IDs to their underlying application object IDs, then perform an immutable ID comparison.
  • Approved automation should be tuned by the exact actor-target pair, with an owner and review date.

Engineering outcome

This exercise converted an external detection idea into a tenant-tested analytic. The work identified a Unicode operation-name pitfall, proved that actor and target IDs represented different Entra object types, validated both positive and negative cases, extracted useful credential evidence, and documented the residual weakness rather than concealing it.

Portfolio takeaway: the value is not only the KQL. The stronger demonstration is the end-to-end method: define the threat behaviour, establish telemetry, create controlled positive and negative tests, inspect raw records, avoid invalid identifier comparisons, and document tuning boundaries.

Sharing note: All tenant IDs, object IDs, client IDs, certificate details, and secret values are intentionally excluded or redacted. The test application names are disposable lab labels.