From f0efa82c7c6bcefa726e023eb080a7c7fbdf26c4 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Mon, 7 Jul 2025 12:09:34 +0100 Subject: [PATCH 1/4] feat: Add Site Resources --- ql/lib/codeql/bicep/Frameworks.qll | 1 + .../codeql/bicep/frameworks/Microsoft/Web.qll | 973 ++++++++++++++++++ 2 files changed, 974 insertions(+) create mode 100644 ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll diff --git a/ql/lib/codeql/bicep/Frameworks.qll b/ql/lib/codeql/bicep/Frameworks.qll index 0235aac..eef3ac9 100644 --- a/ql/lib/codeql/bicep/Frameworks.qll +++ b/ql/lib/codeql/bicep/Frameworks.qll @@ -9,3 +9,4 @@ import frameworks.Microsoft.Network import frameworks.Microsoft.Storage import frameworks.Microsoft.Databases import frameworks.Microsoft.KeyVault +import frameworks.Microsoft.Web diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll new file mode 100644 index 0000000..1db4534 --- /dev/null +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll @@ -0,0 +1,973 @@ +/** + * Provides classes for working with Microsoft.Web resources in Bicep. + * + * This module defines classes for Azure App Service resources (Microsoft.Web/sites), + * their properties, configurations, and security settings. + * + * Classes: + * - WebResource: Base class for all Microsoft.Web resources + * - SitesResource: Class for Microsoft.Web/sites resources (App Services) + * - SiteConfigProperties: Class for site configuration properties + * - SiteIdentityProperties: Class for site managed identity configuration + * - PublicWebResource: Class for publicly accessible web resources + * - ServerFarmsResource: Class for Microsoft.Web/serverfarms resources (App Service Plans) + * - 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) + */ + +private import bicep +private import codeql.bicep.Concepts +private import codeql.bicep.frameworks.Microsoft.General +private import codeql.bicep.ast.Literals + +module Web { + /** + * Represents a Microsoft.Web resource in a Bicep file. + * Base class for all Web-related resources. + */ + class WebResource extends AzureResource { + /** + * Constructs a WebResource for any Microsoft.Web resource type. + */ + WebResource() { this.getResourceType().regexpMatch("^Microsoft.Web/.*") } + + override string toString() { result = "WebResource[" + this.getName() + "]" } + } + + /** + * Represents a Microsoft.Web/sites resource (App Service) in a Bicep file. + * See: https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites + */ + class SitesResource extends WebResource { + /** + * Constructs a SitesResource for Microsoft.Web/sites resources. + */ + SitesResource() { this.getResourceType().regexpMatch("^Microsoft.Web/sites@.*") } + + /** + * Gets the properties object for the App Service. + */ + SitesProperties::Properties getProperties() { result = this.getProperty("properties") } + + /** + * Gets the identity configuration for the App Service. + */ + SitesProperties::SiteIdentity getIdentity() { result = this.getProperty("identity") } + + /** + * Gets the kind of App Service (e.g., "app", "functionapp", "api"). + */ + StringLiteral 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()) + } + + /** + * Checks if the site is a function app. + */ + predicate isFunctionApp() { this.kind().regexpMatch(".*functionapp.*") } + + /** + * Checks if the site is a regular web app. + */ + 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. + */ + predicate isHttpsOnly() { + exists(BooleanLiteral httpsOnly | + httpsOnly = this.getHttpsOnly() and + httpsOnly.getBool() = true + ) + } + + /** + * Gets the extendedLocation configuration. + */ + Object getExtendedLocation() { result = this.getProperty("extendedLocation") } + + /** + * Gets the clientAffinityEnabled flag. + */ + BooleanLiteral getClientAffinityEnabled() { result = this.getProperty("clientAffinityEnabled") } + + /** + * Returns true if client affinity is enabled. + */ + predicate isClientAffinityEnabled() { + exists(BooleanLiteral clientAffinity | + clientAffinity = this.getClientAffinityEnabled() and + clientAffinity.getBool() = true + ) + } + + /** + * Gets the clientCertEnabled flag. + */ + BooleanLiteral getClientCertEnabled() { result = this.getProperty("clientCertEnabled") } + + /** + * Returns true if client certificates are enabled. + */ + predicate isClientCertEnabled() { + exists(BooleanLiteral clientCert | + clientCert = this.getClientCertEnabled() and + clientCert.getBool() = true + ) + } + + /** + * Gets the clientCertMode setting. + */ + StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } + + /** + * Gets the hostNameSslStates array. + */ + Array getHostNameSslStates() { result = this.getProperty("hostNameSslStates") } + + /** + * Gets the hyperV setting. + */ + BooleanLiteral getHyperV() { result = this.getProperty("hyperV") } + + /** + * Returns true if Hyper-V is enabled. + */ + predicate isHyperVEnabled() { + exists(BooleanLiteral hyperv | + hyperv = this.getHyperV() and + hyperv.getBool() = true + ) + } + + /** + * Gets the keyVaultReferenceIdentity. + */ + StringLiteral getKeyVaultReferenceIdentity() { result = this.getProperty("keyVaultReferenceIdentity") } + + /** + * Gets the redundancyMode. + */ + StringLiteral getRedundancyMode() { result = this.getProperty("redundancyMode") } + + /** + * Gets the storageAccountRequired flag. + */ + BooleanLiteral getStorageAccountRequired() { result = this.getProperty("storageAccountRequired") } + + /** + * Returns true if a storage account is required. + */ + predicate isStorageAccountRequired() { + exists(BooleanLiteral storageReq | + storageReq = this.getStorageAccountRequired() and + storageReq.getBool() = true + ) + } + + /** + * Gets the virtualNetworkSubnetId. + */ + StringLiteral getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") } + + override string toString() { result = "AppService[" + this.getIdentifier().getName() + "]" } + } + + /** + * Represents a Microsoft.Web/serverfarms resource (App Service Plan) in a Bicep file. + * See: https://learn.microsoft.com/en-us/azure/templates/microsoft.web/serverfarms + */ + class ServerFarmsResource extends WebResource { + /** + * Constructs a ServerFarmsResource for Microsoft.Web/serverfarms resources. + */ + ServerFarmsResource() { this.getResourceType().regexpMatch("^Microsoft.Web/serverfarms@.*") } + + /** + * Gets the properties object for the App Service Plan. + */ + ServerFarmsProperties::Properties getProperties() { result = this.getProperty("properties") } + + /** + * Gets the SKU object for the App Service Plan. + */ + override Sku getSku() { result = this.getProperty("sku") } + + /** + * Gets whether the App Service Plan is reserved (for Linux). + */ + BooleanLiteral getReserved() { result = this.getProperty("reserved") } + + /** + * Returns true if the App Service Plan is reserved (for Linux). + */ + predicate isReserved() { + exists(BooleanLiteral reserved | + reserved = this.getReserved() and + reserved.getBool() = true + ) + } + + /** + * Gets the hosting environment profile. + */ + Object getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } + + /** + * Gets whether zone redundancy is enabled. + */ + BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + + /** + * Returns true if zone redundancy is enabled. + */ + predicate isZoneRedundant() { + exists(BooleanLiteral zoneRedundant | + zoneRedundant = this.getZoneRedundant() and + zoneRedundant.getBool() = true + ) + } + + override string toString() { result = "AppServicePlan[" + this.getIdentifier().getName() + "]" } + } + + /** + * Represents a Microsoft.Web/sites/slots resource (Deployment Slot) in a Bicep file. + * See: https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites/slots + */ + class SlotResource extends WebResource { + /** + * Constructs a SlotResource for Microsoft.Web/sites/slots resources. + */ + SlotResource() { this.getResourceType().regexpMatch("^Microsoft.Web/sites/slots@.*") } + + /** + * Gets the properties object for the deployment slot. + */ + SitesProperties::Properties getProperties() { result = this.getProperty("properties") } + + /** + * Gets the identity configuration for the deployment slot. + */ + SitesProperties::SiteIdentity getIdentity() { result = this.getProperty("identity") } + + /** + * Gets the kind of the deployment slot. + */ + StringLiteral 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 | + fullName = this.getName() and + result = fullName.regexpCapture("([^/]+)/.*", 1) + ) + } + + /** + * Gets the slot name (without the parent site name). + */ + string getSlotName() { + // Parse from the resource name which is in the format "siteName/slotName" + exists(string fullName | + fullName = this.getName() and + result = fullName.regexpCapture("[^/]+/(.*)", 1) + ) + } + + override string toString() { result = "DeploymentSlot[" + this.getIdentifier().getName() + "]" } + } + + /** + * Represents a Microsoft.Web/staticSites resource (Static Web App) in a Bicep file. + * See: https://learn.microsoft.com/en-us/azure/templates/microsoft.web/staticsites + */ + class StaticSitesResource extends WebResource { + /** + * Constructs a StaticSitesResource for Microsoft.Web/staticSites resources. + */ + StaticSitesResource() { this.getResourceType().regexpMatch("^Microsoft.Web/staticSites@.*") } + + /** + * Gets the properties object for the Static Web App. + */ + StaticSitesProperties::Properties getProperties() { result = this.getProperty("properties") } + + /** + * Gets the identity configuration for the Static Web App. + */ + 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() + "]" } + } + + /** + * Represents a Microsoft.Web/hostingEnvironments resource (App Service Environment) in a Bicep file. + * See: https://learn.microsoft.com/en-us/azure/templates/microsoft.web/hostingenvironments + */ + class HostingEnvironmentsResource extends WebResource { + /** + * Constructs a HostingEnvironmentsResource for Microsoft.Web/hostingEnvironments resources. + */ + HostingEnvironmentsResource() { this.getResourceType().regexpMatch("^Microsoft.Web/hostingEnvironments@.*") } + + /** + * Gets the properties object for the App Service Environment. + */ + HostingEnvironmentsProperties::Properties getProperties() { result = this.getProperty("properties") } + + /** + * Gets the kind of the App Service Environment. + */ + StringLiteral 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() + "]" } + } + + /** + * Module containing properties and configurations for Microsoft.Web/sites resources. + */ + module SitesProperties { + /** + * Represents the properties object for a Microsoft.Web/sites resource. + */ + class Properties extends ResourceProperties { + private SitesResource site; + + /** + * Constructs a Properties object for the given site. + */ + Properties() { this = site.getProperty("properties") } + + /** + * Gets the site configuration. + */ + SiteConfig getSiteConfig() { result = this.getProperty("siteConfig") } + + /** + * Gets the serverFarmId (App Service Plan ID). + */ + StringLiteral getServerFarmId() { result = this.getProperty("serverFarmId") } + + /** + * Gets the hostingEnvironmentProfile. + */ + Object getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } + + /** + * Gets the public network access setting. + */ + StringLiteral getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } + + /** + * Gets the virtualNetworkSubnetId. + */ + StringLiteral getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") } + + /** + * Gets the enabled value. + */ + BooleanLiteral getEnabled() { result = this.getProperty("enabled") } + + /** + * Returns true if the site is enabled. + */ + predicate isEnabled() { + exists(BooleanLiteral enabled | + enabled = this.getEnabled() and + enabled.getBool() = true + ) + } + + /** + * Gets the client certificate mode. + */ + StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } + + /** + * Returns true if client certificate is required. + */ + predicate isClientCertRequired() { + exists(StringLiteral mode | + mode = this.getClientCertMode() and + mode.getValue() = "Required" + ) + } + + /** + * Gets the client certificate exclusion paths. + */ + StringLiteral getClientCertExclusionPaths() { result = this.getProperty("clientCertExclusionPaths") } + + /** + * Gets the container size. + */ + Number getContainerSize() { result = this.getProperty("containerSize") } + + /** + * Gets the custom domain verification ID. + */ + StringLiteral getCustomDomainVerificationId() { result = this.getProperty("customDomainVerificationId") } + + /** + * Gets the daily memory time quota. + */ + Number getDailyMemoryTimeQuota() { result = this.getProperty("dailyMemoryTimeQuota") } + + /** + * Gets the default hostname. + */ + StringLiteral getDefaultHostname() { result = this.getProperty("defaultHostname") } + + /** + * Gets the https certificate settings. + */ + HttpsCertificates getHttpsCertificates() { result = this.getProperty("httpsCertificates") } + + /** + * Returns true if public network access is enabled. + */ + predicate isPublicNetworkAccessEnabled() { + not exists(StringLiteral publicNetworkAccess | + publicNetworkAccess = this.getPublicNetworkAccess() and + publicNetworkAccess.getValue() = "Disabled" + ) + } + + override string toString() { result = "SiteProperties" } + } + + /** + * Represents the site configuration for a Microsoft.Web/sites resource. + */ + class SiteConfig extends Object { + private Properties properties; + + /** + * Constructs a SiteConfig object. + */ + SiteConfig() { this = properties.getProperty("siteConfig") } + + /** + * Gets the minimum TLS version. + */ + StringLiteral getMinTlsVersion() { result = this.getProperty("minTlsVersion") } + + /** + * Gets the ftps state setting. + */ + StringLiteral getFtpsState() { result = this.getProperty("ftpsState") } + + /** + * Gets whether remote debugging is enabled. + */ + BooleanLiteral getRemoteDebuggingEnabled() { result = this.getProperty("remoteDebuggingEnabled") } + + /** + * Returns true if remote debugging is enabled. + */ + predicate isRemoteDebuggingEnabled() { + exists(BooleanLiteral debugEnabled | + debugEnabled = this.getRemoteDebuggingEnabled() and + debugEnabled.getBool() = true + ) + } + + /** + * Gets the remote debugging version. + */ + StringLiteral getRemoteDebuggingVersion() { result = this.getProperty("remoteDebuggingVersion") } + + /** + * Gets whether HTTP 2.0 is enabled. + */ + BooleanLiteral getHttp20Enabled() { result = this.getProperty("http20Enabled") } + + /** + * Returns true if HTTP 2.0 is enabled. + */ + predicate isHttp20Enabled() { + exists(BooleanLiteral http20 | + http20 = this.getHttp20Enabled() and + http20.getBool() = true + ) + } + + /** + * Gets whether Always On is enabled. + */ + BooleanLiteral getAlwaysOn() { result = this.getProperty("alwaysOn") } + + /** + * Returns true if Always On is enabled. + */ + predicate isAlwaysOn() { + exists(BooleanLiteral alwaysOn | + alwaysOn = this.getAlwaysOn() and + alwaysOn.getBool() = true + ) + } + + /** + * Gets whether web sockets are enabled. + */ + BooleanLiteral getWebSocketsEnabled() { result = this.getProperty("webSocketsEnabled") } + + /** + * Returns true if web sockets are enabled. + */ + predicate areWebSocketsEnabled() { + exists(BooleanLiteral webSockets | + webSockets = this.getWebSocketsEnabled() and + webSockets.getBool() = true + ) + } + + /** + * Gets the application stack. + */ + Object getApplicationStack() { result = this.getProperty("applicationStack") } + + /** + * Gets the connection strings. + */ + Array getConnectionStrings() { result = this.getProperty("connectionStrings") } + + /** + * Gets the app settings. + */ + Object getAppSettings() { result = this.getProperty("appSettings") } + + /** + * Gets the CORS settings. + */ + CorsSettings getCors() { result = this.getProperty("cors") } + + /** + * Gets the Linux FX version. + */ + StringLiteral getLinuxFxVersion() { result = this.getProperty("linuxFxVersion") } + + /** + * Gets the Windows FX version. + */ + StringLiteral getWindowsFxVersion() { result = this.getProperty("windowsFxVersion") } + + /** + * Gets the health check path. + */ + StringLiteral getHealthCheckPath() { result = this.getProperty("healthCheckPath") } + + string toString() { result = "SiteConfig" } + } + + /** + * Represents the CORS settings for a site configuration. + */ + class CorsSettings extends Object { + private SiteConfig siteConfig; + + /** + * Constructs a CorsSettings object. + */ + CorsSettings() { this = siteConfig.getProperty("cors") } + + /** + * Gets the allowed origins. + */ + Array getAllowedOrigins() { result = this.getProperty("allowedOrigins") } + + /** + * Gets whether credentials are supported. + */ + BooleanLiteral getSupportCredentials() { result = this.getProperty("supportCredentials") } + + /** + * Returns true if credentials are supported. + */ + predicate areCredentialsSupported() { + exists(BooleanLiteral credentials | + credentials = this.getSupportCredentials() and + credentials.getBool() = true + ) + } + + string toString() { result = "CorsSettings" } + } + + /** + * Represents the HTTPS certificates configuration for a site. + */ + class HttpsCertificates extends Array { + private Properties properties; + + /** + * Constructs an HttpsCertificates object. + */ + HttpsCertificates() { this = properties.getProperty("httpsCertificates") } + + /** + * Gets a certificate by index. + */ + Object getCertificate(int index) { + result = this.getElement(index) + } + + string toString() { result = "HttpsCertificates" } + } + + /** + * Represents the identity configuration for a Microsoft.Web/sites resource. + */ + class SiteIdentity extends Object { + private SitesResource site; + + /** + * Constructs a SiteIdentity object. + */ + SiteIdentity() { this = site.getProperty("identity") } + + /** + * Gets the type of managed identity. + */ + StringLiteral getType() { result = this.getProperty("type") } + + /** + * Returns the managed identity type as a string. + */ + string identityType() { result = this.getType().getValue() } + + /** + * Returns true if the site has a system-assigned identity. + */ + predicate hasSystemAssignedIdentity() { + this.identityType() = "SystemAssigned" or + this.identityType() = "SystemAssigned, UserAssigned" + } + + /** + * Returns true if the site has user-assigned identities. + */ + predicate hasUserAssignedIdentities() { + this.identityType() = "UserAssigned" or + this.identityType() = "SystemAssigned, UserAssigned" + } + + /** + * Gets the user-assigned identities. + */ + Object getUserAssignedIdentities() { result = this.getProperty("userAssignedIdentities") } + + string toString() { result = "SiteIdentity" } + } + } + + /** + * Module containing property classes for Static Web App resources. + */ + module StaticSitesProperties { + /** + * Represents the properties object for a Microsoft.Web/staticSites resource. + */ + class Properties extends ResourceProperties { + private StaticSitesResource staticSite; + + /** + * Constructs a Properties object for the given static site. + */ + Properties() { this = staticSite.getProperty("properties") } + + /** + * Gets the staging environment policy. + */ + StringLiteral getStagingEnvironmentPolicy() { result = this.getProperty("stagingEnvironmentPolicy") } + + /** + * Gets whether private endpoint connections are allowed. + */ + BooleanLiteral getAllowConfigFileUpdates() { result = this.getProperty("allowConfigFileUpdates") } + + /** + * Returns true if config file updates are allowed. + */ + predicate areConfigFileUpdatesAllowed() { + exists(BooleanLiteral allowUpdates | + allowUpdates = this.getAllowConfigFileUpdates() and + allowUpdates.getBool() = true + ) + } + + /** + * Gets the branch configuration for the repository. + */ + Object getRepositoryBranch() { result = this.getProperty("repositoryBranch") } + + /** + * Gets the repository token for the Static Web App. + */ + StringLiteral getRepositoryToken() { result = this.getProperty("repositoryToken") } + + /** + * Gets the repository URL for the Static Web App. + */ + StringLiteral getRepositoryUrl() { result = this.getProperty("repositoryUrl") } + + /** + * Gets whether private endpoint connections are allowed. + */ + BooleanLiteral getAllowPrivateEndpoints() { result = this.getProperty("allowPrivateEndpoints") } + + /** + * Returns true if private endpoints are allowed. + */ + predicate arePrivateEndpointsAllowed() { + exists(BooleanLiteral allowEndpoints | + allowEndpoints = this.getAllowPrivateEndpoints() and + allowEndpoints.getBool() = true + ) + } + + override string toString() { result = "StaticSiteProperties" } + } + } + + /** + * Module containing property classes for App Service Environment resources. + */ + module HostingEnvironmentsProperties { + /** + * Represents the properties object for a Microsoft.Web/hostingEnvironments resource. + */ + class Properties extends ResourceProperties { + private HostingEnvironmentsResource hostingEnv; + + /** + * Constructs a Properties object for the given hosting environment. + */ + Properties() { this = hostingEnv.getProperty("properties") } + + /** + * Gets the dedicated host count. + */ + Number getDedicatedHostCount() { result = this.getProperty("dedicatedHostCount") } + + /** + * Gets whether zone redundancy is enabled. + */ + BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + + /** + * Returns true if zone redundancy is enabled. + */ + predicate isZoneRedundant() { + exists(BooleanLiteral zoneRedundant | + zoneRedundant = this.getZoneRedundant() and + zoneRedundant.getBool() = true + ) + } + + /** + * Gets the internal load balancing mode. + */ + StringLiteral getInternalLoadBalancingMode() { result = this.getProperty("internalLoadBalancingMode") } + + /** + * Gets the cluster settings. + */ + Array getClusterSettings() { result = this.getProperty("clusterSettings") } + + /** + * Gets the virtual network configuration. + */ + VnetConfiguration getVirtualNetworkProfile() { result = this.getProperty("virtualNetworkProfile") } + + override string toString() { result = "HostingEnvironmentProperties" } + } + + /** + * Represents the virtual network configuration for an App Service Environment. + */ + class VnetConfiguration extends Object { + private Properties props; + + /** + * Constructs a VnetConfiguration object. + */ + VnetConfiguration() { this = props.getProperty("virtualNetworkProfile") } + + /** + * Gets the subnet ID. + */ + StringLiteral getSubnetId() { result = this.getProperty("id") } + + /** + * Gets the subnet resource ID. + */ + string subnetId() { result = this.getSubnetId().getValue() } + + string toString() { result = "VnetConfiguration" } + } + } + + /** + * Module containing property classes for App Service Plan resources. + */ + module ServerFarmsProperties { + /** + * Represents the properties object for a Microsoft.Web/serverfarms resource. + */ + class Properties extends ResourceProperties { + private ServerFarmsResource serverFarm; + + /** + * Constructs a Properties object for the given server farm. + */ + Properties() { this = serverFarm.getProperty("properties") } + + /** + * Gets the compute mode. + */ + StringLiteral getComputeMode() { result = this.getProperty("computeMode") } + + /** + * Returns the compute mode as a string. + */ + string computeMode() { result = this.getComputeMode().getValue() } + + /** + * Gets the worker size. + */ + StringLiteral getWorkerSize() { result = this.getProperty("workerSize") } + + /** + * Returns the worker size as a string. + */ + string workerSize() { result = this.getWorkerSize().getValue() } + + /** + * Gets the worker size ID. + */ + Number getWorkerSizeId() { result = this.getProperty("workerSizeId") } + + /** + * Returns the worker size ID as an integer. + */ + int workerSizeId() { result = this.getWorkerSizeId().getValue() } + + /** + * Gets the number of workers. + */ + Number getNumberOfWorkers() { result = this.getProperty("numberOfWorkers") } + + /** + * Returns the number of workers as an integer. + */ + int numberOfWorkers() { result = this.getNumberOfWorkers().getValue() } + + /** + * Gets the per site scaling setting. + */ + BooleanLiteral getPerSiteScaling() { result = this.getProperty("perSiteScaling") } + + /** + * Returns true if per-site scaling is enabled. + */ + predicate isPerSiteScalingEnabled() { + exists(BooleanLiteral perSiteScaling | + perSiteScaling = this.getPerSiteScaling() and + perSiteScaling.getBool() = true + ) + } + + /** + * Gets the elastic scaling setting. + */ + BooleanLiteral getElasticScaleEnabled() { result = this.getProperty("elasticScaleEnabled") } + + /** + * Returns true if elastic scaling is enabled. + */ + predicate isElasticScaleEnabled() { + exists(BooleanLiteral elasticScale | + elasticScale = this.getElasticScaleEnabled() and + elasticScale.getBool() = true + ) + } + + /** + * Gets whether zone redundant deployment is enabled. + */ + BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + + /** + * Returns true if zone redundant deployment is enabled. + */ + predicate isZoneRedundant() { + exists(BooleanLiteral zoneRedundant | + zoneRedundant = this.getZoneRedundant() and + zoneRedundant.getBool() = true + ) + } + + /** + * Gets the maximum number of workers. + */ + Number getMaximumElasticWorkerCount() { result = this.getProperty("maximumElasticWorkerCount") } + + /** + * Returns the maximum number of workers as an integer. + */ + int maximumElasticWorkerCount() { result = this.getMaximumElasticWorkerCount().getValue() } + + override string toString() { result = "ServerFarmProperties" } + } + } +} \ No newline at end of file From c65571edab481a804a79954ac6a85dc3e3a59972 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Mon, 7 Jul 2025 12:10:13 +0100 Subject: [PATCH 2/4] feat(test): Add tests --- .../library-tests/frameworks/web/Web.expected | 21 ++++ ql/test/library-tests/frameworks/web/Web.ql | 10 ++ .../library-tests/frameworks/web/app.bicep | 115 ++++++++++++++++++ .../frameworks/web/test-sites.bicep | 107 ++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 ql/test/library-tests/frameworks/web/Web.expected create mode 100644 ql/test/library-tests/frameworks/web/Web.ql create mode 100644 ql/test/library-tests/frameworks/web/app.bicep create mode 100644 ql/test/library-tests/frameworks/web/test-sites.bicep diff --git a/ql/test/library-tests/frameworks/web/Web.expected b/ql/test/library-tests/frameworks/web/Web.expected new file mode 100644 index 0000000..13d8e3c --- /dev/null +++ b/ql/test/library-tests/frameworks/web/Web.expected @@ -0,0 +1,21 @@ +webApps +| app.bicep:7:1:25:1 | (no string representation) | +| app.bicep:28:1:45:1 | AppService[${appServiceName}-insecure] | +| app.bicep:48:1:70:1 | AppService[${appServiceName}-secure] | +| app.bicep:73:1:84:1 | DeploymentSlot[${MemberExpression}/staging] | +| app.bicep:87:1:100:1 | (no string representation) | +| app.bicep:103:1:115:1 | AppService[${appServiceName}-function] | +| test-sites.bicep:9:1:16:1 | (no string representation) | +| test-sites.bicep:19:1:72:1 | (no string representation) | +| test-sites.bicep:75:1:107:1 | (no string representation) | +webSites +| app.bicep:28:1:45:1 | AppService[${appServiceName}-insecure] | +| app.bicep:48:1:70:1 | AppService[${appServiceName}-secure] | +| app.bicep:103:1:115:1 | AppService[${appServiceName}-function] | +| test-sites.bicep:19:1:72:1 | (no string representation) | +| test-sites.bicep:75:1:107:1 | (no string representation) | +webSlots +| app.bicep:73:1:84:1 | DeploymentSlot[${MemberExpression}/staging] | +webServerFarms +| app.bicep:7:1:25:1 | (no string representation) | +| test-sites.bicep:9:1:16:1 | (no string representation) | diff --git a/ql/test/library-tests/frameworks/web/Web.ql b/ql/test/library-tests/frameworks/web/Web.ql new file mode 100644 index 0000000..127f8e7 --- /dev/null +++ b/ql/test/library-tests/frameworks/web/Web.ql @@ -0,0 +1,10 @@ +private import bicep +import codeql.bicep.frameworks.Microsoft.Web + +query predicate webApps(Web::WebResource web) { any() } + +query predicate webSites(Web::SitesResource sites) { any() } + +query predicate webSlots(Web::SlotResource slots) { any() } + +query predicate webServerFarms(Web::ServerFarmsResource serverFarm) { any() } diff --git a/ql/test/library-tests/frameworks/web/app.bicep b/ql/test/library-tests/frameworks/web/app.bicep new file mode 100644 index 0000000..a48d1fc --- /dev/null +++ b/ql/test/library-tests/frameworks/web/app.bicep @@ -0,0 +1,115 @@ +param location string = resourceGroup().location +param appServicePlanName string = 'myAppServicePlan' +param appServiceName string = 'myAppService' +param staticSiteName string = 'myStaticSite' + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = { + name: appServicePlanName + location: location + sku: { + name: 'S1' + tier: 'Standard' + } + properties: { + reserved: true // For Linux + computeMode: 'Dedicated' + workerSize: 'Small' + workerSizeId: 1 + numberOfWorkers: 2 + perSiteScaling: false + elasticScaleEnabled: true + zoneRedundant: true + maximumElasticWorkerCount: 10 + } +} + +// App Service with insecure configuration (for testing queries) +resource insecureAppService 'Microsoft.Web/sites@2022-09-01' = { + name: '${appServiceName}-insecure' + location: location + kind: 'app' + properties: { + serverFarmId: appServicePlan.id + httpsOnly: false // Insecure setting + publicNetworkAccess: 'Enabled' + clientCertEnabled: false + siteConfig: { + minTlsVersion: '1.0' // Weak TLS + remoteDebuggingEnabled: true // Insecure + ftpsState: 'AllAllowed' + http20Enabled: false + alwaysOn: true + } + } +} + +// App Service with secure configuration +resource secureAppService 'Microsoft.Web/sites@2022-09-01' = { + name: '${appServiceName}-secure' + location: location + kind: 'app' + identity: { + type: 'SystemAssigned' + } + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true + publicNetworkAccess: 'Disabled' + clientCertEnabled: true + clientCertMode: 'Required' + siteConfig: { + minTlsVersion: '1.2' + remoteDebuggingEnabled: false + ftpsState: 'Disabled' + http20Enabled: true + alwaysOn: true + } + virtualNetworkSubnetId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.Network/virtualNetworks/myVNet/subnets/mySubnet' + } +} + +// Deployment slot for testing +resource testSlot 'Microsoft.Web/sites/slots@2022-09-01' = { + name: '${secureAppService.name}/staging' + location: location + kind: 'app' + properties: { + httpsOnly: true + siteConfig: { + minTlsVersion: '1.2' + alwaysOn: true + } + } +} + +// Static Web App +resource staticSite 'Microsoft.Web/staticSites@2022-09-01' = { + name: staticSiteName + location: location + sku: { + name: 'Standard' + tier: 'Standard' + } + properties: { + repositoryUrl: 'https://github.com/example/repo' + repositoryToken: 'your-token-here' + allowConfigFileUpdates: true + allowPrivateEndpoints: false + } +} + +// Function App (another kind of site) +resource functionApp 'Microsoft.Web/sites@2022-09-01' = { + name: '${appServiceName}-function' + location: location + kind: 'functionapp' + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true + siteConfig: { + minTlsVersion: '1.2' + alwaysOn: true + } + } +} diff --git a/ql/test/library-tests/frameworks/web/test-sites.bicep b/ql/test/library-tests/frameworks/web/test-sites.bicep new file mode 100644 index 0000000..3187e93 --- /dev/null +++ b/ql/test/library-tests/frameworks/web/test-sites.bicep @@ -0,0 +1,107 @@ +// This is a test file for Microsoft.Web/sites resources + +param location string = 'eastus' +param appServicePlanName string = 'test-asp' +param webAppName string = 'test-webapp' +param functionAppName string = 'test-function-app' + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: appServicePlanName + location: location + sku: { + name: 'B1' + tier: 'Basic' + } +} + +// Web App (App Service) +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: webAppName + location: location + kind: 'app' + identity: { + type: 'SystemAssigned' + } + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true + clientCertEnabled: true + clientCertMode: 'Required' + virtualNetworkSubnetId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Network/virtualNetworks/MyVnet/subnets/MySubnet' + siteConfig: { + alwaysOn: true + http20Enabled: true + minTlsVersion: '1.2' + ftpsState: 'FtpsOnly' + ipSecurityRestrictions: [ + { + ipAddress: '192.168.1.0/24' + action: 'Allow' + priority: 100 + name: 'Allow internal network' + } + { + ipAddress: '10.0.0.0/16' + action: 'Deny' + priority: 200 + name: 'Block private network' + } + ] + ipSecurityRestrictionsDefaultAction: 'Deny' + appSettings: [ + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~16' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: '00000000-0000-0000-0000-000000000000' + } + ] + connectionStrings: [ + { + name: 'MyDbConnection' + connectionString: 'Data Source=myserver;Initial Catalog=mydb;User ID=myuser;Password=mypassword;' + type: 'SQLAzure' + } + ] + linuxFxVersion: 'NODE|16-lts' + } + } +} + +// Function App +resource functionApp 'Microsoft.Web/sites@2022-03-01' = { + name: functionAppName + location: location + kind: 'functionapp' + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity': {} + } + } + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true + siteConfig: { + alwaysOn: true + use32BitWorkerProcess: false + appSettings: [ + { + name: 'AzureWebJobsStorage' + value: 'DefaultEndpointsProtocol=https;AccountName=mystorageaccount;EndpointSuffix=core.windows.net;AccountKey=mykey==' + } + { + name: 'FUNCTIONS_EXTENSION_VERSION' + value: '~4' + } + { + name: 'FUNCTIONS_WORKER_RUNTIME' + value: 'node' + } + ] + } + } +} From cfb181fa7a8bffc1c66f409015e6e9921dde8e7a Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Mon, 7 Jul 2025 12:29:39 +0100 Subject: [PATCH 3/4] feat(security): Add checks and documentation for remote debugging, insecure FTPS state, and TLS version in Azure Web Apps --- .../security/CWE-306/WebAppRemoteDebugging.md | 41 ++++++++++++ .../security/CWE-306/WebAppRemoteDebugging.ql | 22 +++++++ .../CWE-319/InsecureWebAppFtpsState.md | 63 +++++++++++++++++++ .../CWE-319/InsecureWebAppFtpsState.ql | 26 ++++++++ .../CWE-319/InsecureWebAppTlsVersion.actual | 0 .../CWE-319/InsecureWebAppTlsVersion.md | 41 ++++++++++++ .../CWE-319/InsecureWebAppTlsVersion.ql | 26 ++++++++ .../WebAppRemoteDebugging.expected | 2 + .../WebAppRemoteDebugging.qlref | 1 + .../CWE-306/WebAppRemoteDebugging/app.bicep | 46 ++++++++++++++ .../InsecureWebAppFtpsState.expected | 2 + .../InsecureWebAppFtpsState.qlref | 1 + .../CWE-319/InsecureWebAppFtpsState/app.bicep | 45 +++++++++++++ .../InsecureWebAppTlsVersion.expected | 2 + .../InsecureWebAppTlsVersion.qlref | 1 + .../InsecureWebAppTlsVersion/app.bicep | 47 ++++++++++++++ 16 files changed, 366 insertions(+) create mode 100644 ql/src/security/CWE-306/WebAppRemoteDebugging.md create mode 100644 ql/src/security/CWE-306/WebAppRemoteDebugging.ql create mode 100644 ql/src/security/CWE-319/InsecureWebAppFtpsState.md create mode 100644 ql/src/security/CWE-319/InsecureWebAppFtpsState.ql create mode 100644 ql/src/security/CWE-319/InsecureWebAppTlsVersion.actual create mode 100644 ql/src/security/CWE-319/InsecureWebAppTlsVersion.md create mode 100644 ql/src/security/CWE-319/InsecureWebAppTlsVersion.ql create mode 100644 ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.expected create mode 100644 ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.qlref create mode 100644 ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/app.bicep create mode 100644 ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.expected create mode 100644 ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.qlref create mode 100644 ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/app.bicep create mode 100644 ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.expected create mode 100644 ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.qlref create mode 100644 ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/app.bicep diff --git a/ql/src/security/CWE-306/WebAppRemoteDebugging.md b/ql/src/security/CWE-306/WebAppRemoteDebugging.md new file mode 100644 index 0000000..a9bdb9d --- /dev/null +++ b/ql/src/security/CWE-306/WebAppRemoteDebugging.md @@ -0,0 +1,41 @@ +# Remote debugging enabled in Web App + +Enabling remote debugging in production Azure Web Apps is a security risk. Remote debugging allows developers to connect to and debug the web application remotely, which can expose sensitive information and potentially allow unauthorized access to the application. + +## Recommendation + +Disable remote debugging in production environments. Remote debugging should only be enabled temporarily for development and troubleshooting purposes, and should be disabled once troubleshooting is complete. + +## Example + +Insecure configuration: + +```bicep +resource webApp 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp' + properties: { + siteConfig: { + remoteDebuggingEnabled: true + } + } +} +``` + +Secure configuration: + +```bicep +resource webApp 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp' + properties: { + siteConfig: { + remoteDebuggingEnabled: false // or omit this property as it defaults to false + } + } +} +``` + +## References + +* [Azure Web App Security Best Practices](https://learn.microsoft.com/en-us/azure/app-service/security-recommendations) +* [Remote debugging in Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/configure-language-dotnet-framework#remote-debugging) +* [Common Weakness Enumeration: CWE-306](https://cwe.mitre.org/data/definitions/306.html) diff --git a/ql/src/security/CWE-306/WebAppRemoteDebugging.ql b/ql/src/security/CWE-306/WebAppRemoteDebugging.ql new file mode 100644 index 0000000..0b0af27 --- /dev/null +++ b/ql/src/security/CWE-306/WebAppRemoteDebugging.ql @@ -0,0 +1,22 @@ +/** + * @name Remote debugging enabled in Web App + * @description Enabling remote debugging in production Azure Web Apps is a security risk. + * @kind problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id bicep/webapp-remote-debugging + * @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 should not be used in production environments" diff --git a/ql/src/security/CWE-319/InsecureWebAppFtpsState.md b/ql/src/security/CWE-319/InsecureWebAppFtpsState.md new file mode 100644 index 0000000..8825040 --- /dev/null +++ b/ql/src/security/CWE-319/InsecureWebAppFtpsState.md @@ -0,0 +1,63 @@ +# Insecure FTPS state in Web App + +Using insecure FTP or allowing both FTP and FTPS in Azure Web Apps can expose sensitive credentials and data. FTP (File Transfer Protocol) transmits data and credentials in plaintext, which can be intercepted by attackers through network sniffing. + +## Recommendation + +Use secure file transfer by setting the `ftpsState` property to `FtpsOnly` to enforce FTPS (FTP Secure), which encrypts the connection. If file transfer is not needed, consider setting it to `Disabled`. + +## Example + +Insecure configurations: + +```bicep +// Allows both FTP and FTPS +resource webApp1 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp1' + properties: { + siteConfig: { + ftpsState: 'AllAllowed' + } + } +} + +// Only allows insecure FTP +resource webApp2 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp2' + properties: { + siteConfig: { + ftpsState: 'FtpOnly' + } + } +} +``` + +Secure configurations: + +```bicep +// Only allows secure FTPS +resource webApp3 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp3' + properties: { + siteConfig: { + ftpsState: 'FtpsOnly' + } + } +} + +// Disables both FTP and FTPS +resource webApp4 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp4' + properties: { + siteConfig: { + ftpsState: 'Disabled' + } + } +} +``` + +## References + +* [Azure Web App Security Best Practices](https://learn.microsoft.com/en-us/azure/app-service/security-recommendations) +* [FTP/S connection settings for Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/configure-ftp-deploy) +* [Common Weakness Enumeration: CWE-319](https://cwe.mitre.org/data/definitions/319.html) diff --git a/ql/src/security/CWE-319/InsecureWebAppFtpsState.ql b/ql/src/security/CWE-319/InsecureWebAppFtpsState.ql new file mode 100644 index 0000000..d614afb --- /dev/null +++ b/ql/src/security/CWE-319/InsecureWebAppFtpsState.ql @@ -0,0 +1,26 @@ +/** + * @name Insecure FTPS state in Web App + * @description Using insecure FTP or allowing both FTP and FTPS in Azure Web Apps can expose sensitive credentials and data. + * @kind problem + * @problem.severity error + * @security-severity 7.8 + * @precision high + * @id bicep/insecure-webapp-ftps-state + * @tags security + * bicep + * azure + * CWE-319 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::SiteConfig config, StringLiteral ftpsState +where + config = site.getProperties().getSiteConfig() and + ftpsState = config.getFtpsState() and + ( + ftpsState.getValue() = "AllAllowed" or + ftpsState.getValue() = "FtpOnly" + ) +select site, "Azure Web App allows insecure FTP protocol: " + ftpsState.getValue() + ". Use 'FtpsOnly' or 'Disabled' instead." diff --git a/ql/src/security/CWE-319/InsecureWebAppTlsVersion.actual b/ql/src/security/CWE-319/InsecureWebAppTlsVersion.actual new file mode 100644 index 0000000..e69de29 diff --git a/ql/src/security/CWE-319/InsecureWebAppTlsVersion.md b/ql/src/security/CWE-319/InsecureWebAppTlsVersion.md new file mode 100644 index 0000000..c4984e4 --- /dev/null +++ b/ql/src/security/CWE-319/InsecureWebAppTlsVersion.md @@ -0,0 +1,41 @@ +# Insecure TLS version in Web App + +Using an insecure TLS version (TLS 1.0 or TLS 1.1) in an Azure Web App may lead to security vulnerabilities. These older TLS versions have known security weaknesses that can be exploited by attackers. + +## Recommendation + +Configure Web Apps to use at least TLS 1.2 by setting the `minTlsVersion` property to "1.2". + +## Example + +Insecure configuration: + +```bicep +resource webApp 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp' + properties: { + siteConfig: { + minTlsVersion: '1.1' + } + } +} +``` + +Secure configuration: + +```bicep +resource webApp 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp' + properties: { + siteConfig: { + minTlsVersion: '1.2' + } + } +} +``` + +## References + +* [Azure Web App Security Best Practices](https://learn.microsoft.com/en-us/azure/app-service/security-recommendations) +* [Transport Layer Security (TLS) - Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/configure-ssl-bindings#enforce-tls-versions) +* [Common Weakness Enumeration: CWE-319](https://cwe.mitre.org/data/definitions/319.html) diff --git a/ql/src/security/CWE-319/InsecureWebAppTlsVersion.ql b/ql/src/security/CWE-319/InsecureWebAppTlsVersion.ql new file mode 100644 index 0000000..5a220db --- /dev/null +++ b/ql/src/security/CWE-319/InsecureWebAppTlsVersion.ql @@ -0,0 +1,26 @@ +/** + * @name Insecure TLS version in Web App + * @description Using an insecure TLS version in an Azure Web App may lead to security vulnerabilities. + * @kind problem + * @problem.severity error + * @security-severity 8.1 + * @precision high + * @id bicep/insecure-webapp-tls-version + * @tags security + * bicep + * azure + * CWE-319 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::SiteConfig config, StringLiteral tlsVersion +where + config = site.getProperties().getSiteConfig() and + tlsVersion = config.getMinTlsVersion() and + ( + tlsVersion.getValue() = "1.0" or + tlsVersion.getValue() = "1.1" + ) +select site, "Azure Web App configured with insecure TLS version: " + tlsVersion.getValue() diff --git a/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.expected b/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.expected new file mode 100644 index 0000000..7ebb07e --- /dev/null +++ b/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.expected @@ -0,0 +1,2 @@ +| app.bicep:4:1:12:1 | AppService[webAppBad1] | Azure Web App has remote debugging enabled, which should not be used in production environments | +| app.bicep:15:1:24:1 | AppService[webAppBad2] | Azure Web App has remote debugging enabled, which should not be used in production environments | diff --git a/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.qlref b/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.qlref new file mode 100644 index 0000000..23d4c3f --- /dev/null +++ b/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/WebAppRemoteDebugging.qlref @@ -0,0 +1 @@ +security/CWE-306/WebAppRemoteDebugging.ql diff --git a/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/app.bicep b/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/app.bicep new file mode 100644 index 0000000..3f43b16 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-306/WebAppRemoteDebugging/app.bicep @@ -0,0 +1,46 @@ +// Test cases for WebAppRemoteDebugging.ql + +// BAD: Remote debugging is enabled +resource webAppBad1 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-bad1' + location: 'eastus' + properties: { + siteConfig: { + remoteDebuggingEnabled: true + } + } +} + +// BAD: Remote debugging is enabled with a specific version +resource webAppBad2 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-bad2' + location: 'eastus' + properties: { + siteConfig: { + remoteDebuggingEnabled: true + remoteDebuggingVersion: 'VS2019' + } + } +} + +// GOOD: Remote debugging is explicitly disabled +resource webAppGood1 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-good1' + location: 'eastus' + properties: { + siteConfig: { + remoteDebuggingEnabled: false + } + } +} + +// GOOD: Remote debugging is not specified (defaults to disabled) +resource webAppGood2 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-good2' + location: 'eastus' + properties: { + siteConfig: { + // No remoteDebuggingEnabled property + } + } +} diff --git a/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.expected b/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.expected new file mode 100644 index 0000000..8f3383b --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.expected @@ -0,0 +1,2 @@ +| app.bicep:4:1:12:1 | AppService[webAppBad1] | Azure Web App allows insecure FTP protocol: AllAllowed. Use 'FtpsOnly' or 'Disabled' instead. | +| app.bicep:15:1:23:1 | AppService[webAppBad2] | Azure Web App allows insecure FTP protocol: FtpOnly. Use 'FtpsOnly' or 'Disabled' instead. | diff --git a/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.qlref b/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.qlref new file mode 100644 index 0000000..9f3fc72 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/InsecureWebAppFtpsState.qlref @@ -0,0 +1 @@ +security/CWE-319/InsecureWebAppFtpsState.ql diff --git a/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/app.bicep b/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/app.bicep new file mode 100644 index 0000000..5dc5604 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/InsecureWebAppFtpsState/app.bicep @@ -0,0 +1,45 @@ +// Test cases for InsecureWebAppFtpsState.ql + +// BAD: FTP and FTPS both allowed +resource webAppBad1 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-bad1' + location: 'eastus' + properties: { + siteConfig: { + ftpsState: 'AllAllowed' + } + } +} + +// BAD: Only insecure FTP is allowed +resource webAppBad2 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-bad2' + location: 'eastus' + properties: { + siteConfig: { + ftpsState: 'FtpOnly' + } + } +} + +// GOOD: Only secure FTPS is allowed +resource webAppGood1 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-good1' + location: 'eastus' + properties: { + siteConfig: { + ftpsState: 'FtpsOnly' + } + } +} + +// GOOD: FTP and FTPS are both disabled +resource webAppGood2 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-good2' + location: 'eastus' + properties: { + siteConfig: { + ftpsState: 'Disabled' + } + } +} diff --git a/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.expected b/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.expected new file mode 100644 index 0000000..7a3f882 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.expected @@ -0,0 +1,2 @@ +| app.bicep:15:1:23:1 | AppService[webAppBad1] | Azure Web App configured with insecure TLS version: 1.1 | +| app.bicep:26:1:34:1 | AppService[webAppBad2] | Azure Web App configured with insecure TLS version: 1.0 | diff --git a/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.qlref b/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.qlref new file mode 100644 index 0000000..9bb482b --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/InsecureWebAppTlsVersion.qlref @@ -0,0 +1 @@ +security/CWE-319/InsecureWebAppTlsVersion.ql diff --git a/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/app.bicep b/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/app.bicep new file mode 100644 index 0000000..cd52790 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/InsecureWebAppTlsVersion/app.bicep @@ -0,0 +1,47 @@ +// Test cases for InsecureWebAppTlsVersion.ql + +// GOOD: TLS 1.2 is used +resource webAppGood 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-good' + location: 'eastus' + properties: { + siteConfig: { + minTlsVersion: '1.2' + } + } +} + +// BAD: TLS 1.1 is used +resource webAppBad1 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-bad1' + location: 'eastus' + properties: { + siteConfig: { + minTlsVersion: '1.1' + } + } +} + +// BAD: TLS 1.0 is used +resource webAppBad2 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-bad2' + location: 'eastus' + properties: { + siteConfig: { + minTlsVersion: '1.0' + } + } +} + +// GOOD: TLS 1.2 is used with other configurations +resource webAppGoodWithOtherConfig 'Microsoft.Web/sites@2021-03-01' = { + name: 'mywebapp-good-other-config' + location: 'eastus' + properties: { + siteConfig: { + minTlsVersion: '1.2' + ftpsState: 'FtpsOnly' + http20Enabled: true + } + } +} From 0095f4a768559f1f0283cdaec2c0f99a8764f7a2 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Mon, 7 Jul 2025 12:31:00 +0100 Subject: [PATCH 4/4] fix(tests): Update tests --- .../library-tests/frameworks/web/Web.expected | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/ql/test/library-tests/frameworks/web/Web.expected b/ql/test/library-tests/frameworks/web/Web.expected index 13d8e3c..531574a 100644 --- a/ql/test/library-tests/frameworks/web/Web.expected +++ b/ql/test/library-tests/frameworks/web/Web.expected @@ -1,21 +1,21 @@ webApps -| app.bicep:7:1:25:1 | (no string representation) | -| app.bicep:28:1:45:1 | AppService[${appServiceName}-insecure] | -| app.bicep:48:1:70:1 | AppService[${appServiceName}-secure] | -| app.bicep:73:1:84:1 | DeploymentSlot[${MemberExpression}/staging] | -| app.bicep:87:1:100:1 | (no string representation) | -| app.bicep:103:1:115:1 | AppService[${appServiceName}-function] | -| test-sites.bicep:9:1:16:1 | (no string representation) | -| test-sites.bicep:19:1:72:1 | (no string representation) | -| test-sites.bicep:75:1:107:1 | (no string representation) | +| app.bicep:7:1:25:1 | AppServicePlan[appServicePlan] | +| app.bicep:28:1:45:1 | AppService[insecureAppService] | +| app.bicep:48:1:70:1 | AppService[secureAppService] | +| app.bicep:73:1:84:1 | DeploymentSlot[testSlot] | +| app.bicep:87:1:100:1 | StaticWebApp[staticSite] | +| app.bicep:103:1:115:1 | AppService[functionApp] | +| test-sites.bicep:9:1:16:1 | AppServicePlan[appServicePlan] | +| test-sites.bicep:19:1:72:1 | AppService[webApp] | +| test-sites.bicep:75:1:107:1 | AppService[functionApp] | webSites -| app.bicep:28:1:45:1 | AppService[${appServiceName}-insecure] | -| app.bicep:48:1:70:1 | AppService[${appServiceName}-secure] | -| app.bicep:103:1:115:1 | AppService[${appServiceName}-function] | -| test-sites.bicep:19:1:72:1 | (no string representation) | -| test-sites.bicep:75:1:107:1 | (no string representation) | +| app.bicep:28:1:45:1 | AppService[insecureAppService] | +| app.bicep:48:1:70:1 | AppService[secureAppService] | +| app.bicep:103:1:115:1 | AppService[functionApp] | +| test-sites.bicep:19:1:72:1 | AppService[webApp] | +| test-sites.bicep:75:1:107:1 | AppService[functionApp] | webSlots -| app.bicep:73:1:84:1 | DeploymentSlot[${MemberExpression}/staging] | +| app.bicep:73:1:84:1 | DeploymentSlot[testSlot] | webServerFarms -| app.bicep:7:1:25:1 | (no string representation) | -| test-sites.bicep:9:1:16:1 | (no string representation) | +| app.bicep:7:1:25:1 | AppServicePlan[appServicePlan] | +| test-sites.bicep:9:1:16:1 | AppServicePlan[appServicePlan] |