---
name: harden-waf
description: "Defensive methodology for web application firewall evasion, where an attacker wraps an injection payload (SQLi, XSS, RCE, path traversal) in encodings the WAF misses but the backend still decodes, or abuses chunked Transfer-Encoding and duplicate length headers to desync the front-end filter from the origin (HTTP request smuggling). Maps to MITRE ATT&CK T1190 and T1027. Keys off WAF logs (AWS WAF, Azure WAF, Cloudflare, OWASP CRS / ModSecurity / Coraza, F5 ASM), CDN and reverse-proxy access logs, and backend error logs. Gives a normalization-first detection approach, a WAF-miss and desync hunt, and rule-tuning and protocol-strictness hardening. Use when hardening or tuning a WAF, closing encoding and double-encoding evasion gaps, defending against request-smuggling desync, raising OWASP CRS paranoia level, triaging requests that pass the WAF but error at the backend, or hunting for signature-evasion against a public web app."
---

# WAF Evasion (Encoding and Chunked-Transfer) - Defensive Methodology

> **Tier 2 - methodology skill.** No bespoke UMBRASEC detection rule ships with this skill yet. It gives the defender's method, the log sources and signals, the hunt, and the hardening, and points to public rule sources. Promotion to Tier 1 (a sourced, tuned UMBRASEC rule) is tracked in [COVERAGE.md](https://github.com/umbrasec-labs/umbra-blue/blob/main/COVERAGE.md). Do not treat the content below as citing a UMBRASEC rule; there is none for this threat yet.

## TL;DR

WAF evasion is almost always an impedance-mismatch problem: the WAF inspects the request in one form and the backend decodes it in another, so a payload the signature misses still reaches the interpreter. Two families dominate. **Encoding evasion** hides a known-bad string behind double or mixed encoding (`%2527`, overlong UTF-8, HTML entities, base64, inline SQL comments, case shuffling) that the WAF reads literally but the app canonicalizes and runs. **Chunked-transfer evasion and request smuggling** use ambiguous `Transfer-Encoding`/`Content-Length` framing so the front-end WAF and the origin disagree on where the request ends, letting a payload ride inside a body the WAF never parsed. The durable defenses are the same two moves: make the WAF normalize to exactly the canonical form the backend will use before it matches, and reject (not forward) any request whose length framing is ambiguous. Tune with OWASP CRS at a measured paranoia level in block mode, and treat the WAF as compensating control, never as a substitute for parameterized queries and output encoding in the app.

## What it looks like, defender view

In WAF and proxy logs an encoding bypass presents as a request that the WAF `allow`ed yet carries a high density of percent-encoding, nested encoding (`%25` immediately followed by two hex digits, so `%252f` decodes to `%2f` then `/`), mixed-case keywords (`SeLeCt`, `uNiOn`), inline comments breaking up tokens (`UN/**/ION`, `SEL/*x*/ECT`), HTML entities (`&#x3c;script`), or a base64 blob in a parameter that the app later decodes. The tell is downstream: the same request that passed the filter produces a backend `500`, a database syntax error, a template error, or a `400`, because the decoded payload reached the interpreter. A chunked or smuggling bypass presents as requests carrying `Transfer-Encoding: chunked` to endpoints that normally send `Content-Length`, requests carrying both headers at once, duplicate or whitespace-obfuscated `Transfer-Encoding` (`Transfer-Encoding : chunked`, `Transfer-Encoding: chunked\r\nTransfer-Encoding: x`), non-zero trailing bytes after the terminating chunk, and the classic desync symptoms: intermittent backend `400 Bad Request`, a response whose body does not match the request that seemingly produced it, or one user receiving another user's response.

## Log sources and signals

Centralize the edge and join it to the origin, because the signal lives in the gap between them:

