|
| 1 | +--- |
| 2 | +title: Upgrade CRS or DRS Ruleset Version |
| 3 | +titleSuffix: Azure Web Application Firewall |
| 4 | +description: Learn how to upgrade CRS or DRS ruleset version on Application Gateway Web Application Firewall. |
| 5 | +author: halkazwini |
| 6 | +ms.author: halkazwini |
| 7 | +ms.service: azure-web-application-firewall |
| 8 | +ms.topic: how-to |
| 9 | +ms.date: 08/28/2025 |
| 10 | +--- |
| 11 | + |
| 12 | +# Upgrade CRS or DRS ruleset version |
| 13 | + |
| 14 | +The Azure-managed **Default Rule Set (DRS)** in Azure Application Gateway Web Application Firewall (WAF) protects web applications against common vulnerabilities and exploits, including the OWASP top 10 attack types. The default rule set also incorporates the Microsoft Threat Intelligence Collection rules. We recommend always running the **latest ruleset version**, which includes the most recent security updates, rule enhancements, and fixes. |
| 15 | + |
| 16 | +The Azure-managed Default Rule Set (DRS) is the latest generation of rulesets in Azure WAF, replacing all previous Core Rule Set (CRS) versions. Among DRS releases, always use the highest available version (for example, DRS 2.2 when released) to ensure you have the most up-to-date protections. |
| 17 | + |
| 18 | +This article provides PowerShell examples for upgrading your Azure WAF policy to DRS 2.1. While the examples reference DRS 2.1 specifically, you should always upgrade to the latest available DRS version to ensure maximum protection. |
| 19 | + |
| 20 | +> [!NOTE] |
| 21 | +> PowerShell snippets are examples only. Replace all placeholders with values from your environment. |
| 22 | +
|
| 23 | +## Prerequisites |
| 24 | + |
| 25 | +- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F). |
| 26 | + |
| 27 | +- An existing Azure WAF policy with a Core Rule Set (CRS) or Default Rule Set (DRS) applied. If you don't have a WAF policy yet, see [Create Web Application Firewall policies for Application Gateway](create-waf-policy-ag.md). |
| 28 | + |
| 29 | +- Latest version of [Azure PowerShell installed locally](/powershell/azure/install-azure-powershell). This article requires the [Az.Network Module](/powershell/module/az.network). |
| 30 | + |
| 31 | +## Key considerations when upgrading |
| 32 | + |
| 33 | +When upgrading your Azure WAF ruleset version, make sure to: |
| 34 | + |
| 35 | +- **Preserve existing customizations**: carry over your rule action overrides, rule status (enabled/disabled) overrides, and exclusions. |
| 36 | + |
| 37 | +- **Validate new rules safely**: ensure newly added rules are initially set to **log mode**, so you can monitor their impact and fine-tune them before enabling blocking. |
| 38 | + |
| 39 | +## Prepare your environment and variables |
| 40 | + |
| 41 | +1. Set context of your selected subscription, resource group, and Azure WAF policy. |
| 42 | + |
| 43 | + ```powershell |
| 44 | + Import-Module Az.Network |
| 45 | + Set-AzContext -SubscriptionId "<subscription_id>" |
| 46 | + $resourceGroupName = "<resource_group>" |
| 47 | + $wafPolicyName = "<policy_name>" |
| 48 | + ``` |
| 49 | +1. Get the WAF policy object and retrieve its definitions. |
| 50 | +
|
| 51 | + ```powershell |
| 52 | + $wafPolicy = Get-AzApplicationGatewayFirewallPolicy ` |
| 53 | + -Name $wafPolicyName ` |
| 54 | + -ResourceGroupName $resourceGroupName |
| 55 | + $currentExclusions = $wafPolicy.ManagedRules.Exclusions |
| 56 | + $currentManagedRuleset = $wafPolicy.ManagedRules.ManagedRuleSets |
| 57 | + | Where-Object { $_.RuleSetType -eq "OWASP" } |
| 58 | + $currentVersion = $currentManagedRuleset.RuleSetVersion |
| 59 | + ``` |
| 60 | +
|
| 61 | +## Preserve existing customizations |
| 62 | +
|
| 63 | +1. Don't copy overrides or exclusions that apply to rules removed in **DRS 2.1**. The following function checks if a rule has been removed: |
| 64 | +
|
| 65 | + ```powershell |
| 66 | + function Test-RuleIsRemovedFromDRS21 { |
| 67 | + param ( |
| 68 | + [string]$RuleId, |
| 69 | + [string]$CurrentRulesetVersion |
| 70 | + ) |
| 71 | + $removedRulesByCrsVersion = @{ |
| 72 | + "3.0" = @( "200004", "913100", "913101", "913102", "913110", "913120", "920130", "920140", "920250", "921100", "800100", "800110", "800111", "800112", "800113" ) |
| 73 | + "3.1" = @( "200004", "913100", "913101", "913102", "913110", "913120", "920130", "920140", "920250", "800100", "800110", "800111", "800112", "800113", "800114" ) |
| 74 | + "3.2" = @( "200004", "913100", "913101", "913102", "913110", "913120", "920250", "800100", "800110", "800111", "800112", "800113", "800114" ) |
| 75 | + } |
| 76 | + # If the version isn't known, assume rule has not been removed |
| 77 | + if (-not $removedRulesByCrsVersion.ContainsKey($CurrentRulesetVersion)) { |
| 78 | + return $false |
| 79 | + } |
| 80 | + return $removedRulesByCrsVersion[$CurrentRulesetVersion] -contains $RuleId } |
| 81 | + ``` |
| 82 | +
|
| 83 | +1. When creating new override objects, use the **DRS 2.1 group names**. The following function maps legacy CRS group names to DRS 2.1 |
| 84 | +groups: |
| 85 | +
|
| 86 | + ```powershell |
| 87 | + function Get-DrsRuleGroupName { |
| 88 | + param ( |
| 89 | + [Parameter(Mandatory = $true)] |
| 90 | + [string]$SourceGroupName ) |
| 91 | + $groupMap = @{ |
| 92 | + "REQUEST-930-APPLICATION-ATTACK-LFI" = "LFI" |
| 93 | + "REQUEST-931-APPLICATION-ATTACK-RFI" = "RFI" |
| 94 | + "REQUEST-932-APPLICATION-ATTACK-RCE" = "RCE" |
| 95 | + "REQUEST-933-APPLICATION-ATTACK-PHP" = "PHP" |
| 96 | + "REQUEST-941-APPLICATION-ATTACK-XSS" = "XSS" |
| 97 | + "REQUEST-942-APPLICATION-ATTACK-SQLI" = "SQLI" |
| 98 | + "REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION" = "FIX" |
| 99 | + "REQUEST-944-APPLICATION-ATTACK-JAVA" = "JAVA" |
| 100 | + "REQUEST-921-PROTOCOL-ATTACK" = "PROTOCOL-ATTACK" |
| 101 | + "REQUEST-911-METHOD-ENFORCEMENT" = "METHOD-ENFORCEMENT" |
| 102 | + "REQUEST-920-PROTOCOL-ENFORCEMENT" = "PROTOCOL-ENFORCEMENT" |
| 103 | + "REQUEST-913-SCANNER-DETECTION" = $null # No direct mapping |
| 104 | + "Known-CVEs" = "MS-ThreatIntel-CVEs" |
| 105 | + "General" = "General" |
| 106 | + } |
| 107 | + if ($groupMap.ContainsKey($SourceGroupName)) { |
| 108 | + return $groupMap[$SourceGroupName] |
| 109 | + } else { |
| 110 | + return $SourceGroupName # No known mapping |
| 111 | + } |
| 112 | + } |
| 113 | + ``` |
| 114 | +
|
| 115 | +1. Use the following PowerShell code to define the rules’ overrides, duplicating overrides from existing ruleset version: |
| 116 | +
|
| 117 | + ```powershell |
| 118 | + $groupOverrides = @() |
| 119 | + foreach ($group in $currentManagedRuleset.RuleGroupOverrides) { |
| 120 | + $mappedGroupName = Get-DrsRuleGroupName $group.RuleGroupName |
| 121 | + foreach ($existingRule in $group.Rules) { |
| 122 | + if (-not (Test-RuleIsRemovedFromDRS21 $existingRule.RuleId $currentVersion)) |
| 123 | + { |
| 124 | + `$existingGroup = $groupOverrides | |
| 125 | + Where-Object { $_.RuleGroupName -eq $mappedGroupName } |
| 126 | + if ($existingGroup) { |
| 127 | + if (-not ($existingGroup.Rules | |
| 128 | + Where-Object { $_.RuleId -eq $existingRule.RuleId })) { |
| 129 | + $existingGroup.Rules.Add($existingRule) } } |
| 130 | + else { |
| 131 | + $newGroup = New-AzApplicationGatewayFirewallPolicyManagedRuleGroupOverride ` -RuleGroupName $mappedGroupName ` -Rule @($existingRule) $groupOverrides += $newGroup } } } } |
| 132 | +
|
| 133 | + ``` |
| 134 | +
|
| 135 | +1. Use the following PowerShell code to duplicate your existing exclusions and apply them on DRS 2.1: |
| 136 | +
|
| 137 | + ```powershell |
| 138 | + # Create new exclusion objects |
| 139 | + $newRuleSetExclusions = @() |
| 140 | + |
| 141 | + if ($currentExclusions -ne $null -and $currentExclusions.Count -gt 0) |
| 142 | + { |
| 143 | + foreach ($exclusion in $currentExclusions) { |
| 144 | + $newExclusion = New-AzApplicationGatewayFirewallPolicyExclusion ` |
| 145 | + -MatchVariable $exclusion.MatchVariable ` |
| 146 | + -SelectorMatchOperator $exclusion.SelectorMatchOperator ` |
| 147 | + -Selector $exclusion.Selector |
| 148 | + |
| 149 | + # Migrate scopes: RuleSet, RuleGroup, or individual Rules |
| 150 | + if ($exclusion.ExclusionManagedRuleSets) { |
| 151 | + foreach ($scope in $exclusion.ExclusionManagedRuleSets) { |
| 152 | + # Create RuleGroup objects from existing RuleGroups |
| 153 | + $ruleGroups = @() |
| 154 | + foreach ($group in $scope.RuleGroups) { |
| 155 | + $drsGroupName = Get-DrsRuleGroupName $group.RuleGroupName |
| 156 | + if ($drsGroupName) |
| 157 | + { |
| 158 | + $exclusionRules = @() |
| 159 | + foreach ($rule in $group.Rules) |
| 160 | + { |
| 161 | + if (-not (Test-RuleIsRemovedFromDRS21 $rule.RuleId "3.2")) |
| 162 | + { |
| 163 | + $exclusionRules += New-AzApplicationGatewayFirewallPolicyExclusionManagedRule ` |
| 164 | + -RuleId $rule.RuleId |
| 165 | + } |
| 166 | + } |
| 167 | + if ($exclusionRules -ne $null -and $exclusionRules.Count -gt 0) |
| 168 | + { |
| 169 | + $ruleGroups += New-AzApplicationGatewayFirewallPolicyExclusionManagedRuleGroup ` |
| 170 | + -Name $drsGroupName ` |
| 171 | + -Rule $exclusionRules |
| 172 | + } else { |
| 173 | + $ruleGroups += New-AzApplicationGatewayFirewallPolicyExclusionManagedRuleGroup ` |
| 174 | + -Name $drsGroupName |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + # Create the ManagedRuleSet scope object with the updated RuleGroups |
| 180 | + if ($ruleGroups.Count -gt 0) { |
| 181 | + $newRuleSetScope = New-AzApplicationGatewayFirewallPolicyExclusionManagedRuleSet ` |
| 182 | + -Type "Microsoft_DefaultRuleSet" ` |
| 183 | + -Version "2.1" ` |
| 184 | + -RuleGroup $ruleGroups |
| 185 | + } |
| 186 | + |
| 187 | + # Add to the new exclusion object |
| 188 | + $newExclusion.ExclusionManagedRuleSets += $newRuleSetScope |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + if (-not $newExclusion.ExclusionManagedRuleSets) |
| 193 | + { |
| 194 | + $newExclusion.ExclusionManagedRuleSets = @() |
| 195 | + } |
| 196 | + |
| 197 | + $newRuleSetExclusions += $newExclusion |
| 198 | + } |
| 199 | + } |
| 200 | + ``` |
| 201 | +
|
| 202 | +## Validate new rules safely |
| 203 | +
|
| 204 | +When you upgrade, new DRS 2.1 rules are active by default. If your WAF is in ***Prevention*** mode, set new rules to ***log*** mode first. The *log* mode allows you to review logs before enabling blocking. |
| 205 | +
|
| 206 | +1. The following PowerShell definitions are for rules introduced in DRS 2.1 compared to each CRS version: |
| 207 | +
|
| 208 | + ```powershell |
| 209 | + # Added in DRS 2.1 compared to CRS 3.0 |
| 210 | + $rulesAddedInThisVersionByGroup = @{ |
| 211 | + "General" = @("200002", "200003") |
| 212 | + "PROTOCOL-ENFORCEMENT" = @("920121", "920171", "920181", "920341", "920470", "920480", "920500") |
| 213 | + "PROTOCOL-ATTACK" = @("921190", "921200") |
| 214 | + "RCE" = @("932180") |
| 215 | + "PHP" = @("933200", "933210") |
| 216 | + "NODEJS" = @("934100") |
| 217 | + "XSS" = @("941101", "941360", "941370", "941380") |
| 218 | + "SQLI" = @("942361", "942470", "942480", "942500", "942510") |
| 219 | + "JAVA" = @("944100", "944110", "944120", "944130", "944200", "944210", "944240", "944250") |
| 220 | + "MS-ThreatIntel-WebShells" = @("99005002", "99005003", "99005004", "99005005", "99005006") |
| 221 | + "MS-ThreatIntel-AppSec" = @("99030001", "99030002") |
| 222 | + "MS-ThreatIntel-SQLI" = @("99031001", "99031002", "99031003", "99031004") |
| 223 | + "MS-ThreatIntel-CVEs" = @( "99001001","99001002","99001003","99001004","99001005","99001006", "99001007","99001008","99001009","99001010","99001011","99001012", "99001013","99001014","99001015","99001016","99001017" ) |
| 224 | + } |
| 225 | + ``` |
| 226 | +
|
| 227 | + ```powershell |
| 228 | + # Added in DRS 2.1 compared to CRS 3.1 |
| 229 | + $rulesAddedInThisVersionByGroup = @{ |
| 230 | + "General" = @("200002", "200003") |
| 231 | + "PROTOCOL-ENFORCEMENT" = @("920181", "920500") |
| 232 | + "PROTOCOL-ATTACK" = @("921190", "921200") |
| 233 | + "PHP" = @("933200", "933210") |
| 234 | + "NODEJS" = @("934100") |
| 235 | + "XSS" = @("941360", "941370", "941380") |
| 236 | + "SQLI" = @("942500", "942510") |
| 237 | + "MS-ThreatIntel-WebShells" = @("99005002", "99005003", "99005004", "99005005", "99005006") |
| 238 | + "MS-ThreatIntel-AppSec" = @("99030001", "99030002") |
| 239 | + "MS-ThreatIntel-SQLI" = @("99031001", "99031002", "99031003", "99031004") "MS-ThreatIntel-CVEs" = @( "99001001","99001002","99001003","99001004","99001005","99001006", "99001007","99001008","99001009","99001010","99001011","99001012", "99001013","99001014","99001015","99001016","99001017" ) |
| 240 | + } |
| 241 | + ``` |
| 242 | +
|
| 243 | + ```powershell |
| 244 | + # Added in DRS 2.1 compared to CRS 3.2 |
| 245 | + $rulesAddedInThisVersionByGroup = @{ |
| 246 | + "General" = @("200002", "200003") |
| 247 | + "PROTOCOL-ENFORCEMENT" = @("920181", "920500") |
| 248 | + "PROTOCOL-ATTACK" = @("921190", "921200") |
| 249 | + "PHP" = @("933200", "933210") |
| 250 | + "NODEJS" = @("934100") |
| 251 | + "XSS" = @("941360", "941370", "941380") |
| 252 | + "SQLI" = @("942100", "942500", "942510") |
| 253 | + "MS-ThreatIntel-WebShells" = @("99005002", "99005003", "99005004", "99005005", "99005006") |
| 254 | + "MS-ThreatIntel-AppSec" = @("99030001", "99030002") |
| 255 | + "MS-ThreatIntel-SQLI" = @("99031001", "99031002", "99031003", "99031004") |
| 256 | + "MS-ThreatIntel-CVEs" = @( "99001001","99001002","99001003","99001004","99001005","99001006", "99001007","99001008","99001009","99001010","99001011","99001012", "99001013","99001014","99001015","99001016","99001017" ) |
| 257 | + } |
| 258 | + |
| 259 | + ``` |
| 260 | +
|
| 261 | +1. Use the following PowerShell code to add new rule overrides to the existing `$groupOverrides` object defined previously: |
| 262 | +
|
| 263 | + ```powershell |
| 264 | + foreach ($groupName in $rulesAddedInDRS21.Keys) { |
| 265 | + $ruleOverrides = @() |
| 266 | + foreach ($ruleId in $rulesAddedInDRS21[$groupName]) { |
| 267 | + $alreadyExists = $existingOverrides | |
| 268 | + Where-Object { $_.RuleId -eq $ruleId } |
| 269 | + if (-not $alreadyExists) { |
| 270 | + $ruleOverrides += New-AzApplicationGatewayFirewallPolicyManagedRuleOverride ` |
| 271 | + -RuleId $ruleId ` |
| 272 | + -Action "Log" ` |
| 273 | + -State "Enabled" |
| 274 | + } |
| 275 | + } # Only create group override if we added rules to it |
| 276 | + if ($ruleOverrides.Count -gt 0) { |
| 277 | + $groupOverrides += New-AzApplicationGatewayFirewallPolicyManagedRuleGroupOverride ` |
| 278 | + -RuleGroupName $groupName ` |
| 279 | + -Rule $ruleOverrides } |
| 280 | + } |
| 281 | + ``` |
| 282 | +
|
| 283 | +## Apply customizations and upgrade |
| 284 | +
|
| 285 | +Define your updated Azure WAF policy object, incorporating the duplicated and updated rule overrides and exclusions: |
| 286 | +
|
| 287 | +```powershell |
| 288 | +$managedRuleSet = New-AzApplicationGatewayFirewallPolicyManagedRuleSet ` |
| 289 | + -RuleSetType "Microsoft_DefaultRuleSet" ` |
| 290 | + -RuleSetVersion "2.1" ` |
| 291 | + -RuleGroupOverride $groupOverrides |
| 292 | +for ($i = 0; $i -lt $wafPolicy.ManagedRules.ManagedRuleSets.Count; $i++) { |
| 293 | + if ($wafPolicy.ManagedRules.ManagedRuleSets[$i].RuleSetType -eq "OWASP") { |
| 294 | + $wafPolicy.ManagedRules.ManagedRuleSets[$i] = $managedRuleSet |
| 295 | + break |
| 296 | + } |
| 297 | +} |
| 298 | +# Assign to policy |
| 299 | +if ($newRuleSetExclusions) { |
| 300 | + $wafPolicy.ManagedRules.Exclusions = $currentExclusions + $newRuleSetExclusions |
| 301 | +} |
| 302 | +# Apply the updated WAF policy |
| 303 | +Set-AzApplicationGatewayFirewallPolicy -InputObject $wafPolicy |
| 304 | +``` |
| 305 | + |
| 306 | +## Related content |
| 307 | + |
| 308 | +- [Web Application Firewall DRS and CRS rule groups and rules](/azure/web-application-firewall/ag/application-gateway-crs-rulegroups-rules) |
| 309 | +- [Customize Web Application Firewall rules using the Azure portal](/azure/web-application-firewall/ag/application-gateway-customize-waf-rules-portal) |
0 commit comments