Using rate limits and JA4 fingerprinting in AWS WAF
Alex Norman

Alex Norman @alexander_

About: Posting because I have to

Joined:
May 29, 2025

Using rate limits and JA4 fingerprinting in AWS WAF

Publish Date: Jul 24
7 0

Experiencing a DDoS is not a fun experience. Your site is offline, the application servers are getting smashed with a ton of traffic, and you’re trying to keep a level head while analysing what information you have at hand. But with anything in IT, the learning experience will stick with you for a long time and make anything else you do in the future more resilient.

Being prepared and aware of what your options are in a scenario like this are critical to defending against the attacker. A customer of ours originally made me aware that JA3 fingerprints were a field in the AWS WAF when they were experiencing a DDoS attack. From there, we followed the updates to see that AWS have added support for JA4 fingerprints and their usage with rate limit rules, which is awesome.

In this article, I’m going to focus on AWS WAF and how it can help protect your web service from a DDoS. With this guidance, I’ll be assuming you’re already a bit familiar with AWS WAF and will skip over the basics. And as always with cyber security, you want to take a layered approach and apply defenses in many places along the path to give yourself the best chance of surviving an attack.

For some vaguer DDoS information, check out the Kinde blog at Mitigating denial of service attacks with a mix of fingerprinting and rate limits.

Logging

First up is not actually a defense, but a mechanism to help you find information to start defending. You need logs to know what kind of traffic is being sent your way so that you can start defending against it.

In the WAF ACL for your region, go to the Logging and Metrics tab and you’ll be able to add a logging destination. I’ve always found that Cloudwatch Logs are the easiest to deal with since you can use Cloudwatch Insights to quickly analyse the traffic logs for patterns.

Cloudwatch logging

You can then run queries on your WAF events for patterns. An example may to see what the most used JA4 fingerprint is in your particular time window using an Insights query like

fields @timestamp, action, httpRequest.clientIp, ja4Fingerprint, httpRequest.uri
| filter action = "ALLOW"
| stats count(*) as count by ja4Fingerprint | sort by count desc
Enter fullscreen mode Exit fullscreen mode

And let’s say you you’ve found a particular chatty fingerprint, you can then filter on that fingerprint and see what endpoints are being hit.

fields @timestamp, action, httpRequest.clientIp, ja4Fingerprint, httpRequest.uri
| filter action = "ALLOW" and ja4Fingerprint = "t13d191000_9dc949149365_e7c285222651"
| sort @timestamp desc | limit 200
Enter fullscreen mode Exit fullscreen mode

And finally, run the query on a time window like a week ago and validate that this isn’t a false positive. The last thing you want to do is accidentally block legitimate traffic.

JA4 Fingerprints

A great way to find traffic patterns from attackers is to review the JA4 fingerprint being used. AWS WAF supports both JA3 and JA4 fingerprints on all connections where the WAF is on a public facing CloudFront distribution or Application Load Balancer.

JA4 fingerprints are created when a client connects to your service and can be used across different IP addresses, which means it can be possible to track an attacker event if they’ve distributed their attacker nodes.

You can see more information directly from the source at FoxIO’s blog.

Blocking fingerprints

When creating an ACL in AWS WAF, you can use the JA4 fingerprint on the traffic inspection and then choose an action if the fingerprint matches. Note though that you can only put in a single fingerprint at a time. So unlike IP blocking where you can create a list and then block any IP that is matched from that list, you will need to manage the fingerprint blocking a bit more manually. From a management perspective, blocking by fingerprints is useful for the known abusers, but isn’t super practical for large groups of fingerprints.

Blocking JA4 fingerprints

Here’s the JSON from a sample rule blocking a JA4 fingerprint

{
  "Name": "block-ja4-fingerprint-t13d191000_9dc949149365_e7c285222651",
  "Priority": 1,
  "Statement": {
    "ByteMatchStatement": {
      "SearchString": "t13d191000_9dc949149365_e7c285222651",
      "FieldToMatch": {
        "JA4Fingerprint": {
          "FallbackBehavior": "NO_MATCH"
        }
      },
      "TextTransformations": [
        {
          "Priority": 0,
          "Type": "NONE"
        }
      ],
      "PositionalConstraint": "EXACTLY"
    }
  },
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "block-ja4-fingerprint-t13d191000_9dc949149365_e7c285222651"
  }
}
Enter fullscreen mode Exit fullscreen mode

