diff --git a/ql/lib/codeql/bicep/ast/Literals.qll b/ql/lib/codeql/bicep/ast/Literals.qll index 5929d24..729c7fc 100644 --- a/ql/lib/codeql/bicep/ast/Literals.qll +++ b/ql/lib/codeql/bicep/ast/Literals.qll @@ -74,6 +74,10 @@ class Boolean extends Literals instanceof BooleanImpl { result = false ) } + + boolean getValue() { + result = this.getBool() + } } /** diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll index 1db4534..48d835b 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll @@ -14,6 +14,7 @@ * - SlotResource: Class for Microsoft.Web/sites/slots resources (Deployment Slots) * - StaticSitesResource: Class for Microsoft.Web/staticSites resources (Static Web Apps) * - HostingEnvironmentsResource: Class for Microsoft.Web/hostingEnvironments resources (App Service Environments) + * - ExtendedLocation: Class for extended location configuration */ private import bicep @@ -58,14 +59,12 @@ module Web { /** * Gets the kind of App Service (e.g., "app", "functionapp", "api"). */ - StringLiteral getKind() { result = this.getProperty("kind") } + String getKind() { result = this.getProperty("kind") } /** * Returns the kind of App Service as a string. */ - string kind() { - exists(StringLiteral kind | kind = this.getKind() and result = kind.getValue()) - } + string kind() { exists(String kind | kind = this.getKind() and result = kind.getValue()) } /** * Checks if the site is a function app. @@ -75,115 +74,94 @@ module Web { /** * Checks if the site is a regular web app. */ - predicate isWebApp() { + predicate isWebApp() { this.kind().regexpMatch(".*app.*") and not this.isFunctionApp() } /** - * Gets the HTTPS-only flag for the App Service. - */ - BooleanLiteral getHttpsOnly() { result = this.getProperty("httpsOnly") } - - /** - * Returns true if HTTPS-only setting is enabled. + * Gets the extendedLocation configuration. */ - predicate isHttpsOnly() { - exists(BooleanLiteral httpsOnly | - httpsOnly = this.getHttpsOnly() and - httpsOnly.getBool() = true - ) - } + ExtendedLocation getExtendedLocation() { result = this.getProperty("extendedLocation") } /** - * Gets the extendedLocation configuration. + * Gets the virtualNetworkSubnetId property. */ - Object getExtendedLocation() { result = this.getProperty("extendedLocation") } + String getVirtualNetworkSubnetId() { result = this.getProperties().getVirtualNetworkSubnetId() } /** * Gets the clientAffinityEnabled flag. */ - BooleanLiteral getClientAffinityEnabled() { result = this.getProperty("clientAffinityEnabled") } + Boolean getClientAffinityEnabled() { result = this.getProperties().getClientAffinityEnabled() } /** - * Returns true if client affinity is enabled. + * Gets the clientCertEnabled flag. */ - predicate isClientAffinityEnabled() { - exists(BooleanLiteral clientAffinity | - clientAffinity = this.getClientAffinityEnabled() and - clientAffinity.getBool() = true - ) - } + Boolean getClientCertEnabled() { result = this.getProperties().getClientCertEnabled() } /** - * Gets the clientCertEnabled flag. + * Returns true if the site has client certificates enabled. */ - BooleanLiteral getClientCertEnabled() { result = this.getProperty("clientCertEnabled") } + predicate isClientCertEnabled() { this.getProperties().isClientCertEnabled() } /** - * Returns true if client certificates are enabled. + * Gets the hostNameSslStates array. */ - predicate isClientCertEnabled() { - exists(BooleanLiteral clientCert | - clientCert = this.getClientCertEnabled() and - clientCert.getBool() = true - ) - } + Array getHostNameSslStates() { result = this.getProperties().getHostNameSslStates() } /** - * Gets the clientCertMode setting. + * Gets the hyperV setting. */ - StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } + Boolean getHyperV() { result = this.getProperties().getHyperV() } /** - * Gets the hostNameSslStates array. + * Gets the keyVaultReferenceIdentity. */ - Array getHostNameSslStates() { result = this.getProperty("hostNameSslStates") } + String getKeyVaultReferenceIdentity() { + result = this.getProperties().getKeyVaultReferenceIdentity() + } /** - * Gets the hyperV setting. + * Gets the redundancyMode. */ - BooleanLiteral getHyperV() { result = this.getProperty("hyperV") } + String getRedundancyMode() { result = this.getProperties().getRedundancyMode() } /** - * Returns true if Hyper-V is enabled. + * Gets the storageAccountRequired flag. */ - predicate isHyperVEnabled() { - exists(BooleanLiteral hyperv | - hyperv = this.getHyperV() and - hyperv.getBool() = true - ) + Boolean getStorageAccountRequired() { + result = this.getProperties().getStorageAccountRequired() } /** - * Gets the keyVaultReferenceIdentity. + * Returns true if client affinity is enabled. */ - StringLiteral getKeyVaultReferenceIdentity() { result = this.getProperty("keyVaultReferenceIdentity") } + predicate isClientAffinityEnabled() { this.getProperties().isClientAffinityEnabled() } /** - * Gets the redundancyMode. + * Returns true if Hyper-V is enabled. */ - StringLiteral getRedundancyMode() { result = this.getProperty("redundancyMode") } + predicate isHyperVEnabled() { this.getProperties().isHyperVEnabled() } /** - * Gets the storageAccountRequired flag. + * Returns true if a storage account is required. */ - BooleanLiteral getStorageAccountRequired() { result = this.getProperty("storageAccountRequired") } + predicate isStorageAccountRequired() { this.getProperties().isStorageAccountRequired() } /** - * Returns true if a storage account is required. + * Gets the httpsOnly flag for the site. */ - predicate isStorageAccountRequired() { - exists(BooleanLiteral storageReq | - storageReq = this.getStorageAccountRequired() and - storageReq.getBool() = true - ) - } + Boolean getHttpsOnly() { result = this.getProperties().getHttpsOnly() } /** - * Gets the virtualNetworkSubnetId. + * Returns true if the site has HTTPS-only mode enabled. */ - StringLiteral getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") } + predicate isHttpsOnly() { + exists(Boolean httpsOnly | + httpsOnly = this.getHttpsOnly() and + httpsOnly.getBool() = true + ) + } override string toString() { result = "AppService[" + this.getIdentifier().getName() + "]" } } @@ -211,36 +189,33 @@ module Web { /** * Gets whether the App Service Plan is reserved (for Linux). */ - BooleanLiteral getReserved() { result = this.getProperty("reserved") } + Boolean getReserved() { result = this.getProperty("reserved") } /** * Returns true if the App Service Plan is reserved (for Linux). */ predicate isReserved() { - exists(BooleanLiteral reserved | + exists(Boolean reserved | reserved = this.getReserved() and reserved.getBool() = true ) } /** - * Gets the hosting environment profile. + * Gets whether zone redundant deployment is enabled. */ - Object getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } + Boolean getZoneRedundant() { result = this.getProperties().getZoneRedundant() } /** - * Gets whether zone redundancy is enabled. + * Returns true if zone redundant deployment is enabled. */ - BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + predicate isZoneRedundant() { this.getProperties().isZoneRedundant() } /** - * Returns true if zone redundancy is enabled. + * Gets the hosting environment profile. */ - predicate isZoneRedundant() { - exists(BooleanLiteral zoneRedundant | - zoneRedundant = this.getZoneRedundant() and - zoneRedundant.getBool() = true - ) + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = this.getProperties().getHostingEnvironmentProfile() } override string toString() { result = "AppServicePlan[" + this.getIdentifier().getName() + "]" } @@ -259,7 +234,7 @@ module Web { /** * Gets the properties object for the deployment slot. */ - SitesProperties::Properties getProperties() { result = this.getProperty("properties") } + SlotProperties::Properties getProperties() { result = this.getProperty("properties") } /** * Gets the identity configuration for the deployment slot. @@ -269,34 +244,19 @@ module Web { /** * Gets the kind of the deployment slot. */ - StringLiteral getKind() { result = this.getProperty("kind") } + String getKind() { result = this.getProperty("kind") } /** * Returns the kind of the deployment slot as a string. */ string kind() { result = this.getKind().getValue() } - /** - * Gets the HTTPS-only flag for the deployment slot. - */ - BooleanLiteral getHttpsOnly() { result = this.getProperty("httpsOnly") } - - /** - * Returns true if HTTPS-only setting is enabled for the deployment slot. - */ - predicate isHttpsOnly() { - exists(BooleanLiteral httpsOnly | - httpsOnly = this.getHttpsOnly() and - httpsOnly.getBool() = true - ) - } - /** * Gets the parent site name. */ string getParentSiteName() { // Parse from the resource name which is in the format "siteName/slotName" - exists(string fullName | + exists(string fullName | fullName = this.getName() and result = fullName.regexpCapture("([^/]+)/.*", 1) ) @@ -307,12 +267,22 @@ module Web { */ string getSlotName() { // Parse from the resource name which is in the format "siteName/slotName" - exists(string fullName | + exists(string fullName | fullName = this.getName() and result = fullName.regexpCapture("[^/]+/(.*)", 1) ) } + /** + * Gets the HTTPS-only flag for the deployment slot. + */ + Boolean getHttpsOnly() { result = this.getProperties().getHttpsOnly() } + + /** + * Returns true if HTTPS-only setting is enabled for the deployment slot. + */ + predicate isHttpsOnly() { this.getProperties().isHttpsOnly() } + override string toString() { result = "DeploymentSlot[" + this.getIdentifier().getName() + "]" } } @@ -336,11 +306,6 @@ module Web { */ SitesProperties::SiteIdentity getIdentity() { result = this.getProperty("identity") } - /** - * Gets the SKU object for the Static Web App. - */ - override Sku getSku() { result = this.getProperty("sku") } - override string toString() { result = "StaticWebApp[" + this.getIdentifier().getName() + "]" } } @@ -352,24 +317,174 @@ module Web { /** * Constructs a HostingEnvironmentsResource for Microsoft.Web/hostingEnvironments resources. */ - HostingEnvironmentsResource() { this.getResourceType().regexpMatch("^Microsoft.Web/hostingEnvironments@.*") } + HostingEnvironmentsResource() { + this.getResourceType().regexpMatch("^Microsoft.Web/hostingEnvironments@.*") + } /** * Gets the properties object for the App Service Environment. */ - HostingEnvironmentsProperties::Properties getProperties() { result = this.getProperty("properties") } + HostingEnvironmentsProperties::Properties getProperties() { + result = this.getProperty("properties") + } /** * Gets the kind of the App Service Environment. */ - StringLiteral getKind() { result = this.getProperty("kind") } + String getKind() { result = this.getProperty("kind") } /** * Returns the kind of the App Service Environment as a string. */ string kind() { result = this.getKind().getValue() } - override string toString() { result = "AppServiceEnvironment[" + this.getIdentifier().getName() + "]" } + override string toString() { + result = "AppServiceEnvironment[" + this.getIdentifier().getName() + "]" + } + } + + /** + * Represents an extended location configuration. + */ + class ExtendedLocation extends Object { + private WebResource parent; + + /** + * Constructs an ExtendedLocation object. + */ + ExtendedLocation() { this = parent.getProperty("extendedLocation") } + + /** + * Gets the name of the extended location. + */ + String getName() { result = this.getProperty("name") } + + /** + * Gets the type of the extended location. + */ + String getType() { result = this.getProperty("type") } + + string toString() { result = "ExtendedLocation" } + } + + /** + * Represents a hosting environment profile configuration. + */ + class HostingEnvironmentProfile extends Object { + /** + * Constructs a HostingEnvironmentProfile object. + */ + HostingEnvironmentProfile() { + // This object can be referenced from multiple parent types + exists(WebResource resource | this = resource.getProperty("hostingEnvironmentProfile")) or + exists(SitesProperties::Properties props | + this = props.getProperty("hostingEnvironmentProfile") + ) or + exists(SlotProperties::Properties props | + this = props.getProperty("hostingEnvironmentProfile") + ) or + exists(ServerFarmsProperties::Properties props | + this = props.getProperty("hostingEnvironmentProfile") + ) + } + + /** + * Gets the ID of the hosting environment. + */ + String getId() { result = this.getProperty("id") } + + /** + * Gets the name of the hosting environment. + */ + String getName() { result = this.getProperty("name") } + + string toString() { result = "HostingEnvironmentProfile" } + } + + /** + * Represents an application stack configuration. + */ + class ApplicationStack extends Object { + private SitesProperties::SiteConfig parent; + + /** + * Constructs an ApplicationStack object. + */ + ApplicationStack() { this = parent.getProperty("applicationStack") } + + string toString() { result = "ApplicationStack" } + } + + /** + * Represents app settings configuration. + */ + class AppSettings extends Object { + private SitesProperties::SiteConfig parent; + + /** + * Constructs an AppSettings object. + */ + AppSettings() { this = parent.getProperty("appSettings") } + + string toString() { result = "AppSettings" } + } + + /** + * Represents user-assigned identities configuration. + */ + class UserAssignedIdentities extends Object { + private SitesProperties::SiteIdentity parent; + + /** + * Constructs a UserAssignedIdentities object. + */ + UserAssignedIdentities() { this = parent.getProperty("userAssignedIdentities") } + + string toString() { result = "UserAssignedIdentities" } + } + + /** + * Represents repository branch configuration. + */ + class RepositoryBranch extends Object { + private StaticSitesProperties::Properties parent; + + /** + * Constructs a RepositoryBranch object. + */ + RepositoryBranch() { this = parent.getProperty("repositoryBranch") } + + /** + * Gets the name of the branch. + */ + String getName() { result = this.getProperty("name") } + + string toString() { result = "RepositoryBranch" } + } + + /** + * Represents an HTTPS certificate configuration. + */ + class HttpsCertificate extends Object { + private SitesProperties::HttpsCertificates parent; + private int index; + + /** + * Constructs an HttpsCertificate object. + */ + HttpsCertificate() { this = parent.getElement(index) } + + /** + * Gets the name of the certificate. + */ + String getName() { result = this.getProperty("name") } + + /** + * Gets the thumbprint of the certificate. + */ + String getThumbprint() { result = this.getProperty("thumbprint") } + + string toString() { result = "HttpsCertificate" } } /** @@ -380,12 +495,12 @@ module Web { * Represents the properties object for a Microsoft.Web/sites resource. */ class Properties extends ResourceProperties { - private SitesResource site; + private SitesResource parent; /** * Constructs a Properties object for the given site. */ - Properties() { this = site.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the site configuration. @@ -395,33 +510,43 @@ module Web { /** * Gets the serverFarmId (App Service Plan ID). */ - StringLiteral getServerFarmId() { result = this.getProperty("serverFarmId") } + String getServerFarmId() { result = this.getProperty("serverFarmId") } /** * Gets the hostingEnvironmentProfile. */ - Object getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = this.getProperty("hostingEnvironmentProfile") + } /** * Gets the public network access setting. */ - StringLiteral getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } + String getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } + + /** + * Gets the HTTPS-only flag for the App Service. + */ + Boolean getHttpsOnly() { result = this.getProperty("httpsOnly") } /** * Gets the virtualNetworkSubnetId. */ - StringLiteral getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") } + String getVirtualNetworkSubnetId() { + result = this.getProperty("virtualNetworkSubnetId") or + result = parent.getProperty("virtualNetworkSubnetId") + } /** * Gets the enabled value. */ - BooleanLiteral getEnabled() { result = this.getProperty("enabled") } + Boolean getEnabled() { result = this.getProperty("enabled") } /** * Returns true if the site is enabled. */ predicate isEnabled() { - exists(BooleanLiteral enabled | + exists(Boolean enabled | enabled = this.getEnabled() and enabled.getBool() = true ) @@ -430,13 +555,13 @@ module Web { /** * Gets the client certificate mode. */ - StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } - + String getClientCertMode() { result = this.getProperty("clientCertMode") } + /** * Returns true if client certificate is required. */ predicate isClientCertRequired() { - exists(StringLiteral mode | + exists(String mode | mode = this.getClientCertMode() and mode.getValue() = "Required" ) @@ -445,7 +570,7 @@ module Web { /** * Gets the client certificate exclusion paths. */ - StringLiteral getClientCertExclusionPaths() { result = this.getProperty("clientCertExclusionPaths") } + String getClientCertExclusionPaths() { result = this.getProperty("clientCertExclusionPaths") } /** * Gets the container size. @@ -455,7 +580,9 @@ module Web { /** * Gets the custom domain verification ID. */ - StringLiteral getCustomDomainVerificationId() { result = this.getProperty("customDomainVerificationId") } + String getCustomDomainVerificationId() { + result = this.getProperty("customDomainVerificationId") + } /** * Gets the daily memory time quota. @@ -465,18 +592,114 @@ module Web { /** * Gets the default hostname. */ - StringLiteral getDefaultHostname() { result = this.getProperty("defaultHostname") } + String getDefaultHostname() { result = this.getProperty("defaultHostname") } /** * Gets the https certificate settings. */ HttpsCertificates getHttpsCertificates() { result = this.getProperty("httpsCertificates") } + /** + * Gets the clientAffinityEnabled flag. + */ + Boolean getClientAffinityEnabled() { + result = this.getProperty("clientAffinityEnabled") or + result = parent.getProperty("clientAffinityEnabled") + } + + /** + * Returns true if client affinity is enabled. + */ + predicate isClientAffinityEnabled() { + exists(Boolean clientAffinity | + clientAffinity = this.getClientAffinityEnabled() and + clientAffinity.getBool() = true + ) + } + + /** + * Gets the clientCertEnabled flag. + */ + Boolean getClientCertEnabled() { + result = this.getProperty("clientCertEnabled") or + result = parent.getProperty("clientCertEnabled") + } + + /** + * Returns true if client certificates are enabled. + */ + predicate isClientCertEnabled() { + exists(Boolean clientCert | + clientCert = this.getClientCertEnabled() and + clientCert.getBool() = true + ) + } + + /** + * Gets the hostNameSslStates array. + */ + Array getHostNameSslStates() { + result = this.getProperty("hostNameSslStates") or + result = parent.getProperty("hostNameSslStates") + } + + /** + * Gets the hyperV setting. + */ + Boolean getHyperV() { + result = this.getProperty("hyperV") or + result = parent.getProperty("hyperV") + } + + /** + * Returns true if Hyper-V is enabled. + */ + predicate isHyperVEnabled() { + exists(Boolean hyperv | + hyperv = this.getHyperV() and + hyperv.getBool() = true + ) + } + + /** + * Gets the keyVaultReferenceIdentity. + */ + String getKeyVaultReferenceIdentity() { + result = this.getProperty("keyVaultReferenceIdentity") or + result = parent.getProperty("keyVaultReferenceIdentity") + } + + /** + * Gets the redundancyMode. + */ + String getRedundancyMode() { + result = this.getProperty("redundancyMode") or + result = parent.getProperty("redundancyMode") + } + + /** + * Gets the storageAccountRequired flag. + */ + Boolean getStorageAccountRequired() { + result = this.getProperty("storageAccountRequired") or + result = parent.getProperty("storageAccountRequired") + } + + /** + * Returns true if a storage account is required. + */ + predicate isStorageAccountRequired() { + exists(Boolean storageReq | + storageReq = this.getStorageAccountRequired() and + storageReq.getBool() = true + ) + } + /** * Returns true if public network access is enabled. */ predicate isPublicNetworkAccessEnabled() { - not exists(StringLiteral publicNetworkAccess | + not exists(String publicNetworkAccess | publicNetworkAccess = this.getPublicNetworkAccess() and publicNetworkAccess.getValue() = "Disabled" ) @@ -489,33 +712,33 @@ module Web { * Represents the site configuration for a Microsoft.Web/sites resource. */ class SiteConfig extends Object { - private Properties properties; + private Properties parent; /** * Constructs a SiteConfig object. */ - SiteConfig() { this = properties.getProperty("siteConfig") } + SiteConfig() { this = parent.getProperty("siteConfig") } /** * Gets the minimum TLS version. */ - StringLiteral getMinTlsVersion() { result = this.getProperty("minTlsVersion") } + String getMinTlsVersion() { result = this.getProperty("minTlsVersion") } /** * Gets the ftps state setting. */ - StringLiteral getFtpsState() { result = this.getProperty("ftpsState") } + String getFtpsState() { result = this.getProperty("ftpsState") } /** * Gets whether remote debugging is enabled. */ - BooleanLiteral getRemoteDebuggingEnabled() { result = this.getProperty("remoteDebuggingEnabled") } + Boolean getRemoteDebuggingEnabled() { result = this.getProperty("remoteDebuggingEnabled") } /** * Returns true if remote debugging is enabled. */ predicate isRemoteDebuggingEnabled() { - exists(BooleanLiteral debugEnabled | + exists(Boolean debugEnabled | debugEnabled = this.getRemoteDebuggingEnabled() and debugEnabled.getBool() = true ) @@ -524,18 +747,18 @@ module Web { /** * Gets the remote debugging version. */ - StringLiteral getRemoteDebuggingVersion() { result = this.getProperty("remoteDebuggingVersion") } + String getRemoteDebuggingVersion() { result = this.getProperty("remoteDebuggingVersion") } /** * Gets whether HTTP 2.0 is enabled. */ - BooleanLiteral getHttp20Enabled() { result = this.getProperty("http20Enabled") } + Boolean getHttp20Enabled() { result = this.getProperty("http20Enabled") } /** * Returns true if HTTP 2.0 is enabled. */ predicate isHttp20Enabled() { - exists(BooleanLiteral http20 | + exists(Boolean http20 | http20 = this.getHttp20Enabled() and http20.getBool() = true ) @@ -544,13 +767,18 @@ module Web { /** * Gets whether Always On is enabled. */ - BooleanLiteral getAlwaysOn() { result = this.getProperty("alwaysOn") } + Boolean getAlwaysOn() { result = this.getProperty("alwaysOn") } + + /** + * Returns the Always On setting as a boolean. + */ + boolean alwaysOn() { result = this.getAlwaysOn().getBool() } /** * Returns true if Always On is enabled. */ predicate isAlwaysOn() { - exists(BooleanLiteral alwaysOn | + exists(Boolean alwaysOn | alwaysOn = this.getAlwaysOn() and alwaysOn.getBool() = true ) @@ -559,13 +787,13 @@ module Web { /** * Gets whether web sockets are enabled. */ - BooleanLiteral getWebSocketsEnabled() { result = this.getProperty("webSocketsEnabled") } + Boolean getWebSocketsEnabled() { result = this.getProperty("webSocketsEnabled") } /** * Returns true if web sockets are enabled. */ predicate areWebSocketsEnabled() { - exists(BooleanLiteral webSockets | + exists(Boolean webSockets | webSockets = this.getWebSocketsEnabled() and webSockets.getBool() = true ) @@ -574,7 +802,7 @@ module Web { /** * Gets the application stack. */ - Object getApplicationStack() { result = this.getProperty("applicationStack") } + ApplicationStack getApplicationStack() { result = this.getProperty("applicationStack") } /** * Gets the connection strings. @@ -584,7 +812,7 @@ module Web { /** * Gets the app settings. */ - Object getAppSettings() { result = this.getProperty("appSettings") } + AppSettings getAppSettings() { result = this.getProperty("appSettings") } /** * Gets the CORS settings. @@ -594,17 +822,17 @@ module Web { /** * Gets the Linux FX version. */ - StringLiteral getLinuxFxVersion() { result = this.getProperty("linuxFxVersion") } + String getLinuxFxVersion() { result = this.getProperty("linuxFxVersion") } /** * Gets the Windows FX version. */ - StringLiteral getWindowsFxVersion() { result = this.getProperty("windowsFxVersion") } + String getWindowsFxVersion() { result = this.getProperty("windowsFxVersion") } /** * Gets the health check path. */ - StringLiteral getHealthCheckPath() { result = this.getProperty("healthCheckPath") } + String getHealthCheckPath() { result = this.getProperty("healthCheckPath") } string toString() { result = "SiteConfig" } } @@ -613,12 +841,12 @@ module Web { * Represents the CORS settings for a site configuration. */ class CorsSettings extends Object { - private SiteConfig siteConfig; + private SiteConfig parent; /** * Constructs a CorsSettings object. */ - CorsSettings() { this = siteConfig.getProperty("cors") } + CorsSettings() { this = parent.getProperty("cors") } /** * Gets the allowed origins. @@ -628,13 +856,13 @@ module Web { /** * Gets whether credentials are supported. */ - BooleanLiteral getSupportCredentials() { result = this.getProperty("supportCredentials") } + Boolean getSupportCredentials() { result = this.getProperty("supportCredentials") } /** * Returns true if credentials are supported. */ predicate areCredentialsSupported() { - exists(BooleanLiteral credentials | + exists(Boolean credentials | credentials = this.getSupportCredentials() and credentials.getBool() = true ) @@ -647,19 +875,17 @@ module Web { * Represents the HTTPS certificates configuration for a site. */ class HttpsCertificates extends Array { - private Properties properties; + private Properties parent; /** * Constructs an HttpsCertificates object. */ - HttpsCertificates() { this = properties.getProperty("httpsCertificates") } + HttpsCertificates() { this = parent.getProperty("httpsCertificates") } /** * Gets a certificate by index. */ - Object getCertificate(int index) { - result = this.getElement(index) - } + HttpsCertificate getCertificate(int index) { result = this.getElement(index) } string toString() { result = "HttpsCertificates" } } @@ -668,17 +894,17 @@ module Web { * Represents the identity configuration for a Microsoft.Web/sites resource. */ class SiteIdentity extends Object { - private SitesResource site; + private SitesResource parent; /** * Constructs a SiteIdentity object. */ - SiteIdentity() { this = site.getProperty("identity") } + SiteIdentity() { this = parent.getProperty("identity") } /** * Gets the type of managed identity. */ - StringLiteral getType() { result = this.getProperty("type") } + String getType() { result = this.getProperty("type") } /** * Returns the managed identity type as a string. @@ -704,7 +930,9 @@ module Web { /** * Gets the user-assigned identities. */ - Object getUserAssignedIdentities() { result = this.getProperty("userAssignedIdentities") } + UserAssignedIdentities getUserAssignedIdentities() { + result = this.getProperty("userAssignedIdentities") + } string toString() { result = "SiteIdentity" } } @@ -718,28 +946,28 @@ module Web { * Represents the properties object for a Microsoft.Web/staticSites resource. */ class Properties extends ResourceProperties { - private StaticSitesResource staticSite; + private StaticSitesResource parent; /** * Constructs a Properties object for the given static site. */ - Properties() { this = staticSite.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the staging environment policy. */ - StringLiteral getStagingEnvironmentPolicy() { result = this.getProperty("stagingEnvironmentPolicy") } + String getStagingEnvironmentPolicy() { result = this.getProperty("stagingEnvironmentPolicy") } /** * Gets whether private endpoint connections are allowed. */ - BooleanLiteral getAllowConfigFileUpdates() { result = this.getProperty("allowConfigFileUpdates") } + Boolean getAllowConfigFileUpdates() { result = this.getProperty("allowConfigFileUpdates") } /** * Returns true if config file updates are allowed. */ predicate areConfigFileUpdatesAllowed() { - exists(BooleanLiteral allowUpdates | + exists(Boolean allowUpdates | allowUpdates = this.getAllowConfigFileUpdates() and allowUpdates.getBool() = true ) @@ -748,28 +976,28 @@ module Web { /** * Gets the branch configuration for the repository. */ - Object getRepositoryBranch() { result = this.getProperty("repositoryBranch") } + RepositoryBranch getRepositoryBranch() { result = this.getProperty("repositoryBranch") } /** * Gets the repository token for the Static Web App. */ - StringLiteral getRepositoryToken() { result = this.getProperty("repositoryToken") } + String getRepositoryToken() { result = this.getProperty("repositoryToken") } /** * Gets the repository URL for the Static Web App. */ - StringLiteral getRepositoryUrl() { result = this.getProperty("repositoryUrl") } + String getRepositoryUrl() { result = this.getProperty("repositoryUrl") } /** * Gets whether private endpoint connections are allowed. */ - BooleanLiteral getAllowPrivateEndpoints() { result = this.getProperty("allowPrivateEndpoints") } + Boolean getAllowPrivateEndpoints() { result = this.getProperty("allowPrivateEndpoints") } /** * Returns true if private endpoints are allowed. */ predicate arePrivateEndpointsAllowed() { - exists(BooleanLiteral allowEndpoints | + exists(Boolean allowEndpoints | allowEndpoints = this.getAllowPrivateEndpoints() and allowEndpoints.getBool() = true ) @@ -787,12 +1015,12 @@ module Web { * Represents the properties object for a Microsoft.Web/hostingEnvironments resource. */ class Properties extends ResourceProperties { - private HostingEnvironmentsResource hostingEnv; + private HostingEnvironmentsResource parent; /** * Constructs a Properties object for the given hosting environment. */ - Properties() { this = hostingEnv.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the dedicated host count. @@ -802,13 +1030,13 @@ module Web { /** * Gets whether zone redundancy is enabled. */ - BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + Boolean getZoneRedundant() { result = this.getProperty("zoneRedundant") } /** * Returns true if zone redundancy is enabled. */ predicate isZoneRedundant() { - exists(BooleanLiteral zoneRedundant | + exists(Boolean zoneRedundant | zoneRedundant = this.getZoneRedundant() and zoneRedundant.getBool() = true ) @@ -817,7 +1045,9 @@ module Web { /** * Gets the internal load balancing mode. */ - StringLiteral getInternalLoadBalancingMode() { result = this.getProperty("internalLoadBalancingMode") } + String getInternalLoadBalancingMode() { + result = this.getProperty("internalLoadBalancingMode") + } /** * Gets the cluster settings. @@ -827,7 +1057,9 @@ module Web { /** * Gets the virtual network configuration. */ - VnetConfiguration getVirtualNetworkProfile() { result = this.getProperty("virtualNetworkProfile") } + VnetConfiguration getVirtualNetworkProfile() { + result = this.getProperty("virtualNetworkProfile") + } override string toString() { result = "HostingEnvironmentProperties" } } @@ -836,17 +1068,17 @@ module Web { * Represents the virtual network configuration for an App Service Environment. */ class VnetConfiguration extends Object { - private Properties props; + private Properties parent; /** * Constructs a VnetConfiguration object. */ - VnetConfiguration() { this = props.getProperty("virtualNetworkProfile") } + VnetConfiguration() { this = parent.getProperty("virtualNetworkProfile") } /** * Gets the subnet ID. */ - StringLiteral getSubnetId() { result = this.getProperty("id") } + String getSubnetId() { result = this.getProperty("id") } /** * Gets the subnet resource ID. @@ -865,17 +1097,17 @@ module Web { * Represents the properties object for a Microsoft.Web/serverfarms resource. */ class Properties extends ResourceProperties { - private ServerFarmsResource serverFarm; + private ServerFarmsResource parent; /** * Constructs a Properties object for the given server farm. */ - Properties() { this = serverFarm.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the compute mode. */ - StringLiteral getComputeMode() { result = this.getProperty("computeMode") } + String getComputeMode() { result = this.getProperty("computeMode") } /** * Returns the compute mode as a string. @@ -885,7 +1117,7 @@ module Web { /** * Gets the worker size. */ - StringLiteral getWorkerSize() { result = this.getProperty("workerSize") } + String getWorkerSize() { result = this.getProperty("workerSize") } /** * Returns the worker size as a string. @@ -915,13 +1147,13 @@ module Web { /** * Gets the per site scaling setting. */ - BooleanLiteral getPerSiteScaling() { result = this.getProperty("perSiteScaling") } + Boolean getPerSiteScaling() { result = this.getProperty("perSiteScaling") } /** * Returns true if per-site scaling is enabled. */ predicate isPerSiteScalingEnabled() { - exists(BooleanLiteral perSiteScaling | + exists(Boolean perSiteScaling | perSiteScaling = this.getPerSiteScaling() and perSiteScaling.getBool() = true ) @@ -930,13 +1162,13 @@ module Web { /** * Gets the elastic scaling setting. */ - BooleanLiteral getElasticScaleEnabled() { result = this.getProperty("elasticScaleEnabled") } + Boolean getElasticScaleEnabled() { result = this.getProperty("elasticScaleEnabled") } /** * Returns true if elastic scaling is enabled. */ predicate isElasticScaleEnabled() { - exists(BooleanLiteral elasticScale | + exists(Boolean elasticScale | elasticScale = this.getElasticScaleEnabled() and elasticScale.getBool() = true ) @@ -945,29 +1177,95 @@ module Web { /** * Gets whether zone redundant deployment is enabled. */ - BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + Boolean getZoneRedundant() { result = this.getProperty("zoneRedundant") } /** * Returns true if zone redundant deployment is enabled. */ predicate isZoneRedundant() { - exists(BooleanLiteral zoneRedundant | + exists(Boolean zoneRedundant | zoneRedundant = this.getZoneRedundant() and zoneRedundant.getBool() = true ) } /** - * Gets the maximum number of workers. + * Gets the hosting environment profile. + */ + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = parent.getProperty("hostingEnvironmentProfile") + } + + /** + * Gets whether the App Service Plan is reserved (for Linux). */ - Number getMaximumElasticWorkerCount() { result = this.getProperty("maximumElasticWorkerCount") } + Boolean getReserved() { result = parent.getProperty("reserved") } /** - * Returns the maximum number of workers as an integer. + * Returns true if the App Service Plan is reserved (for Linux). */ - int maximumElasticWorkerCount() { result = this.getMaximumElasticWorkerCount().getValue() } + predicate isReserved() { + exists(Boolean reserved | + reserved = this.getReserved() and + reserved.getBool() = true + ) + } override string toString() { result = "ServerFarmProperties" } } } -} \ No newline at end of file + + /** + * Module containing property classes for Deployment Slot resources. + */ + module SlotProperties { + /** + * Represents the properties object for a Microsoft.Web/sites/slots resource. + */ + class Properties extends ResourceProperties { + private SlotResource parent; + + /** + * Constructs a Properties object for the given slot. + */ + Properties() { this = parent.getProperty("properties") } + + /** + * Gets the HTTPS-only flag for the deployment slot. + */ + Boolean getHttpsOnly() { + result = this.getProperty("httpsOnly") or + result = parent.getProperty("httpsOnly") + } + + /** + * Returns true if HTTPS-only setting is enabled for the deployment slot. + */ + predicate isHttpsOnly() { + exists(Boolean httpsOnly | + httpsOnly = this.getHttpsOnly() and + httpsOnly.getBool() = true + ) + } + + /** + * Gets the site configuration. + */ + SitesProperties::SiteConfig getSiteConfig() { result = this.getProperty("siteConfig") } + + /** + * Gets the serverFarmId (App Service Plan ID). + */ + String getServerFarmId() { result = this.getProperty("serverFarmId") } + + /** + * Gets the hostingEnvironmentProfile. + */ + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = this.getProperty("hostingEnvironmentProfile") + } + + override string toString() { result = "SlotProperties" } + } + } +} diff --git a/ql/src/reliability/WebAppAlwaysOnDisabled.md b/ql/src/reliability/WebAppAlwaysOnDisabled.md new file mode 100644 index 0000000..48ca6bf --- /dev/null +++ b/ql/src/reliability/WebAppAlwaysOnDisabled.md @@ -0,0 +1,65 @@ +# Web App without Always On enabled + +Azure Web Apps should have the "Always On" setting enabled in production environments to ensure reliability, performance, and security. When Always On is disabled, the application may experience cold start delays and periodic shutdowns that could impact availability and security. + +## Recommendation + +Enable the "Always On" setting for all production Azure Web Apps: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: true // Enable Always On for reliability and security + } + } +} +``` + +## Example + +### Suboptimal configuration + +```bicep +resource webAppWithoutAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + // Always On is not explicitly enabled, which can lead to + // application shutdowns and cold starts + } + } +} +``` + +### Recommended configuration + +```bicep +resource webAppWithAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: true // Explicitly enable Always On + } + } +} +``` + +## Why this is important + +When "Always On" is disabled: +- The application can be unloaded after a period of inactivity +- Cold starts can cause delays for users and create availability issues +- Periodic recycling can interrupt background processes +- Attackers could potentially exploit behavior differences between cold and warm instances + +## References +* [Configure an App Service app](https://learn.microsoft.com/en-us/azure/app-service/configure-common) +* [Azure App Service plan overview](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans) diff --git a/ql/src/reliability/WebAppAlwaysOnDisabled.ql b/ql/src/reliability/WebAppAlwaysOnDisabled.ql new file mode 100644 index 0000000..45b5a91 --- /dev/null +++ b/ql/src/reliability/WebAppAlwaysOnDisabled.ql @@ -0,0 +1,24 @@ +/** + * @name Web App without Always On enabled + * @description Azure Web Apps should have Always On enabled to ensure reliability and security. + * @kind problem + * @problem.severity warning + * @security-severity 4.0 + * @precision high + * @id bicep/webapp-always-on-disabled + * @tags security + * bicep + * azure + * reliability + */ + +import bicep + +from Web::SitesResource site, Web::SitesProperties::SiteConfig config +where + config = site.getProperties().getSiteConfig() and + not config.isAlwaysOn() and + // Only apply to production web apps, not function apps + site.isWebApp() and + not site.isFunctionApp() +select site, "Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts." diff --git a/ql/src/security/CWE-284/WebAppPublicNetworkAccess.md b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.md new file mode 100644 index 0000000..5a6378f --- /dev/null +++ b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.md @@ -0,0 +1,65 @@ +# Web App with unrestricted public access + +Azure Web Apps with unrestricted public network access are potentially vulnerable to attacks from the internet. For sensitive applications, restricting network access by using Virtual Network integration or by disabling public network access provides an additional layer of security. + +## Recommendation + +Restrict public network access to your Azure Web App by either: + +1. Integrating with Azure Virtual Network: +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + virtualNetworkSubnetId: subnet.id // Add VNet integration + } +} +``` + +2. Or by explicitly disabling public network access (for critical applications): +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Disabled' // Disable public network access + } +} +``` + +## Example + +### Insecure configuration + +```bicep +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + // No network restrictions - accessible from anywhere + } +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Disabled' + virtualNetworkSubnetId: resourceId('Microsoft.Network/virtualNetworks/subnets', 'myVNet', 'mySubnet') + } +} +``` + +## References +* [Azure App Service networking features](https://learn.microsoft.com/en-us/azure/app-service/networking-features) +* [Configure regional VNet integration](https://learn.microsoft.com/en-us/azure/app-service/configure-vnet-integration-enable) +* [CWE-284: Improper Access Control](https://cwe.mitre.org/data/definitions/284.html) diff --git a/ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql new file mode 100644 index 0000000..0d028b7 --- /dev/null +++ b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql @@ -0,0 +1,27 @@ +/** + * @name Web App with unrestricted public access + * @description Azure Web Apps should restrict public network access to enhance security. + * @kind problem + * @problem.severity warning + * @security-severity 6.5 + * @precision high + * @id bicep/sites-public-network-access + * @tags security + * bicep + * azure + * CWE-284 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::Properties props +where + props = site.getProperties() and + // Check if the site has public network access enabled (default) and no VNet integration + props.isPublicNetworkAccessEnabled() and + not exists(StringLiteral vnetSubnetId | + vnetSubnetId = site.getVirtualNetworkSubnetId() or + vnetSubnetId = props.getVirtualNetworkSubnetId() + ) +select site, "Azure Web App has unrestricted public network access without VNet integration, potentially increasing attack surface." diff --git a/ql/src/security/CWE-295/WebAppMissingClientCert.md b/ql/src/security/CWE-295/WebAppMissingClientCert.md new file mode 100644 index 0000000..8adcd3b --- /dev/null +++ b/ql/src/security/CWE-295/WebAppMissingClientCert.md @@ -0,0 +1,58 @@ +# Web App without Client Certificate requirement + +Azure Web Apps that handle sensitive operations should consider requiring client certificates for mutual TLS authentication. Client certificates provide an additional layer of security by ensuring that only authenticated clients with valid certificates can access the application. + +## Recommendation + +For applications handling sensitive information or operations, enable and require client certificates: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'mySecureWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Required' // Enforce client certificate validation + } + httpsOnly: true +} +``` + +## Example + +### Incomplete configuration + +```bicep +resource webAppWithIncompleteConfig 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + // Client certificates are enabled but not required + clientCertEnabled: true + // Missing clientCertMode: 'Required' + } + httpsOnly: true +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'mySecureWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Required' // Certificates are required + } + httpsOnly: true +} +``` + +## References +* [Configure TLS mutual authentication for Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) +* [Microsoft.Web/sites resource type](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites) +* [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html) diff --git a/ql/src/security/CWE-295/WebAppMissingClientCert.ql b/ql/src/security/CWE-295/WebAppMissingClientCert.ql new file mode 100644 index 0000000..cdea4d6 --- /dev/null +++ b/ql/src/security/CWE-295/WebAppMissingClientCert.ql @@ -0,0 +1,31 @@ +/** + * @name Web App without Client Certificate requirement + * @description Azure Web Apps handling sensitive operations should require client certificates for additional authentication. + * @kind problem + * @problem.severity warning + * @security-severity 5.0 + * @precision medium + * @id bicep/webapp-missing-client-cert + * @tags security + * bicep + * azure + * CWE-295 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site +where + // Site has HTTPS enabled (showing security awareness) + site.isHttpsOnly() and + ( + // But doesn't have client certificate enabled at the site level + not site.isClientCertEnabled() or + // Or has it enabled but not set to required in properties + ( + site.isClientCertEnabled() and + not site.getProperties().isClientCertRequired() + ) + ) +select site, "Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication." diff --git a/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md new file mode 100644 index 0000000..b19dfb5 --- /dev/null +++ b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md @@ -0,0 +1,59 @@ +# Web App with Remote Debugging enabled + +Remote debugging should be disabled in production Azure Web Apps. When remote debugging is enabled in a production environment, it can expose sensitive information and potentially allow unauthorized access to your application. + +## Recommendation + +Disable remote debugging in production environments: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + siteConfig: { + // Ensure remote debugging is disabled + remoteDebuggingEnabled: false + // or omit the property entirely as it defaults to false + } + } +} +``` + +## Example + +### Insecure configuration + +```bicep +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecureApp' + location: location + properties: { + siteConfig: { + // Remote debugging should never be enabled in production + remoteDebuggingEnabled: true + remoteDebuggingVersion: 'VS2019' + } + } +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secureApp' + location: location + properties: { + siteConfig: { + // Remote debugging is explicitly disabled + remoteDebuggingEnabled: false + } + } +} +``` + +## References +* [Azure App Service security best practices](https://learn.microsoft.com/en-us/azure/app-service/security-recommendations) +* [Microsoft.Web/sites resource type](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites) +* [CWE-306: Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) diff --git a/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql new file mode 100644 index 0000000..99fac15 --- /dev/null +++ b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql @@ -0,0 +1,22 @@ +/** + * @name Web App with Remote Debugging enabled + * @description Remote debugging should be disabled in production web apps to prevent unauthorized access. + * @kind problem + * @problem.severity error + * @security-severity 8.0 + * @precision high + * @id bicep/webapp-remote-debugging-enabled + * @tags security + * bicep + * azure + * CWE-306 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::SiteConfig config +where + config = site.getProperties().getSiteConfig() and + config.isRemoteDebuggingEnabled() +select site, "Azure Web App has remote debugging enabled, which can expose sensitive debugging information." diff --git a/ql/src/security/CWE-319/SitesWithoutHttpsOnly.md b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.md new file mode 100644 index 0000000..a5090db --- /dev/null +++ b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.md @@ -0,0 +1,55 @@ +# Web App without HTTPS-only setting + +Azure Web Apps should have the HTTPS-only setting enabled to enforce secure communications. When the HTTPS-only flag is not set, the web app can accept connections over unencrypted HTTP, potentially exposing sensitive information. + +## Recommendation + +Enable the HTTPS-only setting for all Azure Web Apps to ensure that all traffic is redirected to HTTPS. + +In Bicep files, set the `httpsOnly` property to `true`: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + // other properties + } + // Enable HTTPS-only mode + httpsOnly: true +} +``` + +## Example + +### Insecure configuration + +```bicep +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + } + // HTTPS-only is not set, defaulting to false +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + } + // HTTPS-only is explicitly enabled + httpsOnly: true +} +``` + +## References +* [Azure App Service secure settings](https://learn.microsoft.com/en-us/azure/app-service/overview-security#https-and-certificates) +* [Microsoft.Web/sites resource type](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites) +* [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) diff --git a/ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql new file mode 100644 index 0000000..2f1832a --- /dev/null +++ b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql @@ -0,0 +1,22 @@ +/** + * @name Web App without HTTPS-only setting + * @description Azure Web Apps should enforce HTTPS-only mode to secure communications. + * @kind problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id bicep/sites-without-https-only + * @tags security + * bicep + * azure + * CWE-319 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site +where + // Find all sites without httpsOnly set to true + not site.isHttpsOnly() +select site, "Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections." \ No newline at end of file diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected new file mode 100644 index 0000000..1a9eee4 --- /dev/null +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected @@ -0,0 +1,2 @@ +| app.bicep:17:1:27:1 | AppService[webAppWithoutAlwaysOn] | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | +| app.bicep:30:1:40:1 | AppService[webAppWithAlwaysOnDisabled] | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref new file mode 100644 index 0000000..f2862f9 --- /dev/null +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref @@ -0,0 +1 @@ +reliability/WebAppAlwaysOnDisabled.ql diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep new file mode 100644 index 0000000..4e8c110 --- /dev/null +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep @@ -0,0 +1,66 @@ +// This is a test file for the WebAppAlwaysOnDisabled query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// Insecure: Web App without Always On enabled (Default) +resource webAppWithoutAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'webapp-without-alwayson' + location: location + kind: 'app' // Specifies it's a regular web app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + // Always On is not explicitly set, defaults to false + } + } +} + +// Insecure: Web App with Always On explicitly disabled +resource webAppWithAlwaysOnDisabled 'Microsoft.Web/sites@2022-03-01' = { + name: 'webapp-with-alwayson-disabled' + location: location + kind: 'app' // Specifies it's a regular web app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: false // Explicitly disabled + } + } +} + +// Secure: Web App with Always On enabled +resource webAppWithAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'webapp-with-alwayson' + location: location + kind: 'app' // Specifies it's a regular web app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: true // Explicitly enabled + } + } +} + +// Function App without Always On (should not be flagged) +resource functionApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'function-app-without-alwayson' + location: location + kind: 'functionapp' // Specifies it's a function app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + // Always On not set for function app is acceptable + } + } +} diff --git a/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected new file mode 100644 index 0000000..c41b7ce --- /dev/null +++ b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected @@ -0,0 +1,2 @@ +| app.bicep:46:1:54:1 | AppService[insecureWebApp] | Azure Web App has unrestricted public network access without VNet integration, potentially increasing attack surface. | +| app.bicep:57:1:65:1 | AppService[explicitlyInsecureWebApp] | Azure Web App has unrestricted public network access without VNet integration, potentially increasing attack surface. | diff --git a/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref new file mode 100644 index 0000000..df65360 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref @@ -0,0 +1 @@ +security/CWE-284/WebAppPublicNetworkAccess.ql diff --git a/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep new file mode 100644 index 0000000..b763b22 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep @@ -0,0 +1,96 @@ +// This is a test file for the WebAppPublicNetworkAccess query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// VNet resource +resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: 'test-vnet' + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '10.0.0.0/16' + ] + } + subnets: [ + { + name: 'app-subnet' + properties: { + addressPrefix: '10.0.1.0/24' + delegations: [ + { + name: 'delegation' + properties: { + serviceName: 'Microsoft.Web/serverfarms' + } + } + ] + } + } + ] + } +} + +// Insecure: Web App with default public network access (enabled) and no VNet integration +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecure-public-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + // No public network access restrictions + // No VNet integration + } +} + +// Insecure: Web App with explicitly enabled public network access and no VNet integration +resource explicitlyInsecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'explicitly-insecure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Enabled' // Explicitly allowing public access + // No VNet integration + } +} + +// Secure: Web App with VNet integration +resource secureWithVNetWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-vnet-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + // Public network access not explicitly disabled, but has VNet integration + virtualNetworkSubnetId: '${vnet.id}/subnets/app-subnet' + } +} + +// Secure: Web App with disabled public network access +resource secureNoPublicWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-no-public-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Disabled' // Explicitly disabling public access + } +} + +// Secure: Web App with VNet integration at the properties level +resource secureWithPropsVNetWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-props-vnet-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + virtualNetworkSubnetId: '${vnet.id}/subnets/app-subnet' + } +} diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected new file mode 100644 index 0000000..83cb2f4 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected @@ -0,0 +1,3 @@ +| app.bicep:18:1:26:1 | AppService[insecureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | +| app.bicep:30:1:39:1 | AppService[partiallySecureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | +| app.bicep:43:1:52:1 | AppService[explicitlyOptionalWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref new file mode 100644 index 0000000..dd57955 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref @@ -0,0 +1 @@ +security/CWE-295/WebAppMissingClientCert.ql diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep new file mode 100644 index 0000000..fc972b7 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep @@ -0,0 +1,77 @@ +// This is a test file for the WebAppMissingClientCert query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// Insecure: Web App with HTTPS-Only but no client cert configuration +// This should be flagged by the query +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecure-webapp-no-clientcert' + location: location + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true // HTTPS is enabled but no client cert + // No client cert configuration + } +} + +// Insecure: Web App with HTTPS-Only and client cert enabled but not required +// This should be flagged by the query +resource partiallySecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'partially-secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + httpsOnly: true + // clientCertMode not set to Required + } +} + +// Insecure: Web App with explicit non-Required client cert mode +// This should be flagged by the query +resource explicitlyOptionalWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'explicitly-optional-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Optional' // Explicitly not required + httpsOnly: true + } +} + +// Secure: Web App with HTTPS-Only and required client cert +// This should NOT be flagged by the query +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Required' // Client cert is required + httpsOnly: true + } +} + +// Web App without HTTPS-Only +// This should NOT be flagged by the query (since we only check apps with HTTPS enabled) +resource httpWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'http-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + // No client cert configuration + } + // No httpsOnly setting +} diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected new file mode 100644 index 0000000..43ec9f4 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected @@ -0,0 +1,2 @@ +| app.bicep:17:1:27:1 | AppService[insecureWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | +| app.bicep:43:1:53:1 | AppService[missingHttpsOnlyWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref new file mode 100644 index 0000000..4d84307 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref @@ -0,0 +1 @@ +security/CWE-319/SitesWithoutHttpsOnly.ql diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep new file mode 100644 index 0000000..6cbe696 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep @@ -0,0 +1,53 @@ +// This is a test file for the SitesWithoutHttpsOnly query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// Insecure: Web App without HTTPS Only +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + ftpsState: 'AllAllowed' // Insecure: allows non-secure FTP + } + httpsOnly: false // Explicitly insecure: allows HTTP + } +} + +// Secure: Web App with HTTPS Only enabled +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + ftpsState: 'FtpsOnly' // Secure: only allows FTPS + } + httpsOnly: true // Secure: enforces HTTPS + } +} + +// Insecure: Web App with missing httpsOnly property +resource missingHttpsOnlyWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'missing-httpsonly-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + ftpsState: 'AllAllowed' // Insecure: allows non-secure FTP + } + // httpsOnly is not specified - defaults to false in Azure + } +}