- **OWASP CRS on ModSecurity / Coraza audit log.** The richest source. Each transaction records matched rule IDs by category (`920xxx` PROTOCOL-ENFORCEMENT, `921xxx` PROTOCOL-ATTACK including `921110` HTTP Request Smuggling and `921120`/`921130` HTTP Response Splitting, `941xxx` XSS, `942xxx` SQLi, `932xxx` RCE), the inbound and outbound `anomaly_score`, the configured `paranoia level`, and the transformations that fired (`t:urlDecodeUni`, `t:htmlEntityDecode`, `t:base64Decode`, `t:cmdLine`, `t:normalisePath`). Protocol-enforcement rules such as invalid content-length, conflicting connection headers, and URL/UTF-8 encoding abuse are exactly the encoding and framing signals.
- **AWS WAF** webACL logs (CloudWatch Logs, S3, or Kinesis): `action`, `terminatingRuleId`, `httpRequest.args`, `httpRequest.uri`, `httpRequest.headers[]` (capture `Transfer-Encoding`, `Content-Length`), and `rateBasedRuleList`.
- **Azure WAF**: `FrontDoorWebApplicationFirewallLog` and Application Gateway `ApplicationGatewayFirewallLog` / `AGWFirewallLogs` in Log Analytics (also under `AzureDiagnostics`), fields `action_s`, `ruleId_s`, `details_matches_s`, `requestUri_s`, `Message`.
- **Cloudflare**: Firewall Events and Logpush with `Action`, `RuleID`, `Source`, `ClientRequestURI`, `EdgeResponseStatus`.
- **F5 BIG-IP ASM / Advanced WAF**: request/violation logs with violation names (`VIOL_EVASION`, `VIOL_HTTP_PROTOCOL`), attack types, and support IDs.
- **CDN and reverse-proxy access logs.** nginx, Envoy, HAProxy. These carry the raw request line, header set, `Transfer-Encoding`, `Content-Length`, and the status the origin actually returned. Envoy exposes `response_flags` (for example `DPE` downstream protocol error) that flag malformed framing.
- **Backend application and error logs.** Framework error logs, database error logs, and the app server status code. A WAF `allow` paired with a backend SQL error, stack trace, or `400`/`500` is the WAF-miss signal that no edge log shows alone.

What normal looks like: a given endpoint uses a stable request shape (method, `Content-Length` or chunked, parameter set), WAF anomaly scores sit well below the blocking threshold, and edge status matches backend status. The anomaly is a request that clears the edge but destabilizes the origin, or whose length framing is self-contradictory.

## Detection approach

Do not hand-write a rule here; a tuned UMBRASEC rule is not yet shipped for this threat. The detection logic is normalization-first and desync-aware:

- **Decode before you match.** Most encoding bypasses are a WAF that matched the raw bytes while the backend canonicalized them. Rely on an engine that applies the full transformation chain (recursive URL decode, Unicode and overlong UTF-8 normalization, HTML entity decode, base64 decode, command-line normalization) before the signature runs. OWASP CRS does this; the defensive work is enabling and ordering those transformations and raising paranoia level until coverage meets your false-positive budget.
- **Alert on the WAF-miss shape.** The highest-value signal is cross-layer: a request the WAF `allow`ed whose paired backend response is a `500`, a database or template error, or a `400`. Join edge logs to origin logs on request id or timestamp plus client IP and path, and treat `edge=allow AND backend_error` as a candidate evasion, not a benign fault.
- **Alert on framing ambiguity directly.** Any request presenting both `Transfer-Encoding` and `Content-Length`, duplicate `Transfer-Encoding` headers, a `Transfer-Encoding` value other than a clean `chunked`, whitespace before the header colon, or chunked encoding to an endpoint that never uses it, is a smuggling candidate regardless of payload. CRS protocol-enforcement (`920xxx`) and protocol-attack (`921xxx`, including `921110`) rules encode this logic.
- **Alert on encoding density.** Flag requests whose argument bytes are disproportionately percent-encoded, that contain `%25` followed by hex (double encoding), mixed-case SQL/script keywords, or comment sequences inside parameter values.

