diff --git a/docs/attack-techniques/azure/azure.credential-access.app-service-publishing-credentials.md b/docs/attack-techniques/azure/azure.credential-access.app-service-publishing-credentials.md new file mode 100755 index 000000000..8783bfd92 --- /dev/null +++ b/docs/attack-techniques/azure/azure.credential-access.app-service-publishing-credentials.md @@ -0,0 +1,68 @@ +--- +title: Retrieve App Service Publishing Credentials +--- + +# Retrieve App Service Publishing Credentials + + + idempotent + +Platform: Azure + +## Mappings + +- MITRE ATT&CK + - Credential Access + + + +## Description + + +Retrieves the publishing profile of an Azure App Service (Web App), which contains FTP and Web Deploy credentials in cleartext. + +An attacker with read-or-higher access to an App Service can call the `publishxml` ARM action to download the publishing profile, without needing access to any Key Vault or secret store. The returned XML document contains `userName` and `userPWD` attributes that grant direct FTP and Web Deploy access to the application host. These credentials can then be used for lateral movement or to deploy malicious code to the running application. + +This technique was observed in the STORM-2949 intrusion, where the threat actor harvested App Service publishing credentials to move from a compromised identity into application hosts. + +Warm-up: + +- Create an Azure App Service (Linux Web App) + +Detonation: + +- Call the `listPublishingProfileXMLWithSecrets` action on the App Service to retrieve the publishing profile containing FTP and Web Deploy credentials + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate azure.credential-access.app-service-publishing-credentials +``` +## Detection + + +Identify the Microsoft.Web/sites/publishxml/action operation in Azure Activity logs. + +Sample event (redacted for clarity): + +```json hl_lines="6" +{ + "resourceId": "/SUBSCRIPTIONS//RESOURCEGROUPS/STRATUS-RED-TEAM-ASP-CRED-RG/PROVIDERS/MICROSOFT.WEB/SITES/SRT-ASP-CRED", + "evt": { + "category": "Administrative", + "outcome": "Success", + "name": "MICROSOFT.WEB/SITES/PUBLISHXML/ACTION" + }, + "level": "Information", + "properties": { + "message": "Microsoft.Web/sites/publishxml/action", + "eventCategory": "Administrative", + "entity": "/subscriptions//resourceGroups/stratus-red-team-asp-cred-rg/providers/Microsoft.Web/sites/srt-asp-cred" + } +} +``` + +Note that this operation is logged even when the App Service has only basic (publishing) authentication disabled, since the action is evaluated before authentication settings are honored. + + diff --git a/docs/attack-techniques/azure/index.md b/docs/attack-techniques/azure/index.md index fdae18101..f45fbe1a8 100755 --- a/docs/attack-techniques/azure/index.md +++ b/docs/attack-techniques/azure/index.md @@ -25,6 +25,11 @@ Note that some Stratus attack techniques may correspond to more than a single AT - [Elevate to User Access Administrator at Root Scope](./azure.privilege-escalation.root-user-access-administrator.md) +## Credential Access + + - [Retrieve App Service Publishing Credentials](./azure.credential-access.app-service-publishing-credentials.md) + + ## Exfiltration - [Export Disk Through SAS URL](./azure.exfiltration.disk-export.md) diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index d370d4c26..76e6b591e 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -52,6 +52,7 @@ This page contains the list of all Stratus Attack Techniques. | [Create an IAM Roles Anywhere trust anchor](./AWS/aws.persistence.rolesanywhere-create-trust-anchor.md) | [AWS](./AWS/index.md) | Persistence, Privilege Escalation | | [Generate temporary AWS credentials using GetFederationToken](./AWS/aws.persistence.sts-federation-token.md) | [AWS](./AWS/index.md) | Persistence | | [Change IAM user password](./AWS/aws.privilege-escalation.iam-update-user-login-profile.md) | [AWS](./AWS/index.md) | Privilege Escalation | +| [Retrieve App Service Publishing Credentials](./azure/azure.credential-access.app-service-publishing-credentials.md) | [Azure](./azure/index.md) | Credential Access | | [Execute Command on Virtual Machine using Custom Script Extension](./azure/azure.execution.vm-custom-script-extension.md) | [Azure](./azure/index.md) | Execution | | [Execute Commands on Virtual Machine using Run Command](./azure/azure.execution.vm-run-command.md) | [Azure](./azure/index.md) | Execution | | [Export Disk Through SAS URL](./azure/azure.exfiltration.disk-export.md) | [Azure](./azure/index.md) | Exfiltration | diff --git a/docs/attack-techniques/mitre-attack-coverage-matrices.md b/docs/attack-techniques/mitre-attack-coverage-matrices.md index c1a12097a..d40c1efed 100644 --- a/docs/attack-techniques/mitre-attack-coverage-matrices.md +++ b/docs/attack-techniques/mitre-attack-coverage-matrices.md @@ -43,11 +43,11 @@ This provides coverage matrices of MITRE ATT&CK tactics and techniques currently

