AWS WAF Challenge : bloquer les bots avant qu’ils n’atteignent l’application
Quand on m’a appelé, l’attaque durait depuis environ une semaine. Elle visait la page de connexion d’une application historique qui générait son HTML côté serveur. Les requêtes se comptaient en millions et provenaient d’un très grand nombre d’adresses IP, ce qui rendait un blocage par IP peu efficace. Contrairement à d’autres campagnes que j’avais rencontrées, l’assaillant faisait également…
When the call came, the attack had been ongoing for about a week. It targeted the login page of a legacy application that generated its HTML server-side. The queries amounted to millions and originated from a vast number of IP addresses, making IP-based blocking ineffective. Unlike other campaigns I had seen, the attacker also varied their JA3 and JA4 fingerprints.
Even a coarse-grained rate limiting based on these signals could only provide partial protection. A few days later, a similar attack struck another application belonging to the same client. This time, it was a single-page application (SPA) that called a JSON API to create accounts. These two incidents allowed me to test both integration modes of AWS WAF's Challenge feature: the challenge sent directly by the WAF for an HTML page, and the challenge resolved upstream via challenge.js, then transmitted to an API called with fetch.
Why place the challenge at the infrastructure layer? When the client called me seven days later, it was because their team had first attempted to handle the attack at the application level using Cloudflare Turnstile integration. The integration was robust, with a PrestaShop module managing keys, activation, and tenant-specific configurations.
Upon submission, the application retrieved the token from the form and validated it internally with Cloudflare: $turnstileToken = Tools :: getValue ( cf-turnstile-response ); if ( empty ( $turnstileToken )) { $turnstileValid = false ; } elseif ( ! $this - verifyTurnstileToken ( $turnstileToken )) { $turnstileValid = false ; } The validation process then required a server call to siteverify: $response = Tools :: file_get_contents ( https://challenges.cloudflare.com/turnstile/v0/siteverify , false , stream_context_create ([ http = [ method = POST , header = Content-Type: application/x-www-form-urlencoded , content = http_build_query ([ secret = $secretKey , response = $token , remoteip = Tools :: getRemoteAddr (), ]), timeout = 5 , ], ]) ); This approach worked, but only partially.
A significant proportion of traffic still received valid tokens. We observed tokens generated from a group of Dutch IP addresses, then presented to our infrastructure from Spain, indicating that the attacker had industrialized the acquisition and distribution of tokens. The token proved that a challenge was solved, but it did not constitute a durable identity or a strict link with the originating IP address.
Most importantly, this protection had a significant drawback: before rejecting a request, the entire chain had to be mobilized: the CDN, load balancer, web server, PHP, framework, and validation code. Even when authentication failed, a considerable portion of technical cost had already been incurred, impacting application performance and billing with millions of requests.
With a Challenge rule placed in AWS WAF (which was already used by the application), the query without a valid token is stopped at the edge. It does not consume a PHP worker, database connection, or compute capacity. This was exactly what we needed in the face of several million requests. There is also a maintenance benefit to moving the control.
Any validation managed by the application inevitably involves code in the form, controller, secret management, remote calls, and error messages. With a challenge managed by WAF, the backend is unaware of its existence. It only receives requests that have passed the control. The backend provider of the application challenge (Cloudflare vs AWS) is not the subject of this comparison.
The same architectural distinction exists with any mechanism validated at the backend, regardless of the provider.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — it may contain errors, so check the original before relying on it.