Skip to main content
emnode
Compliance

Harden Azure App Service apps

One capability across web apps and API apps: make sure every app forces HTTPS, refuses remote debugging, scopes CORS to the origins you actually own, and runs a supported language runtime, instead of shipping the permissive defaults to the internet.

14 min·10 sections·AZURE

Last reviewed

Hardening App Service apps: the basics

What does an under-hardened Azure App Service app actually look like?

An App Service app is an internet-facing front door, and its exposure shows up as a handful of distinct settings rather than one switch. HTTPS-only can be off, so the app still answers plain HTTP and any session cookie or token travels in the clear. CORS can be set to a wildcard '*', so any website in the world can call the app's API from a victim's browser. Remote debugging can be left enabled, so a debugger port stays open on a production app long after the one engineer who needed it has moved on. The language runtime, Java, PHP or Python, can be pinned to a version that has stopped receiving security patches. Each is its own Microsoft Defender for Cloud recommendation, but the underlying risk is the same: an app that accepts, trusts or runs more than you meant it to.

Defender for Cloud turns each of these into its own recommendation, which is why one App Service plan with a few apps on it can fail several checks at once. 'App Service apps should only be accessible over HTTPS', 'Remote debugging should be turned off' and the CORS recommendation are the ones that close the widest doors, and they pair with the client-certificate and runtime-version checks to form a single capability. They read as separate problems on the report, but they are one job: stop shipping the permissive defaults and pin the app to a hardened, supported configuration.

Most of this is drift, not intent. An app scaffolded with a wildcard CORS rule for local testing that never got tightened, a deployment slot where someone flipped remote debugging on to chase a bug, a runtime that was current when the app shipped and quietly aged out of support. The work is to find every app that forces HTTPS too loosely, trusts too many origins, leaves a debugger open or runs an unsupported runtime, decide which few settings are deliberate, and pin the rest to a hardened baseline.

In this lesson you will learn how Azure App Service expresses transport security, cross-origin trust, debugging exposure and runtime support, how to find every under-hardened app in a subscription, and how to pin them to a safe baseline without breaking the apps that have a legitimate reason for a given setting. The Controls this lesson covers section lists every Defender for Cloud recommendation in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The wildcard that opened the door

A CORS policy set to '' is one of the most common findings on any App Service estate, and it is almost never a deliberate decision. It is the value a developer pastes in to make a browser error go away during local testing, and then it ships. The catch is that a wildcard origin tells every website on the internet that it may call your API from a logged-in user's browser, which is exactly the trust boundary CORS exists to defend. Azure does not set a wildcard for you, so every '' in the estate was typed by someone, usually to unblock a demo, and then forgotten.

Finding under-hardened apps across a subscription

Sam is the security lead at a scale-up preparing for its first ISO 27001 audit. Defender for Cloud shows App Service findings spread across a dozen apps in two subscriptions that pre-date the team's current guardrails.

Rather than work the findings one by one, Sam starts by listing which apps still allow plain HTTP and which still leave remote debugging on, so the genuinely exposed front doors can be separated from drift before anything changes.

Start by listing the apps that do not force HTTPS and that still have a debugger port open. These are the widest-open doors.

$ az webapp list --query "[?!httpsOnly].{name:name, group:resourceGroup}" -o table
Name Group
---------------- ------------------
public-api-prod rg-app-prod
internal-admin rg-app-internal
# public-api-prod answers plain HTTP AND is internet-facing. Force HTTPS first.

An internet-facing app that still answers plain HTTP is the highest-value target in this group. Fix these first, then check each one for an open debugger port and a wildcard CORS rule.

How Defender for Cloud decides an App Service app is under-hardeneddeep dive

Most App Service recommendations resolve to one of a few properties on the site or its configuration. 'httpsOnly' on the site: when it is false, the app answers plain HTTP, so cookies and tokens can travel unencrypted. The CORS allowed-origins list in 'siteConfig.cors': when it contains a single '*', any origin may call the app from a browser, which defeats the cross-origin trust boundary. 'siteConfig.remoteDebuggingEnabled': when it is true, a debugger port is reachable on the app, widening its attack surface even though access is authenticated. The runtime stack, exposed as 'siteConfig.linuxFxVersion' on Linux, pins the app to a Java, PHP or Python version that may have aged out of security support.

Several of these checks have a history worth knowing. The classic 'API App' resource type has been retired, so the API-app-specific variants ('CORS should not allow every resource to access API Apps', 'Remote debugging should be turned off for API App', the API-app client-certificate check and the Java, PHP and Python API-app version checks) are deprecated and folded into the consolidated 'App Service apps...' recommendations and policies. The language-version policies were also removed from the Microsoft Cloud Security Benchmark default initiative: they still work and can be assigned, but they no longer auto-assign, so a clean MCSB score does not by itself prove your runtimes are supported. Author for the consolidated, current recommendations and treat the runtime check as something you assign deliberately.