Azure

diff --git a/docs/index.yaml b/docs/index.yaml index d4dcdc3b7..6965f640d 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -767,6 +767,14 @@ GCP: platform: GCP isIdempotent: false Azure: + Credential Access: + - id: azure.credential-access.app-service-publishing-credentials + name: Retrieve App Service Publishing Credentials + isSlow: false + mitreAttackTactics: + - Credential Access + platform: Azure + isIdempotent: true Execution: - id: azure.execution.vm-custom-script-extension name: Execute Command on Virtual Machine using Custom Script Extension diff --git a/v2/go.mod b/v2/go.mod index 64ba23704..24e759541 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -8,6 +8,7 @@ require ( cloud.google.com/go/storage v1.62.1 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v4 v4.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.5.0 diff --git a/v2/go.sum b/v2/go.sum index 7f70a049b..26e1356f4 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -35,6 +35,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v4 v4.1.0 h1:dZurN2OdkxAZlaNw6cjEvo7uOonGFErtqQtos0RDl5Q= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v4 v4.1.0/go.mod h1:/Qjzbz3yeXizRgrwP1lbwBIYYsAuMfDRWN0P5YbYgBM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0/go.mod h1:/pz8dyNQe+Ey3yBp/XuYz7oqX8YDNWVpPB0hH3XWfbc= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ= diff --git a/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.go b/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.go new file mode 100644 index 000000000..68d78273b --- /dev/null +++ b/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.go @@ -0,0 +1,105 @@ +package azure + +import ( + "context" + _ "embed" + "errors" + "io" + "log" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v4" + "github.com/datadog/stratus-red-team/v2/internal/providers" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" +) + +//go:embed main.tf +var tf []byte + +func init() { + const codeBlock = "```" + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "azure.credential-access.app-service-publishing-credentials", + FriendlyName: "Retrieve App Service Publishing Credentials", + Description: ` +Retrieves the publishing profile of an Azure App Service (Web App), which contains FTP and Web Deploy credentials in cleartext. + +An attacker with read-or-higher access to an App Service can call the ` + "`publishxml`" + ` ARM action to download the publishing profile, without needing access to any Key Vault or secret store. The returned XML document contains ` + "`userName`" + ` and ` + "`userPWD`" + ` attributes that grant direct FTP and Web Deploy access to the application host. These credentials can then be used for lateral movement or to deploy malicious code to the running application. + +This technique was observed in the STORM-2949 intrusion, where the threat actor harvested App Service publishing credentials to move from a compromised identity into application hosts. + +Warm-up: + +- Create an Azure App Service (Linux Web App) + +Detonation: + +- Call the ` + "`listPublishingProfileXMLWithSecrets`" + ` action on the App Service to retrieve the publishing profile containing FTP and Web Deploy credentials +`, + Detection: ` +Identify the Microsoft.Web/sites/publishxml/action operation in Azure Activity logs. + +Sample event (redacted for clarity): + +` + codeBlock + `json hl_lines="6" +{ + "resourceId": "/SUBSCRIPTIONS//RESOURCEGROUPS/STRATUS-RED-TEAM-ASP-CRED-RG/PROVIDERS/MICROSOFT.WEB/SITES/SRT-ASP-CRED", + "evt": { + "category": "Administrative", + "outcome": "Success", + "name": "MICROSOFT.WEB/SITES/PUBLISHXML/ACTION" + }, + "level": "Information", + "properties": { + "message": "Microsoft.Web/sites/publishxml/action", + "eventCategory": "Administrative", + "entity": "/subscriptions//resourceGroups/stratus-red-team-asp-cred-rg/providers/Microsoft.Web/sites/srt-asp-cred" + } +} +` + codeBlock + ` + +Note that this operation is logged even when the App Service has only basic (publishing) authentication disabled, since the action is evaluated before authentication settings are honored. +`, + Platform: stratus.Azure, + IsIdempotent: true, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.CredentialAccess}, + PrerequisitesTerraformCode: tf, + Detonate: detonate, + }) +} + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + appServiceName := params["app_service_name"] + resourceGroup := params["resource_group_name"] + + webAppsClient, err := getAzureWebAppsClient(providers.Azure()) + if err != nil { + return errors.New("unable to instantiate Azure web apps client: " + err.Error()) + } + + log.Println("Retrieving publishing credentials for App Service " + appServiceName) + response, err := webAppsClient.ListPublishingProfileXMLWithSecrets( + context.Background(), + resourceGroup, + appServiceName, + armappservice.CsmPublishingProfileOptions{}, + nil, + ) + if err != nil { + return errors.New("unable to retrieve App Service publishing credentials: " + err.Error()) + } + defer response.Body.Close() + + publishingProfile, err := io.ReadAll(response.Body) + if err != nil { + return errors.New("unable to read App Service publishing profile: " + err.Error()) + } + + _ = publishingProfile // contains the FTP and Web Deploy credentials in cleartext + log.Println("Successfully retrieved publishing credentials for App Service " + appServiceName) + return nil +} + +func getAzureWebAppsClient(azure *providers.AzureProvider) (*armappservice.WebAppsClient, error) { + return armappservice.NewWebAppsClient(azure.SubscriptionID, azure.GetCredentials(), azure.ClientOptions) +} diff --git a/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.tf b/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.tf new file mode 100644 index 000000000..d191062bf --- /dev/null +++ b/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.tf @@ -0,0 +1,52 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.8.0" + } + } +} + +provider "azurerm" { + features {} +} + +resource "random_string" "suffix" { + length = 8 + special = false + upper = false +} + +resource "azurerm_resource_group" "rg" { + name = "stratus-red-team-asp-cred-rg-${random_string.suffix.result}" + location = "West US" +} + +resource "azurerm_service_plan" "plan" { + name = "stratus-red-team-asp-cred-plan" + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + os_type = "Linux" + sku_name = "B1" +} + +resource "azurerm_linux_web_app" "app" { + name = "srt-asp-cred-${random_string.suffix.result}" + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_service_plan.plan.location + service_plan_id = azurerm_service_plan.plan.id + + site_config {} +} + +output "app_service_name" { + value = azurerm_linux_web_app.app.name +} + +output "resource_group_name" { + value = azurerm_resource_group.rg.name +} + +output "display" { + value = format("App Service %s ready in resource group %s", azurerm_linux_web_app.app.name, azurerm_resource_group.rg.name) +} diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index c8733f99e..8e0a79b0e 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -44,6 +44,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/persistence/rolesanywhere-create-trust-anchor" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/persistence/sts-federation-token" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/aws/privilege-escalation/change-iam-user-password" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-run-command" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/exfiltration/disk-export"