Table of Contents
- 1. Introduction
- 2. Example 1: Limit requests based on unique user identifier
- 3. Example 2: Allow requests from humans when there are numerous requests to a specific page
- 4. Example 3: Block when there are numerous requests to a specific page
- 5. Example 4: Limit requests based on traits like no user-agents
- 6. Conclusion
1. Introduction
Enhancements on rate-based rules conditions have been released from AWS on May 16th, 2023.
AWS WAF enhances rate-based rules to support request headers and composite keys
Previously, rate-based rules could only track requests based on IP addresses.
You could scope down the condition to track the rate by using the scope-down statement, but you could not block requests if they came from different IP addresses.
With this release, you can configure to track requests based on criteria other than IP address.
In this blog post, we will take a look at some examples.
For more details on rate-based rules, please refer to the blog post below.
How to use rate-based rule
2. Example 1: Limit requests based on unique user identifier
A new option to aggregate the requests called [Custom Key] has been added.
By using this option, you can specify values in headers or queries.
If there are unique identifiers for each user, you can block requests from multiple IP addresses that contain the same ID in the header.
This is useful when a user is given the same unique ID and wants to block a large number of requests from the specific user.
{ "Name": "ratebased-id", "Priority": 0, "Statement": { "RateBasedStatement": { "Limit": 100, "AggregateKeyType": "CUSTOM_KEYS", "CustomKeys": [ { "Header": { "Name": "ID", "TextTransformations": [ { "Priority": 0, "Type": "NONE" } ] } } ] } }, "Action": { "Block": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "ratebased-id" } }
As stated above, the ID in the header is the only aggregated key, so the request will match the condition even if the requesters' IP addresses are different.
If you want to aggregate based on the same ID and IP address, you can choose [IP address] as the second key using [Request aggregation key 2].
In this case, the request will not match the rule if the ID is the same but the IP addresses are different.
Please keep in mind that [IP address] can only be used as the second key, as stated on the page when configuring the first key.
3. Example 2: Allow requests from humans when there are numerous requests to a specific page
You can also use the rate-based rule when there are numerous requests from bots to a specific URI.
With the new [Count all] option, you can track all requests that match the specified condition using the scope-down statement.
For example, when there are large amounts of requests from bots to the contact form, you can use the CAPTCHA action to block the bots but allow legitimate users to access the contact form.
You can create a rule like below to show the CAPTCHA test when there are 100 requests in 5 minutes to pages under [/contact].
{ "Name": "ratebased-uri-captcha", "Priority": 0, "Statement": { "RateBasedStatement": { "Limit": 100, "AggregateKeyType": "CONSTANT", "ScopeDownStatement": { "ByteMatchStatement": { "SearchString": "/contact", "FieldToMatch": { "UriPath": {} }, "TextTransformations": [ { "Priority": 0, "Type": "NONE" } ], "PositionalConstraint": "STARTS_WITH" } } } }, "Action": { "Captcha": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "ratebased-uri-captcha" }, "CaptchaConfig": { "ImmunityTimeProperty": { "ImmunityTime": 120 } } }
This rule traces all requests that match the condition. In this case, requests from any IP addresses will be subjected to the CAPTCHA test once the threshold for the pages under [/contact] exceeds the specified limit until the effects from rate-based rules expire.
If a user passes the CAPTCHA test, they will be allowed to access the page without taking another CAPTCHA test for the time specified under [Immunity time] on the rule.
Note that [IP address] cannot be included as a key to trace requests like [Custom Key] in Example 1 if you use the [Count all] option.
Please refer to the blog post below for using rate-based rules and CAPTCHA action.
Protect from bot traffic using AWS WAF CAPTCHA
4. Example 3: Block when there are numerous requests to a specific page
Using the [Count all] option we've looked at in the previous example, you can also block large requests by using the BLOCK action.
This will be beneficial if you want to simply block large numbers of requests to certain URI.
In this case, you can create a similar rule to Example 2 and use action BLOCK instead.
Using the BLOCK action will block all requests to the specified URI until the effects of rate-based rules are over, unlike the CAPTCHA action. To avoid unintentionally stopping the service, you may need to consider the conditions beforehand.
You could select COUNT action first to see what kind of requests would match the rule and then change the action to BLOCK once you are satisfied with the results.
5. Example 4: Limit requests based on traits like no user-agents
We'll take a look at a rule to detect numerous requests similar to DDoS attacks that we've observed before.
In this scenario, the requests came from multiple IP addresses.
We looked at some of the requests and found that the requests had one thing in common: the requests didn't contain a User-Agent header.
Let's consider a rate-based rule using the specific trait we found.
*Blocking requests without a User-Agent header would also be effective as a countermeasure.
If there are many requests from specific User-Agents, a rule like below can track the target condition.
Keep in mind that adding [IP address] as a key will change how the rule behaves when using [Custom Key], just like Example 1.
{ "Name": "ratebased-UA-value", "Priority": 1, "Statement": { "RateBasedStatement": { "Limit": 100, "AggregateKeyType": "CUSTOM_KEYS", "CustomKeys": [ { "Header": { "Name": "User-Agent", "TextTransformations": [ { "Priority": 0, "Type": "NONE" } ] } } ] } }, "Action": { "Block": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "ratebased-UA-value" } }
If requests do not contain a User-Agent header at all, use the [Count all] option and NOT statement in the scope-down statement.
{ "Name": "ratebased-no-UA", "Priority": 0, "Statement": { "RateBasedStatement": { "Limit": 100, "AggregateKeyType": "CONSTANT", "ScopeDownStatement": { "NotStatement": { "Statement": { "SizeConstraintStatement": { "FieldToMatch": { "SingleHeader": { "Name": "user-agent" } }, "ComparisonOperator": "GT", "Size": 0, "TextTransformations": [ { "Priority": 0, "Type": "NONE" } ] } } } } } }, "Action": { "Block": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "ratebased-no-UA" } }
6. Conclusion
With the enhancements to specify detailed options in rate-based rules, we can now create rules for various cases.
Although rate-based rules may not be the countermeasure for DDoS attacks, we can have higher expectations to mitigate DDoS attacks since they can block requests from multiple IP addresses as long as the specified condition matches the requests.
On the other hand, the conditions have become a lot more complicated, making rule creation more challenging than before.
A rule may unexpectedly block legitimate requests depending on the specified conditions, so we recommend you to test the rules first when creating a rate-based rule with complicated conditions.