CVE-2025-29914

The Double-Slash Deception: Bypassing Coraza WAF with RFC Compliance

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 6, 2026·6 min read

Executive Summary (TL;DR)

Coraza WAF treated URIs starting with `//` as protocol-relative URLs, interpreting the first path segment as a hostname. This caused the WAF to effectively 'delete' the first directory from the path it inspected (e.g., `//admin` became a host named `admin` with an empty path), allowing attackers to bypass Access Control Lists (ACLs) while the backend server still normalized and served the sensitive path.

A parser logic discrepancy in OWASP Coraza WAF allows attackers to bypass path-based security rules using double slashes in the URI, exploiting Go's standard URL parsing behavior.

The Hook: When the Bouncer is Too Literal

Web Application Firewalls (WAFs) are supposed to be the bouncers of the internet. They stand at the velvet rope, checking IDs (requests) against a list of known troublemakers (signatures). OWASP Coraza is the cool new bouncer in the Go ecosystem—a ModSecurity-compatible library designed to bring enterprise-grade filtering to modern Go applications. It’s robust, fast, and generally trustworthy. But as we've learned time and time again in InfoSec, complexity is the enemy of security, and sometimes, being technically correct is the worst kind of wrong.

CVE-2025-29914 isn't a buffer overflow. It isn't a fancy ROP chain. It is a classic case of Parser Differential—a fancy term for when two systems look at the exact same data but see two completely different things. In this case, the Coraza WAF looked at a URL and saw a harmless request to a root directory, while the backend server saw a request for a sensitive admin panel.

This vulnerability is a perfect example of "The Impedance Mismatch" between generic programming libraries and specific security implementations. The developers trusted the standard library to parse a URL, assuming it would behave like a web server. It didn't. It behaved like an RFC-compliant URL parser, and that difference blew the doors wide open for anyone who knows how to type two slashes instead of one.

The Flaw: A Tale of Two Parsers

To understand this bug, we have to look at how Go handles URLs. The root cause lies in the distinction between a generic URI and an HTTP Request-URI. Coraza was using Go's net/url package, specifically the url.Parse() function, to deconstruct incoming requests. The developers assumed that url.Parse("/admin/users") and url.Parse("//admin/users") would basically result in the same path. They were wrong.

According to RFC 3986 (Uniform Resource Identifier Generic Syntax), a string starting with // is a Network-Path Reference. In this context, the parser expects the syntax //authority/path. The segment immediately following the double slash is interpreted as the Authority (or Host), not the path.

So, when an attacker sends GET //etc/passwd, Coraza's internal logic calls url.Parse("//etc/passwd"). The parser dutifully reports:

  • Host: etc
  • Path: /passwd

The WAF then populates its REQUEST_FILENAME variable with /passwd. If you have a rule saying "Block everything starting with /etc/", the WAF looks at /passwd, shrugs, and lets it through. Meanwhile, the backend web server (Nginx, Apache, Caddy) receives the raw request line, normalizes //etc/passwd to /etc/passwd, and happily serves the file. The WAF was tricked by its own adherence to the spec.

The Code: The Smoking Gun

The vulnerability lived in internal/corazawaf/transaction.go. It's a subtle one-liner that looks completely innocent during a code review unless you have the Go documentation memorized. Here is the vulnerable logic from the pre-patch version:

// BEFORE: Vulnerable Code in transaction.go
func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string) {
    // ... (fragment stripping logic) ...
    
    // The fatal mistake: Using the generic parser
    parsedURL, err := url.Parse(uri) 
    
    if err != nil {
        return
    }
    
    // The WAF uses parsedURL.Path, which might be missing the first segment
    tx.variables.requestFilename.Set(parsedURL.Path)
}

The fix, introduced in commit 4722c9ad0d502abd56b8d6733c6b47eb4111742d, was to switch the parser. Go provides url.ParseRequestURI, which is specifically designed to handle the Request-Line of an HTTP request. It doesn't treat // as an authority delimiter in the same way, preventing the confusion.

