CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2025-29914
5.40.11%

Double Slash, Double Trouble: Coraza WAF Bypass via Parser Confusion

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 26, 2026·5 min read·32 visits

PoC Available

Executive Summary (TL;DR)

Coraza WAF used the wrong Go URL parser (`url.Parse` instead of `url.ParseRequestURI`). Sending a request like `//admin/` causes Coraza to treat `admin` as a hostname and strip it from the path it inspects. The WAF sees `/`, but the backend sees `/admin/`, completely bypassing path-based access controls.

A subtle but effective parser confusion vulnerability in the OWASP Coraza WAF allows attackers to bypass path-based security rules using double slashes in the URI. By misinterpreting the request path as a scheme-relative URL, Coraza accidentally truncates the first path segment, blinding the WAF to the attacker's true destination.

The Hook: The Bouncer is Drunk

Imagine a nightclub bouncer who has been told strictly: "Do not let anyone named 'Admin' into the VIP room." A man walks up. His ID card says "Admin". The bouncer looks at it, squints, and somehow decides the name is actually empty, just a blank space. He waves the man through. The bartender inside, however, has better eyes. He sees "Admin" clearly and serves him the premium champagne (or the admin panel, in this analogy).

This is exactly what happened with CVE-2025-29914 in the OWASP Coraza WAF. Coraza is a popular, open-source Web Application Firewall written in Go. It is supposed to be the bouncer that inspects HTTP traffic before it hits your application.

However, due to a subtle misuse of Go's standard library, Coraza developed a blind spot. It turns out that simply adding an extra slash (/) to the beginning of your URL was enough to confuse the parser, effectively slicing off the most critical part of the path—the directory you were trying to protect. It's a classic case of Parser Confusion, where the security device and the backend application disagree on what the request actually is.

The Flaw: A Tale of Two Parsers

To understand this bug, we have to look at how URLs are defined in RFC 3986. In the web world, a string starting with // is often a "scheme-relative" or "network-path" reference. For example, if you see <script src="//evil.com/x.js"> in HTML, the browser knows to fetch that resource using the current protocol (HTTP or HTTPS) from the host evil.com.

Coraza is written in Go. Go's standard library has a powerful URL parser: net/url. However, it has two different functions that sound similar but behave very differently:

  1. url.Parse(): Designed for general URL references. It respects the // rule. If you feed it //admin/users, it thinks admin is the Host and /users is the Path.
  2. url.ParseRequestURI(): Designed specifically for the Request-Line in an HTTP request. It assumes the input is a path (unless it's an absolute proxy request). If you feed it //admin/users, it treats the whole thing as the Path.

The developers of Coraza inadvertently used url.Parse(). So, when an attacker sends GET //admin/setup.php, Coraza's internal logic splits it:

  • Host: admin (Wait, what?)
  • Path: /setup.php

Coraza then populates the WAF variable REQUEST_FILENAME with /setup.php. The critical segment admin has vanished into the void of the "Host" field, which isn't checked by path-based rules.

The Code: The Smoking Gun

Let's look at the diff. The fix is a one-liner, but it changes the entire behavior of the application's perception of reality. The vulnerability existed in internal/corazawaf/transaction.go.

The Vulnerable Code:

// internal/corazawaf/transaction.go
 
func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string) {
    // ...
    // WRONG: Interprets //foo as host 'foo'
    parsedURL, err := url.Parse(uri) 
    // ...
}

Because parsedURL.Path was used to populate the REQUEST_FILENAME variable, the WAF was inspecting a truncated version of the attack string.

The Fix (Commit 4722c9ad0d502abd56b8d6733c6b47eb4111742d):

// internal/corazawaf/transaction.go
 
func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string) {
    // ...
    // RIGHT: Interprets //foo as path '//foo'
    parsedURL, err := url.ParseRequestURI(uri)
    // ...
}

By switching to ParseRequestURI, Coraza now correctly understands that in the context of an HTTP Request Line (e.g., GET //admin HTTP/1.1), the double slash is just a part of the path, not a signal to switch hosts.

The Exploit: Bypassing the Rules

Let's construct a scenario. You are running a legacy PHP application behind Coraza. You have a rule to block external access to the /private/ directory.

WAF Rule:

SecRule REQUEST_FILENAME "@beginsWith /private/" "id:1001,phase:1,deny,status:403"

The Attack Chain:

  1. Attacker Request: GET //private/db_dump.sql HTTP/1.1 Host: target.com

  2. Coraza Processing (Vulnerable):

    • Input: //private/db_dump.sql
    • url.Parse behavior: Host = private, Path = /db_dump.sql
    • REQUEST_FILENAME = /db_dump.sql
  3. WAF Logic:

    • Does /db_dump.sql begin with /private/?
    • NO.
    • Result: ALLOW.
  4. Backend Processing (e.g., Nginx/Apache):

    • Web servers are generally very forgiving. They see //private/db_dump.sql.
    • Path Normalization kicks in: // becomes /.
    • Final Path: /private/db_dump.sql.
    • Result: The file is served.

This diagram illustrates the divergence in perception:

This is effectively a "Get Out of Jail Free" card for any rule relying on REQUEST_FILENAME to restrict access to specific directories.

The Fix: Remediation

This vulnerability affects all versions of Coraza prior to 3.3.3. If you are using Coraza as a library in your Go application, or as part of the Coraza Caddy plugin (depending on the version bundled), you are likely vulnerable.

Immediate Actions:

  1. Upgrade: Update your go.mod to require github.com/corazawaf/coraza/v3 v3.3.3.
  2. Verify: If you cannot upgrade immediately, you might be able to mitigate this at the ingress level (e.g., in an upstream Nginx or Load Balancer) by merging slashes before traffic hits Coraza. However, upgrading the library is the only robust fix.

The patch ensures that // paths are preserved in REQUEST_FILENAME, allowing your regex rules to match them correctly. If you have rules like @beginsWith /admin, they will now correctly see //admin (assuming you handle the extra slash in your regex or the WAF normalizes it correctly after parsing).

Official Patches

CorazaRelease notes for version 3.3.3
GitHubCommit fixing the parser logic

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.11%
Top 70% most exploited

Affected Systems

OWASP Coraza WAF < 3.3.3Applications embedding Coraza WAF

Affected Versions Detail

Product
Affected Versions
Fixed Version
Coraza
OWASP
< 3.3.33.3.3
AttributeDetail
CWECWE-706 (Incorrect Resolution of Path)
CVSS5.4 (Medium)
VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N
Attack VectorNetwork (HTTP)
ImpactSecurity Rule Bypass
StatusPatched in v3.3.3

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1006Direct Volume Access
Defense Evasion
CWE-706
Use of Incorrectly-Resolved Name or Reference

The product uses a name or reference to resolve a resource, but the name/reference resolves to a different resource than intended.

Known Exploits & Detection

TheorySend GET //path/to/bypass to evade path-based blocking rules.

Vulnerability Timeline

Vulnerability Disclosed
2025-03-20
Patch Released (v3.3.3)
2025-03-20
GHSA Published
2025-03-20

References & Sources

  • [1]GHSA-q9f5-625g-xm39
  • [2]Go Documentation: ParseRequestURI
  • [3]RFC 3986: Relative Reference

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.