Feb 26, 2026·5 min read·32 visits
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.
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.
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:
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.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:
admin (Wait, what?)/setup.phpCoraza 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.
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.
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:
Attacker Request:
GET //private/db_dump.sql HTTP/1.1
Host: target.com
Coraza Processing (Vulnerable):
//private/db_dump.sqlurl.Parse behavior: Host = private, Path = /db_dump.sqlREQUEST_FILENAME = /db_dump.sqlWAF Logic:
/db_dump.sql begin with /private/?Backend Processing (e.g., Nginx/Apache):
//private/db_dump.sql.// becomes /./private/db_dump.sql.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.
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:
go.mod to require github.com/corazawaf/coraza/v3 v3.3.3.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).
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Coraza OWASP | < 3.3.3 | 3.3.3 |
| Attribute | Detail |
|---|---|
| CWE | CWE-706 (Incorrect Resolution of Path) |
| CVSS | 5.4 (Medium) |
| Vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N |
| Attack Vector | Network (HTTP) |
| Impact | Security Rule Bypass |
| Status | Patched in v3.3.3 |
The product uses a name or reference to resolve a resource, but the name/reference resolves to a different resource than intended.