// AFTER: Patched Code
func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string) {
    // ... 
    
    // The Fix: Using the specific Request URI parser
    parsedURL, err := url.ParseRequestURI(uri)
    
    // ...
}

This seemingly minor change aligns the WAF's perception of the URL with how an HTTP server actually interprets it.

The Exploit: Bypassing the Gatekeeper

Let's construct a practical attack scenario. Imagine a corporate application protected by Coraza. The security team has configured a rule to protect the administration interface.

The Defense Rule: SecRule REQUEST_FILENAME "@beginsWith /admin" "id:1001,phase:1,deny,status:403"

The Attack Chain:

  1. Reconnaissance: We identify that /admin returns a 403 Forbidden. The WAF is doing its job.
  2. The Bypass Attempt: We construct a request using the double-slash technique: GET //admin/dashboard HTTP/1.1.
  3. WAF Processing:
    • Coraza parses //admin/dashboard.
    • It determines the Host is admin.
    • It determines the Path is /dashboard.
    • It compares /dashboard against the rule @beginsWith /admin.
    • Result: NO MATCH. The packet is forwarded.
  4. Backend Processing:
    • The backend server (e.g., Nginx) receives //admin/dashboard.
    • It normalizes the path (merging slashes) to /admin/dashboard.
    • It serves the content.

[!NOTE] This technique effectively shifts the first directory level into the 'Host' bucket, rendering it invisible to path-based rules. It works best against rules that rely on REQUEST_FILENAME or REQUEST_URI.

The Impact: Why This Matters

The CVSS score of 5.4 might lead you to believe this is a "Medium" severity issue, but in the right context, it is critical. This is a pure Access Control List (ACL) bypass. If your WAF is the only thing standing between the public internet and your internal metrics dashboard, you are in trouble.

While this doesn't directly lead to Remote Code Execution (RCE) on its own, it removes the safety net. It exposes vulnerabilities that the WAF was shielding—like a SQL injection in an /admin endpoint or an unrestricted file upload in a /private folder. It turns a "Defense in Depth" strategy into "Defense in Doubt."

Furthermore, this isn't just about accessing /admin. It can be used to bypass virtual patching. If you deployed a Coraza rule to block a specific exploit path (e.g., blocking /vulnerable-app/login), an attacker simply needs to request //vulnerable-app/login to walk right past your hotfix.

The Fix: Patching and Lessons Learned

The immediate fix is simple: upgrade github.com/corazawaf/coraza/v3 to version 3.3.3 or higher. This version implements url.ParseRequestURI, ensuring that double slashes are treated as part of the path, not the authority.

However, the broader lesson here is about normalization. Security controls must see input in its normalized form before making decisions. If you cannot update Coraza immediately, you can mitigate this at the load balancer level.

For example, if you are running Nginx in front of your application, ensure that merge_slashes is set to on (which is the default). Nginx will rewrite //admin to /admin before the request is even passed to the application logic where Coraza might be running (depending on deployment mode). Never assume that the library you are using perceives reality the same way the OS or the web server does.

Fix Analysis (1)

Technical Appendix

CVSS Score
5.4/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N
EPSS Probability
0.10%
Top 100% most exploited

Affected Systems

OWASP Coraza WAF < 3.3.3Go applications using Coraza as a libraryCaddy with Coraza WAF module (older versions)Traefik with Coraza middleware (older versions)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Coraza WAF
OWASP
< 3.3.33.3.3
AttributeDetail
CWE IDCWE-706 (Incorrectly-Resolved Name)
Attack VectorNetwork (HTTP)
CVSS v3.15.4 (Medium)
ImpactSecurity Bypass / ACL Evasion
Exploit StatusPoC Available
LanguageGo (Golang)
CWE-706
Use of Incorrectly-Resolved Name or Reference

The software uses a name or reference that resolves to a resource different from the intended one, leading to incorrect authorization decisions.

Vulnerability Timeline

Internal discovery of URI normalization issues
2025-01-09
Fix committed to repository
2025-03-17
Coraza v3.3.3 released and Advisory Published
2025-03-20

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.