Rate limiting IPs

An IP based rate limit is a great way to prevent a DoS attack since they tend to come from a single or small group of IP addresses. And while a DDoS is distributed, the IP rate limit can help minimise the impact by forcing the attacker to constantly rotate through more IP addresses, which inevitably costs them more money to maintain.

You’ll need to measure the traffic for your application and then try and figure out what a good threshold would be.

I’ll use Cloudwatch Insights for this example again and take a measurement of traffic coming from a single IP in 5 minute blocks. I’m using 5 minutes because that’s one of the default time measurements used in the rate limit rules.

fields @timestamp, action, httpRequest.clientIp, ja4Fingerprint, httpRequest.uri
| filter action = "ALLOW"
| stats count(*) as count by bin(5m), httpRequest.clientIp | sort by count desc
Enter fullscreen mode Exit fullscreen mode

The output will be sorted in 5 minute blocks based on the source IP. Make sure to select a time window where you know there’s lots of traffic. This should help get you started with a baseline of what rate limit to use. From my own personal experience, I tend to do a 2 or 3x multiplier to give customers some wiggle room, but this is totally up to you, your application’s ability to handle traffic, and risk tolerance.

Cloudwatch Insights logging for IP rate limits

Let’s say for this example based on the traffic logs, we want to setup a rate limit of 600 requests per 5 minute period.

Setting up an IP rate limit rule

The evaluation window can be customised down to 1 minute.

Note that the WAF takes up to 20 seconds to complete an evaluation. This is based on personal experience, so might change. This means that an attacker could send 10,000 requests in one blast and it’ll take up to 20 seconds for the rate limit to activate. So there’s a bit of a warm up delay.

Another part I often do is set the response code to 429, which is generally known as the “too many requests” response code. But setting this up may reveal to your attacker that you have a rate limit. On the other hand, if customers are integrating with your web service, then this might give them important information to setup backoff patterns. Again, up to you!

WAF rule action

Rate limiting fingerprints

And finally, there’s rate limits based on the JA4 fingerprint. This is a relatively new feature from AWS released in early 2025. I think this was long overdue since previously you could only setup rate limits on the IP address.

Fingerprints are more widely used than the IP addresses. For example, a particular SDK for your product will have a fingerprint. And let’s say all your customers use that same SDK. This means that you’ll see a lot of traffic for that fingerprint.

Using CloudWatch Insights again, let’s see if we can figure out an appropriate rate limit for the JA4 fingerprint.

fields @timestamp, action, httpRequest.clientIp, ja4Fingerprint, httpRequest.uri
| filter action = "ALLOW"
| stats count(*) as count by bin(5m), ja4Fingerprint | sort by count desc
Enter fullscreen mode Exit fullscreen mode

Notice how the numbers are different for the fingerprints compared to the IPs from earlier on this test web service.

Cloudwatch Insights logging for JA4 fingerprints

For the JA4 fingerprint, I tend to much more liberal with the rate limit. So in this case, I’ll do a 10x multiplier.

When setting up the rate limit, you’ll need to select Custom Keys and then JA4 fingerprint under the Request aggregation key.

Setting up a JA4 fingerprint rate limit WAF rule

Setting up a JA4 fingerprint rate limit WAF rule

Summary

Hope that this post has been useful to those of you using AWS WAF. I was certainly surprised at some of the new features coming from AWS this year since it felt like the WAF product was getting a bit stale compared to some of the other industry cloud providers.

See their announcement post AWS WAF adds JA4 fingerprinting and aggregation on JA3 and JA4 fingerprints for rate-based rules.

And not specifically mentioned in this post, but is certainly something I will be playing around with is their new application layer (L7) DDoS protections for AWS WAF, which is basically a pre-made rule designed to protect against a DDoS without having to be a Shield Advanced customer. Easy (or at least easier) mode for this type of defense.

For more information about JA4 fingerprints, go directly from the source at FoxIO’s blog.

Comments 0 total

    Add comment