Skip to main content
cd ../config-traps
risk/register/azure-app-services-two-basic-auth-policies-not-one.html
Azure App Service Securityhigh severityAzure App Service

Azure App Service's Two Basic Auth Policies, Not One

Severity
high
Reviewed
23 Jul 2026
Remediation
~20 minutes
Overview

Disabling Basic Auth in the Azure Portal only touches the SCM publishing policy, not the separate FTP policy resource. Leaked publish profile credentials still authenticate over FTPS, and Always On keeps the resulting worker process running indefinitely instead of idling out, so normal recycling never interrupts an uploaded webshell.

Operational summary

At a glance

Symptom
A leaked publish profile authenticates over FTPS to an app where only the SCM policy was disabled, uploading a webshell that Always On keeps…
Likely cause
The scm basic-auth toggle is disabled while the ftp basicPublishingCredentialsPolicies resource remains enabled independently.
Impact
Attackers gain persistent code execution via FTP-deployed webshells that bypass SCM-based deployment auditing and survive worker recycling.
Verification signal
Re-run the detection test and a controlled negative-path test.
Safe correction
Disable both scm and ftp basicPublishingCredentialsPolicies resources explicitly, set ftpsState to Disabled, and rotate publish profiles.
Rollback or recovery
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.

The Trap

Azure App Service exposes two independent basic-auth publishing policies under Microsoft.Web/sites/basicPublishingCredentialsPolicies: one named scm, one named ftp. The Portal’s Configuration > General Settings toggle labelled “Basic Auth Publishing Credentials” writes to the scm policy only. There is no equivalent Portal control for ftp. Admins who flip that single toggle and consider deployment authentication closed have, in practice, left FTPS basic auth untouched.

The Default State

New App Service instances ship with ftpsState set to AllAllowed and both policy children defaulting to properties.allow: true. The publish profile generated at site creation contains a static username and password tied to the site itself, not to Entra ID, and it does not expire or rotate on any schedule. Combined with Always On: true, which most teams enable purely to avoid cold-start latency on the primary web app, the platform’s keep-alive ping continuously warms the worker process rather than letting it idle out after twenty minutes of inactivity.

The Blast Radius

Publish profiles leak routinely: committed into CI pipeline logs, left in old zip-deploy scripts, or pasted into ticketing systems during migrations. An attacker holding a leaked profile connects over FTPS, uploads an aspx or php shell into /site/wwwroot, and finds the SCM basic-auth toggle irrelevant because FTP authentication was never gated by it. Because Always On prevents the worker process from being recycled on idle, the shell’s process persists without re-upload, and FTP-based file drops don’t pass through the same deployment-slot audit trail as Kudu or source control deployments, so standard deployment logging misses the write entirely.

The Lead Mechanic Fix

Disable both policy resources explicitly and remove FTP as an authentication path where it isn’t operationally required:

az resource update --resource-group <rg> --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<app-name> --name ftp --set properties.allow=false

az resource update --resource-group <rg> --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<app-name> --name scm --set properties.allow=false

az webapp config set --resource-group <rg> --name <app-name> --ftps-state Disabled

Rotate the publish profile immediately afterwards with az webapp deployment user set --user-name <new-user> --password <new-pass>, and move deployment to OIDC-federated GitHub Actions or Entra ID-authenticated pipelines so no static publish credential exists to leak.

03

Apply the safer control

Before you change production

Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.

Fix commands and configuration

az resource update --resource-group <rg> --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<app-name> --name ftp --set properties.allow=false
az resource update --resource-group <rg> --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<app-name> --name scm --set properties.allow=false
az webapp config set --resource-group <rg> --name <app-name> --ftps-state Disabled
04

Verify, roll back or escalate

Verify

Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.

Rollback

Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.

Escalate

Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.

After remediation

Further reading stays below the corrective workflow and is selected by platform, category and shared technical keywords.

Discover more

Connected KBY resources