Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: Retrieve App Service Publishing Credentials
---

# Retrieve App Service Publishing Credentials


<span class="smallcaps w3-badge w3-blue w3-round w3-text-white" title="This attack technique can be detonated multiple times">idempotent</span>

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.

<span style="font-variant: small-caps;">Warm-up</span>:

- Create an Azure App Service (Linux Web App)

<span style="font-variant: small-caps;">Detonation</span>:

- 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 <code>Microsoft.Web/sites/publishxml/action</code> operation in Azure Activity logs.

Sample event (redacted for clarity):

```json hl_lines="6"
{
"resourceId": "/SUBSCRIPTIONS/<your-subscription-id>/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/<your-subscription-id>/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.


5 changes: 5 additions & 0 deletions docs/attack-techniques/azure/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/attack-techniques/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
8 changes: 4 additions & 4 deletions docs/attack-techniques/mitre-attack-coverage-matrices.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ This provides coverage matrices of MITRE ATT&CK tactics and techniques currently
</div>
<h2>Azure</h2>
<div class="table-container"><table>
<thead><tr><th>Execution</th><th>Persistence</th><th>Privilege Escalation</th><th>Exfiltration</th><th>Impact</th></tr></thead>
<thead><tr><th>Execution</th><th>Persistence</th><th>Privilege Escalation</th><th>Credential Access</th><th>Exfiltration</th><th>Impact</th></tr></thead>
<tbody>
<tr><td><a href="../Azure/azure.execution.vm-custom-script-extension">Execute Command on Virtual Machine using Custom Script Extension</a></td><td><a href="../Azure/azure.persistence.backdoor-managed-identity-fic">Backdoor Azure Managed Identity with Federated Identity Credential (FIC)</a></td><td><a href="../Azure/azure.persistence.backdoor-managed-identity-fic">Backdoor Azure Managed Identity with Federated Identity Credential (FIC)</a></td><td><a href="../Azure/azure.exfiltration.disk-export">Export Disk Through SAS URL</a></td><td><a href="../Azure/azure.impact.blob-ransomware-client-encryption-scope">Azure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key</a></td></tr>
<tr><td><a href="../Azure/azure.execution.vm-run-command">Execute Commands on Virtual Machine using Run Command</a></td><td><a href="../Azure/azure.persistence.create-bastion-shareable-link">Create Azure VM Bastion shareable link</a></td><td><a href="../Azure/azure.privilege-escalation.root-user-access-administrator">Elevate to User Access Administrator at Root Scope</a></td><td><a href="../Azure/azure.exfiltration.storage-public-access">Exfiltrate Azure Storage via public access</a></td><td><a href="../Azure/azure.impact.blob-ransomware-individual-file-deletion">Azure ransomware via Storage Account Blob deletion</a></td></tr>
<tr><td></td><td></td><td></td><td><a href="../Azure/azure.exfiltration.storage-sas-export">Exfiltrate Azure Storage through SAS URL</a></td><td><a href="../Azure/azure.impact.resource-lock">Delete Azure resource lock</a></td></tr>
<tr><td><a href="../Azure/azure.execution.vm-custom-script-extension">Execute Command on Virtual Machine using Custom Script Extension</a></td><td><a href="../Azure/azure.persistence.backdoor-managed-identity-fic">Backdoor Azure Managed Identity with Federated Identity Credential (FIC)</a></td><td><a href="../Azure/azure.persistence.backdoor-managed-identity-fic">Backdoor Azure Managed Identity with Federated Identity Credential (FIC)</a></td><td><a href="../Azure/azure.credential-access.app-service-publishing-credentials">Retrieve App Service Publishing Credentials</a></td><td><a href="../Azure/azure.exfiltration.disk-export">Export Disk Through SAS URL</a></td><td><a href="../Azure/azure.impact.blob-ransomware-client-encryption-scope">Azure Blob Storage ransomware through Encryption Scope using client-managed Key Vault key</a></td></tr>
<tr><td><a href="../Azure/azure.execution.vm-run-command">Execute Commands on Virtual Machine using Run Command</a></td><td><a href="../Azure/azure.persistence.create-bastion-shareable-link">Create Azure VM Bastion shareable link</a></td><td><a href="../Azure/azure.privilege-escalation.root-user-access-administrator">Elevate to User Access Administrator at Root Scope</a></td><td></td><td><a href="../Azure/azure.exfiltration.storage-public-access">Exfiltrate Azure Storage via public access</a></td><td><a href="../Azure/azure.impact.blob-ransomware-individual-file-deletion">Azure ransomware via Storage Account Blob deletion</a></td></tr>
<tr><td></td><td></td><td></td><td></td><td><a href="../Azure/azure.exfiltration.storage-sas-export">Exfiltrate Azure Storage through SAS URL</a></td><td><a href="../Azure/azure.impact.resource-lock">Delete Azure resource lock</a></td></tr>
</tbody>
</table>
</div>
Expand Down
8 changes: 8 additions & 0 deletions docs/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions v2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <code>Microsoft.Web/sites/publishxml/action</code> operation in Azure Activity logs.

Sample event (redacted for clarity):

` + codeBlock + `json hl_lines="6"
{
"resourceId": "/SUBSCRIPTIONS/<your-subscription-id>/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/<your-subscription-id>/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)
}
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 1 addition & 0 deletions v2/internal/attacktechniques/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down