Defender for Cloud evaluates these against the Microsoft Cloud Security Benchmark on a periodic cycle, so a fix does not flip the recommendation to Healthy instantly. The control-plane change itself is immediate, but the posture catches up on the next assessment. The strongest position combines the per-app setting with an Azure Policy backstop: a deny on non-HTTPS apps and an audit on runtime versions means the hardened configuration is enforced, not merely encouraged.

What is the impact of leaving App Service under-hardened?

The direct impact is application-layer compromise. An app that answers plain HTTP leaks every session cookie and bearer token to anyone who can observe the traffic, and a stolen session is a logged-in user until it expires. A wildcard CORS policy lets a malicious site call your authenticated API from a victim's browser, turning their session into the attacker's. An open remote-debugging port is extra authenticated attack surface on a production app that almost nobody is using deliberately. An end-of-life runtime ships known, unpatched vulnerabilities straight to the internet. None of these requires a sophisticated attacker; they are the settings automated scanners look for first.

The second-order impact is blast radius. Every plain-HTTP endpoint, wildcard origin, open debugger and stale runtime is attack surface that has to be defended continuously. Pinning every app to a hardened baseline shrinks that surface to a known, supported configuration you can monitor, put a web application firewall in front of, and reason about.

On the compliance side, every modern framework, ISO 27001, SOC 2, HIPAA, PCI DSS and the UK and EU data-protection regimes, expects evidence that data in transit is encrypted, that cross-origin trust is scoped, and that internet-facing software runs supported versions. A passing set of App Service recommendations across every subscription is one of the cheapest and most defensible artefacts you can hand an auditor.

How do you harden App Service apps safely?

Work the capability as one loop rather than chasing individual findings. The order matters: identify the apps with live external callers before you change CORS or force HTTPS, so you do not break a partner integration or a redirect.

1. Inventory every app by exposure

List the apps that are not HTTPS-only, that have a wildcard CORS origin, that leave remote debugging on, and whose runtime has aged out of support. Treat this inventory as the source of truth, not the Defender finding count, because one app can trigger several recommendations at once.

2. Separate deliberate settings from drift

Most findings are unintended. For each app, decide whether a setting is on purpose. A public API may genuinely need a small, named set of CORS origins, never a wildcard. An app may need a client certificate for mutual TLS; that works fine over HTTP/2 in Required or Optional mode, but avoid the Optional Interactive User mode and certificate exclusion paths on an HTTP/2 or TLS 1.3 app, because those settings rely on TLS renegotiation, which HTTP/2 and TLS 1.3 do not support. Everything else, the stray wildcard, the forgotten debugger, the plain-HTTP default, is drift and can be closed.

3. Pin the baseline, highest impact first

Force HTTPS-only, turn off remote debugging, replace the wildcard CORS rule with the explicit origins you own, and move each app to a supported runtime. Prioritise internet-facing apps over internal ones. Most changes are a single command per app and take effect immediately; runtime upgrades need a test pass first.

4. Ratchet it shut with Azure Policy

Assign a deny policy so no new app can be created without HTTPS-only, audit for wildcard CORS and for unsupported runtimes, so the hardened configuration is enforced rather than merely encouraged. Look the built-in policies up by display name rather than pasting a GUID, so the assignment always targets the current definition.

SUB="/subscriptions/00000000-0000-0000-0000-000000000000"
RG="rg-app-prod"

# Harden every app that is not already HTTPS-only.
for app in $(az webapp list -g "$RG" \
    --query "[?!httpsOnly].name" -o tsv); do
  # 1. Force HTTPS on the site itself.
  az webapp update -g "$RG" -n "$app" --https-only true
  # 2. Turn off the remote debugger.
  az webapp config set -g "$RG" -n "$app" --remote-debugging-enabled false
  # 3. Drop any wildcard CORS origin (replace with your own origins after).
  az webapp cors remove -g "$RG" -n "$app" --allowed-origins '*'
  echo "$app: HTTPS forced, remote debugging off, wildcard CORS removed"
done

# Ratchet it shut: deny any future app that is not HTTPS-only.
# Look the built-in policy up by display name, never hardcode the GUID.
pid=$(az policy definition list \
  --query "[?displayName=='App Service apps should only be accessible over HTTPS'].name" -o tsv)
az policy assignment create \
  --name deny-appservice-http \
  --policy "$pid" \
  --scope "$SUB"

Quick quiz

Question 1 of 5

Defender for Cloud shows App Service findings across HTTPS-only, CORS, remote debugging and runtime versions on several apps. What is the most efficient way to think about them?

You can now treat Azure App Service as one capability rather than a scatter of findings: inventory what answers plain HTTP, trusts every origin, leaves a debugger open or runs an unsupported runtime, separate the deliberate settings, pin the rest to a hardened baseline highest-impact first, and ratchet the estate shut with Azure Policy. The Controls this lesson covers section below links every recommendation in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many Microsoft Defender for Cloud recommendations. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.