Point to public, maintained rule sources rather than inventing one: the OWASP Core Rule Set at [github.com/coreruleset/coreruleset](https://github.com/coreruleset/coreruleset) (PROTOCOL-ENFORCEMENT and PROTOCOL-ATTACK rule files, plus the paranoia-level and anomaly-scoring docs), SigmaHQ web and proxy rules at [github.com/SigmaHQ/sigma/tree/master/rules/web](https://github.com/SigmaHQ/sigma/tree/master/rules/web) and threat-hunting variants at [github.com/SigmaHQ/sigma/tree/master/rules-threat-hunting/web](https://github.com/SigmaHQ/sigma/tree/master/rules-threat-hunting/web), and the PortSwigger Web Security Academy reference on request smuggling at [portswigger.net/web-security/request-smuggling](https://portswigger.net/web-security/request-smuggling) for the framing taxonomy (CL.TE, TE.CL, TE.TE) a defender needs to recognize.

## Threat hunt

For the patient attacker who lands one crafted request and stops, score-threshold rules will not fire. Hunt instead:

1. **WAF-miss join.** Over a long window, pull every request where the WAF action was `allow` and the origin returned `500`, a SQL/stack/template error, or `400`. Rank by endpoint sensitivity. Each surviving pair is a payload the edge did not understand but the backend tried to execute.
2. **Encoding density ranking.** For the surviving requests, compute the fraction of argument bytes that are `%xx`, flag presence of `%25` + hex (double encoding), mixed-case `union`/`select`/`script`/`eval`, inline `/*...*/` comments, and base64-looking blobs. Review the top of the list by hand.
3. **Chunked and dual-framing sweep.** List all requests carrying `Transfer-Encoding`. Keep those that also carry `Content-Length`, that have duplicate or obfuscated `Transfer-Encoding`, that target endpoints which normally use `Content-Length`, or that left non-zero trailing bytes. Any hit is a smuggling candidate.
4. **Desync symptom sweep.** Look for bursts of backend `400`, Envoy `DPE`/downstream-protocol-error flags, responses whose `Content-Length` or body does not match the request, or reports of one user seeing another's data. These are the observable consequences of a successful desync even when the smuggled payload itself was not logged.
5. **Version and path parity.** Confirm the WAF covers every hostname, path, and API version. Attackers route around WAFs by hitting an origin IP directly, a stale subdomain, or an unprotected `/api/v0` that the ruleset was never applied to.

## Hardening

Shrink the attack surface so detection is the backstop, not the only line. The control codes below are grounded in the UMBRASEC [foundation guide](https://umbrasec.dev/guide/foundation.html) and [detection and response guide](https://umbrasec.dev/guide/detection-and-response.html); a cell is left blank where no single control maps cleanly.

| Control | What it does | Essential Eight | ISO 27001:2022 | NIST CSF 2.0 |
|---|---|---|---|---|
| Normalize before inspect (shared canonicalization) | Make the WAF decode (URL, recursive, Unicode/UTF-8, HTML entity, base64) to the same canonical form the backend uses before matching; closes encoding and double-encoding evasion | - | A.8.8 Technical vulnerabilities | PR.PS |
| Reject ambiguous HTTP framing | Drop requests carrying both `Transfer-Encoding` and `Content-Length`, duplicate or obfuscated `Transfer-Encoding`, or malformed chunk data; prefer HTTP/2 end to end and avoid HTTP/2-to-HTTP/1.1 downgrade at the origin; closes request smuggling | - | A.8.8 Technical vulnerabilities | PR.PS |
| Deploy OWASP CRS in block mode at a tuned paranoia level | Run a maintained managed ruleset with anomaly scoring, raise paranoia level to the limit of your false-positive budget, and fail closed after baselining | Patch applications (ML1+) | A.8.8 Technical vulnerabilities | PR.PS |
| Keep the WAF engine and managed rulesets updated | Apply engine and signature updates promptly so new evasion and smuggling bypasses are covered | Patch applications (ML1+) | A.8.8 Technical vulnerabilities; A.8.19 Software on systems | PR.PS |
| WAF logging and cross-layer monitoring | Log blocks, allows, anomaly scores, and rule IDs, and join edge to origin so WAF-miss and desync become legible | ML2/ML3 event log and monitoring | A.8.15 Logging; A.8.16 Monitoring | DE.CM Continuous monitoring |
| Fix the vulnerability in the app, not only at the edge | Parameterized queries, context-aware output encoding, and input validation so a bypassed payload hits nothing exploitable | - | A.8.8 Technical vulnerabilities | PR.PS |
| Ensure complete WAF coverage of all hosts and versions | Apply the ruleset to every hostname, path, and API version, and block direct-to-origin access so the WAF cannot be routed around | - | A.8.8 Technical vulnerabilities | PR.PS |

## Honest limitations

A WAF is a compensating control, not a fix, and any signature or anomaly model can be bypassed by an encoding the ruleset does not normalize the same way the backend does; novel or app-specific canonicalization quirks will always leave a gap, which is why fixing the underlying SQLi/XSS/RCE in the application is the only durable control. Request smuggling re-emerges wherever a modern HTTP/2 front end downgrades to HTTP/1.1 at the origin, so end-to-end protocol consistency matters more than any smuggling signature. The WAF sees nothing on traffic that bypasses it: direct-to-origin requests, an unprotected subdomain or API version, or a path the ruleset was never applied to. Raising paranoia level buys coverage at the cost of false positives, so block mode must follow baselining, not precede it. The WAF-miss join depends on reliably correlating edge and origin logs; where no shared request id exists the correlation is lossy. A single low-and-slow crafted request will not trip anomaly or velocity thresholds, which is why the hunt and the protocol-strictness controls carry the load the scoring rules cannot.

## ATT&CK and D3FEND

- MITRE ATT&CK: [T1190 Exploit Public-Facing Application](https://attack.mitre.org/techniques/T1190/) (the web exploit the WAF exists to stop); [T1027 Obfuscated Files or Information](https://attack.mitre.org/techniques/T1027/) (payload encoding, double encoding, and obfuscation used to evade the signature). Chunked-transfer desync has no dedicated ATT&CK technique; it is the delivery mechanism that lands the T1190 exploit past the edge control.
- MITRE D3FEND: Inbound Traffic Filtering (D3-ITF) is the WAF itself; Network Traffic Analysis (D3-NTA) and Protocol Metadata Anomaly Detection (D3-PMAD) cover the framing-ambiguity and chunked-transfer signals; Application Configuration Hardening (D3-ACH) covers protocol-strictness, ruleset tuning, and complete coverage. Browse the matrix at [d3fend.mitre.org](https://d3fend.mitre.org/).
- Public rule and guidance sources: [OWASP Core Rule Set](https://github.com/coreruleset/coreruleset) (PROTOCOL-ENFORCEMENT `920xxx`, PROTOCOL-ATTACK `921xxx`, paranoia level and anomaly scoring); [OWASP CRS documentation](https://coreruleset.org/docs/); [SigmaHQ web rules](https://github.com/SigmaHQ/sigma/tree/master/rules/web); [PortSwigger request smuggling reference](https://portswigger.net/web-security/request-smuggling); HTTP/1.1 message framing in [RFC 9112 Section 6](https://datatracker.ietf.org/doc/html/rfc9112#section-6).

**Scope note.** This is a defensive skill. It explains WAF evasion only to the depth needed to detect, tune against, and mitigate it, and ships no offensive tooling, bypass payloads, or smuggling scripts. UMBRASEC publishes defense, not offense.
