Table of Contents
- Summary
- The assumption this exploits
- What the extension does
- What the current limits actually are
- When it works, when it doesn't
- A worked example
- Why this keeps working in the wild
- Assetnote's wordlists, while you're here
- Takeaway
- Appendix
Summary #
Most WAFs don't inspect the entire body of a request. They inspect the first N kilobytes and wave the rest through, because inspecting every byte of every request at line rate is expensive and operators tune the limit down to keep latency manageable. If the malicious portion of a payload sits past the inspection boundary, the WAF sees a clean prefix and the application sees the full payload.
nowafpls is a Burp extension from Assetnote that automates that idea. It pads a request with junk so the interesting part of the body lands beyond whatever inspection limit the WAF is using. It's a one-trick extension, and the trick doesn't always work, but when it does it converts a "blocked by WAF" finding into a real one with no further effort. It's earned its place in the toolkit, with the caveat that the easy wins have thinned out as the major vendors have raised their defaults. More on that below.
The assumption this exploits #
The defective component is not the application, it's the WAF, and specifically the assumption that a fixed-size inspection window is a complete view of the request. The vendor docs are usually upfront about this: there's a configurable maximum inspection size, anything beyond it is passed through unmodified, and the default is rarely tuned up in production because doing so increases CPU and memory pressure on the appliance, or in the cloud case, the bill.
That leaves a gap between two sets of behaviour. The WAF says "I have inspected this request and it is clean." The application says "I have parsed this request and acted on its full contents." Both statements are true. They're talking about different requests. The first stops at the inspection limit; the second doesn't.
Two assumptions break down here, and both are common.
The first is the assumption that the WAF's verdict applies to the whole request. It doesn't. The verdict applies to whatever the WAF actually parsed, and the limits are an operational compromise the WAF doesn't surface to the upstream.
The second is the assumption that an attacker has to put the payload at the front. They don't. The application's parser doesn't care where in the body a parameter appears, and most body formats multipart, JSON, XML, form-encoded are tolerant of extra fields, comments, or whitespace before the field that matters.
What the extension does #
The extension is small enough to describe in a paragraph. It registers a context-menu item in Burp ("Send to nowafpls" / "Pad request") that takes the currently selected request, asks how much padding to insert, and rewrites the body to include that much filler ahead of (or around) the original payload. It's content-type aware: for JSON it adds a junk key, for XML it adds a comment, for form-encoded bodies it adds a junk parameter, for multipart it adds an extra part. The point is that the padding is syntactically valid in the body format the application expects, so the application parses past it without complaining and reaches the field that carries the real payload.
That's the whole feature set. There's no detection logic, no fingerprinting of which WAF is in front of the target, no auto-tuning of the padding size. You pick a number, the extension inserts that many bytes, you send the request, you look at the response.
What the current limits actually are #
Worth grounding the technique in numbers, because the landscape has shifted enough that older write-ups (including the version of this post I wrote a few years back) are out of date.
For AWS WAF, the body inspection default is now 16 KB for CloudFront, API Gateway, Cognito, App Runner, and Verified Access raised from 8 KB in 2023 for CloudFront and in 2024 for the regional resources. Operators can increase that to 32, 48, or 64 KB for an additional per-request charge. The 8 KB floor is still in force for Application Load Balancer and AppSync, where the limit isn't configurable at all. So a default-config CloudFront target needs more padding than it used to, an ALB-fronted target probably doesn't.
Other vendors cluster around similar numbers. Azure Front Door's default body inspection is in the tens-of-KB range and tunable. Akamai App & API Protector is configurable per-policy. ModSecurity's SecRequestBodyLimit defaults vary by distribution but are commonly set in the 128 KB to 13 MB range, which is wide enough that this technique rarely helps against ModSecurity unless someone has tuned the limit way down. Cloudflare inspects a larger body than most by default and is usually not the place to start with this.
The takeaway: the 8 KB rule of thumb you'll see in older posts is no longer right for AWS-fronted targets, and was never right for everyone else. Treat any specific number as a starting point, not a fact.
When it works, when it doesn't #
It works when the WAF's inspection limit is smaller than the request body and the application doesn't have its own body-size limit kicking in earlier. ALB-fronted apps with default WAF config, on-prem appliances tuned for throughput, and bespoke "WAF-as-a-Lambda" setups all tend to fall in this bucket. The signal is straightforward: a payload that produces a 403 from the WAF starts producing a normal application response (200, 500, an application-level error) once enough padding is added.
It doesn't work when the WAF inspects the full body, when the application rejects oversized requests before the payload is parsed, when the WAF's content-type parsing diverges from the application's (so the padding is interpreted as part of the malicious field, or the field is no longer recognised as the field it was), or when the WAF normalises the body before inspection and your padding gets stripped. CloudFront/WAFv2 with body-inspection raised to 64 KB, ModSecurity with sensible defaults, and most reverse proxies that buffer and re-emit will not be impressed.
This is also not a clever bypass in the cryptographic sense. You aren't defeating the rule, you're arranging for the rule to never see the input. If the operator increases the inspection limit, your payload starts being blocked again with no other change. Treat findings backed by this technique accordingly: they're real, but they live or die at the WAF's config, and a reporting note explaining that is worth more than the finding alone.
A worked example #
Adapted from an engagement, with identifiers changed. Treat it as illustrative the specific numbers depended on the WAF tier and config the target had at the time, and the same target today might respond differently. The mechanism is what generalises, not the byte count.
The target had a search endpoint that took a JSON body and reflected one of its fields into a downstream SQL query. The reflection was unsafe, the parameter was injectable, and the WAF in front of it was blocking the obvious payloads.
Baseline request, payload at the front:
POST /api/search HTTP/1.1
Host: target.example.com
Content-Type: application/json
Content-Length: 53
{"q":"' UNION SELECT username,password FROM users--"}
Response:
HTTP/1.1 403 Forbidden
Server: cloudfront
The WAF caught it. Sending the same payload through nowafpls, with enough padding to push q past the inspection window:
POST /api/search HTTP/1.1
Host: target.example.com
Content-Type: application/json
Content-Length: 24631
{"_pad":"AAAAAAAAAAAAAA...[~24 KB of A's]...AAAA","q":"' UNION SELECT username,password FROM users--"}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{"results":[{"username":"admin","password_hash":"..."}]}
Same payload, same endpoint, same WAF. The only thing that changed is where in the body the malicious field landed. The WAF parsed up to its inspection limit, saw nothing but a long string of A's in a _pad key, and let the request through. The application parsed the full body, ignored _pad (it wasn't a field it cared about), and reached q with the injection intact.
A few practical notes.
Pick your starting padding based on what's in front of the target. If you can fingerprint a CloudFront default, start past 16 KB 24 to 32 KB is a reasonable first try. ALB or AppSync, start past 8 KB 12 to 16 KB. Unknown WAF, sweep: 16, 32, 64, 96 KB. Smaller than the inspection window and you're inside it; much larger than the application's body limit and you trip a different error before the payload lands.
Watch the response code, not the response body. A WAF block is usually a clean 403 or a generic block page. The signal you want is that response transitioning to whatever the application normally returns, even if that's a 500. A 500 means your payload reached the application; iterate from there.
If the request goes from 403 to 413 ("payload too large"), you've crossed an application or proxy limit. Walk the padding back down until you find the window between "WAF blocks" and "server rejects". Sometimes there isn't one and that's the end of the road.
If the verdict doesn't change at any padding size, the WAF is either inspecting the full body or normalising it before inspection. Move on.
Why this keeps working in the wild #
The honest answer is that defenders haven't been forced to fix it, and the fix costs money. AWS's 16 KB default is free; raising it costs per-request fees that scale with traffic. A team that doesn't have a specific incident pushing them to tune the limit up will leave it at the default, and "the WAF didn't inspect bytes 16,385 onward" doesn't appear in any log line.
There's also a structural reason. The inspection limit isn't a bug, it's a tradeoff. Inspecting the full body of every request scales badly, and operators who hit performance ceilings (or billing ones) turn the limit down before they turn it off. A vendor default that worked for a small site quietly stops being adequate as that site grows, and nothing in the system flags the change.
The fix is unglamorous: raise the inspection limit until it covers the largest body your application accepts, or cap the body size at the proxy to whatever the WAF will inspect, whichever is cheaper. Either way, the rule is the same as for every other input-validation problem. The boundary the defender enforces has to match the boundary the application actually parses. If they disagree, the disagreement is the vulnerability.
Assetnote's wordlists, while you're here #
Same group, separate project, also worth a mention. Assetnote's wordlists are kept up to date in a way most public lists aren't, regenerated on the 28th of each month from Commonspeak2 via GitHub Actions. They cover content discovery and subdomain enumeration, and the per-technology lists (specific extensions, frameworks, cloud providers) are the ones I reach for most often. Generic common.txt lists waste a lot of requests on paths that aren't relevant to whatever stack the target is actually running.
Pull the lot with:
wget -r --no-parent -R "index.html*" \
-e robots=off -nH \
https://wordlists-cdn.assetnote.io/data/
If the schedule or the CDN URL has drifted by the time you read this, the wordlists repo README is the source of truth. PRs against the workflow file add new lists on the next monthly run.
Takeaway #
nowafpls isn't a sophisticated bypass. It's a one-line idea put the payload past the inspection window wrapped in just enough Burp plumbing to be ergonomic during testing. It works often enough to be worth trying when a WAF gets in the way, and it fails cleanly enough that you'll know within a handful of requests whether to keep going or move on. The defaults have crept up over the last few years, so the technique requires more padding than it used to and lands less often, but the underlying gap WAF inspects a prefix, application parses the whole thing hasn't gone anywhere.
The more general point is the one the SSRF and command-injection write-ups on this site keep landing on: a control that validates a portion of the input is not a control over the input. Whether that portion is "the part of the URL the regex matched", "the part of the email address that looks email-shaped", or "the first 16 KB of the request body", the same pattern holds. The application sees the whole thing, and the attacker only has to land their payload on the side of the boundary the validator isn't looking at.